From f4c2f268642db18eb111410a2867832f52dc1c19 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 18:00:04 +0200 Subject: [PATCH 001/428] Implement draft for opengl reviews from Houdini --- openpype/hosts/houdini/api/lib.py | 2 + .../houdini/plugins/create/create_review.py | 57 +++++++++++++++++++ .../houdini/plugins/publish/collect_frames.py | 2 +- .../houdini/plugins/publish/extract_review.py | 52 +++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_review.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_review.py diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index c8a7f92bb9..aa7fb66aea 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -125,6 +125,8 @@ def get_output_parameter(node): return node.parm("filename") elif node_type == "comp": return node.parm("copoutput") + elif node_type == "opengl": + return node.parm("picture") elif node_type == "arnold": if node.evalParm("ar_ass_export_enable"): return node.parm("ar_ass_file") diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py new file mode 100644 index 0000000000..3d0f33ad51 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -0,0 +1,57 @@ +from openpype.hosts.houdini.api import plugin + + +class CreateReview(plugin.Creator): + """Review with OpenGL ROP""" + + label = "Review" + family = "review" + icon = "video-camera" + + def __init__(self, *args, **kwargs): + super(CreateReview, self).__init__(*args, **kwargs) + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + self.data.update({"node_type": "opengl"}) + + def _process(self, instance): + """Creator main entry point. + + Args: + instance (hou.Node): Created Houdini instance. + + """ + import hou + + frame_range = hou.playbar.frameRange() + + parms = { + "picture": "$HIP/pyblish/{0}/{0}.$F4.png".format(self.name), + # Render frame range + "trange": 1, + + # Unlike many other ROP nodes the opengl node does not default + # to expression of $FSTART and $FEND so we preserve that behavior + # but do set the range to the frame range of the playbar + "f1": frame_range[0], + "f2": frame_range[1] + } + + if self.nodes: + # todo: allow only object paths? + node_paths = " ".join(node.path() for node in self.nodes) + parms.update({"scenepath": node_paths}) + + instance.setParms(parms) + + # Lock any parameters in this list + to_lock = [ + # Lock some Avalon attributes + "family", + "id", + ] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index 9bd43d8a09..4ceeac3a62 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -20,7 +20,7 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"] + families = ["vdbcache", "imagesequence", "ass", "redshiftproxy", "review"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/extract_review.py b/openpype/hosts/houdini/plugins/publish/extract_review.py new file mode 100644 index 0000000000..18b2765b32 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_review.py @@ -0,0 +1,52 @@ +import os + +import pyblish.api +import openpype.api +from openpype.hosts.houdini.api.lib import render_rop + + +class ExtractOpenGL(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract OpenGL (Review)" + families = ["review"] + hosts = ["houdini"] + optional = True + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("picture") + staging_dir = os.path.dirname(output) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + # We run the render + self.log.info("Extracting '%s' to '%s'" % (file_name, staging_dir)) + render_rop(ropnode) + + # Unfortunately user interrupting the extraction does not raise an + # error and thus still continues to the integrator. To capture that + # we make sure all files exist + files = instance.data["frames"] + missing = [fname for fname in files + if not os.path.exists(os.path.join(staging_dir, fname))] + if missing: + raise RuntimeError("Failed to complete review extraction. " + "Missing output files: {}".format(missing)) + + representation = { + "name": "png", + "ext": "png", + "files": files, + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + } + + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(representation) From 1f05d5b532e36c734db9a820bbb5fc6c2d37d8cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 18:07:09 +0200 Subject: [PATCH 002/428] Add houdini to ExtractReview --- openpype/plugins/publish/extract_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 27117510b2..7daaeef022 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -41,6 +41,7 @@ class ExtractReview(pyblish.api.InstancePlugin): hosts = [ "nuke", "maya", + "houdini", "shell", "hiero", "premiere", From a2f077555a403af8d5ef5a6b1fd29d3db290f655 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 18:10:54 +0200 Subject: [PATCH 003/428] Add review tag to representation --- openpype/hosts/houdini/plugins/publish/extract_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/plugins/publish/extract_review.py b/openpype/hosts/houdini/plugins/publish/extract_review.py index 18b2765b32..3bf4e39aeb 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_review.py +++ b/openpype/hosts/houdini/plugins/publish/extract_review.py @@ -45,6 +45,7 @@ class ExtractOpenGL(openpype.api.Extractor): "stagingDir": staging_dir, "frameStart": instance.data["frameStart"], "frameEnd": instance.data["frameEnd"], + "tags": ["review"] } if "representations" not in instance.data: From 79812118215b11280dac110f5ab2736591e6c820 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 22:35:05 +0200 Subject: [PATCH 004/428] Set resolution on OpenGL rop node instead of inheriting from camera node --- .../houdini/plugins/create/create_review.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 3d0f33ad51..f24fa7e1cd 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -8,6 +8,13 @@ class CreateReview(plugin.Creator): family = "review" icon = "video-camera" + # Default settings for the ROP + # todo: expose in OpenPype settings? + override_resolution = True + width = 1280 + height = 720 + aspect = 1.0 + def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -36,9 +43,18 @@ class CreateReview(plugin.Creator): # to expression of $FSTART and $FEND so we preserve that behavior # but do set the range to the frame range of the playbar "f1": frame_range[0], - "f2": frame_range[1] + "f2": frame_range[1], } + if self.override_resolution: + # Override resolution + parms.update({ + "tres": True, # Override Camera Resolution + "res0": self.width, + "res1": self.height, + "aspect": self.aspect + }) + if self.nodes: # todo: allow only object paths? node_paths = " ".join(node.path() for node in self.nodes) From 4cb8276287de0dd083ad1e48cb0e8dc98a6ae3a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 22:39:01 +0200 Subject: [PATCH 005/428] Fix parm names --- openpype/hosts/houdini/plugins/create/create_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index f24fa7e1cd..36d87ba8de 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -50,8 +50,8 @@ class CreateReview(plugin.Creator): # Override resolution parms.update({ "tres": True, # Override Camera Resolution - "res0": self.width, - "res1": self.height, + "res1": self.width, + "res2": self.height, "aspect": self.aspect }) From c687fe0b818a3bf04da1f3302485e998727ba37c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 00:29:30 +0200 Subject: [PATCH 006/428] Relabel to Extract OpenGL to avoid confusion with global Extract Review plugin --- .../plugins/publish/{extract_review.py => extract_opengl.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename openpype/hosts/houdini/plugins/publish/{extract_review.py => extract_opengl.py} (97%) diff --git a/openpype/hosts/houdini/plugins/publish/extract_review.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py similarity index 97% rename from openpype/hosts/houdini/plugins/publish/extract_review.py rename to openpype/hosts/houdini/plugins/publish/extract_opengl.py index 3bf4e39aeb..f99c987634 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_review.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import render_rop class ExtractOpenGL(openpype.api.Extractor): order = pyblish.api.ExtractorOrder - label = "Extract OpenGL (Review)" + label = "Extract OpenGL" families = ["review"] hosts = ["houdini"] optional = True From 937d0c5833ff41e57ae9deafede32f1aac55d42a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Sep 2022 00:29:39 +0200 Subject: [PATCH 007/428] Add houdini --- openpype/plugins/publish/extract_burnin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 4179199317..5a5fda38cd 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -50,7 +50,8 @@ class ExtractBurnin(publish.Extractor): "webpublisher", "aftereffects", "photoshop", - "flame" + "flame", + "houdini" # "resolve" ] optional = True From 2fdd13738066f320359a2cf7d84407bcd93e8fbd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 28 Nov 2022 09:40:42 +0000 Subject: [PATCH 008/428] Optional control of display lights on playblast. --- openpype/hosts/maya/plugins/create/create_review.py | 2 ++ openpype/hosts/maya/plugins/publish/extract_playblast.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index ba51ffa009..65aeb2d76a 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -25,6 +25,7 @@ class CreateReview(plugin.Creator): "depth peeling", "alpha cut" ] + displayLights = ["default", "all", "selected", "active", "none"] def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -41,5 +42,6 @@ class CreateReview(plugin.Creator): data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane data["transparency"] = self.transparency + data["displayLights"] = self.displayLights self.data = data diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index b19d24fad7..cbf99eccaa 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -97,6 +97,10 @@ class ExtractPlayblast(publish.Extractor): refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True)) pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) + + # Show lighting mode. + index = instance.data.get("displayLights", 0) + preset["viewport_options"]["displayLights"] = self.displayLights[index] # Override transparency if requested. transparency = instance.data.get("transparency", 0) From e214062047f09e6b0879cb015e6445b889b0d33a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Dec 2022 12:19:57 +0000 Subject: [PATCH 009/428] Missing class data. --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index cbf99eccaa..c6bbe44efc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -23,6 +23,7 @@ class ExtractPlayblast(publish.Extractor): families = ["review"] optional = True capture_preset = {} + displayLights = ["default", "all", "selected", "active", "none"] def process(self, instance): self.log.info("Extracting capture..") @@ -97,7 +98,7 @@ class ExtractPlayblast(publish.Extractor): refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True)) pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) - + # Show lighting mode. index = instance.data.get("displayLights", 0) preset["viewport_options"]["displayLights"] = self.displayLights[index] From 6bc8748b99e58894479071e14b04780a3da9cd15 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 14 Dec 2022 09:18:45 +0000 Subject: [PATCH 010/428] Collect display lights list in lib. --- openpype/hosts/maya/api/lib.py | 2 ++ openpype/hosts/maya/plugins/create/create_review.py | 3 +-- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 2530021eba..617e4e3d3a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -113,6 +113,8 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94} RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"] +DISPLAY_LIGHTS = ["default", "all", "selected", "active", "none"] + def get_main_window(): """Acquire Maya's main window""" diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 65aeb2d76a..1935d18deb 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -25,7 +25,6 @@ class CreateReview(plugin.Creator): "depth peeling", "alpha cut" ] - displayLights = ["default", "all", "selected", "active", "none"] def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -42,6 +41,6 @@ class CreateReview(plugin.Creator): data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane data["transparency"] = self.transparency - data["displayLights"] = self.displayLights + data["displayLights"] = lib.DISPLAY_LIGHTS self.data = data diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index c6bbe44efc..d8e1184335 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -23,7 +23,6 @@ class ExtractPlayblast(publish.Extractor): families = ["review"] optional = True capture_preset = {} - displayLights = ["default", "all", "selected", "active", "none"] def process(self, instance): self.log.info("Extracting capture..") @@ -101,7 +100,7 @@ class ExtractPlayblast(publish.Extractor): # Show lighting mode. index = instance.data.get("displayLights", 0) - preset["viewport_options"]["displayLights"] = self.displayLights[index] + preset["viewport_options"]["displayLights"] = lib.DISPLAY_LIGHTS[index] # Override transparency if requested. transparency = instance.data.get("transparency", 0) From c921bc14c56ec3780981e6a432a26bc32fd84235 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 15 Dec 2022 08:57:22 +0000 Subject: [PATCH 011/428] Convert enum to string in collector --- openpype/hosts/maya/plugins/publish/collect_review.py | 5 +++++ openpype/hosts/maya/plugins/publish/extract_playblast.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index eb872c2935..995bd23687 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -5,6 +5,7 @@ import pyblish.api from openpype.client import get_subset_by_name from openpype.pipeline import legacy_io +from openpype.hosts.maya.api import lib class CollectReview(pyblish.api.InstancePlugin): @@ -139,3 +140,7 @@ class CollectReview(pyblish.api.InstancePlugin): "filename": node.filename.get() } ) + + # Convert enum attribute to string. + index = instance.data.get("displayLights", 0) + instance.data["displayLights"] = lib.DISPLAY_LIGHTS[index] diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index d8e1184335..08eb754c6d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -99,8 +99,8 @@ class ExtractPlayblast(publish.Extractor): pm.currentTime(refreshFrameInt, edit=True) # Show lighting mode. - index = instance.data.get("displayLights", 0) - preset["viewport_options"]["displayLights"] = lib.DISPLAY_LIGHTS[index] + display_lights = instance.data["displayLights"] + preset["viewport_options"]["displayLights"] = display_lights # Override transparency if requested. transparency = instance.data.get("transparency", 0) From 9284e986d1f3d3131bb4b4cce1a2808cad141bb0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 15 Dec 2022 08:57:36 +0000 Subject: [PATCH 012/428] Use display lights in thumbnail --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 712159c2be..bb9cef2c5c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -105,6 +105,10 @@ class ExtractThumbnail(publish.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) + # Show lighting mode. + display_lights = instance.data["displayLights"] + preset["viewport_options"]["displayLights"] = display_lights + # Isolate view is requested by having objects in the set besides a # camera. if preset.pop("isolate_view", False) and instance.data.get("isolate"): From 67b95c218b51e5c87132e5270d0a7c47760ed7e5 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 15 Dec 2022 13:13:48 +0000 Subject: [PATCH 013/428] Update openpype/hosts/maya/plugins/publish/collect_review.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 995bd23687..d15eb7a12b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -141,6 +141,6 @@ class CollectReview(pyblish.api.InstancePlugin): } ) - # Convert enum attribute to string. + # Convert enum attribute index to string. index = instance.data.get("displayLights", 0) instance.data["displayLights"] = lib.DISPLAY_LIGHTS[index] From 9a6dc109254ab4d1aca99f4abd3464d65b4e46c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 03:02:31 +0100 Subject: [PATCH 014/428] Initial draft for Substance Painter integration --- openpype/hosts/substancepainter/__init__.py | 10 + openpype/hosts/substancepainter/addon.py | 34 +++ .../hosts/substancepainter/api/__init__.py | 8 + .../hosts/substancepainter/api/pipeline.py | 234 ++++++++++++++++++ .../deploy/plugins/openpype_plugin.py | 15 ++ .../resources/app_icons/substancepainter.png | Bin 0 -> 107059 bytes .../system_settings/applications.json | 27 ++ openpype/settings/entities/enum_entity.py | 1 + .../schema_substancepainter.json | 40 +++ .../system_schema/schema_applications.json | 4 + 10 files changed, 373 insertions(+) create mode 100644 openpype/hosts/substancepainter/__init__.py create mode 100644 openpype/hosts/substancepainter/addon.py create mode 100644 openpype/hosts/substancepainter/api/__init__.py create mode 100644 openpype/hosts/substancepainter/api/pipeline.py create mode 100644 openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py create mode 100644 openpype/resources/app_icons/substancepainter.png create mode 100644 openpype/settings/entities/schemas/system_schema/host_settings/schema_substancepainter.json diff --git a/openpype/hosts/substancepainter/__init__.py b/openpype/hosts/substancepainter/__init__.py new file mode 100644 index 0000000000..4c33b9f507 --- /dev/null +++ b/openpype/hosts/substancepainter/__init__.py @@ -0,0 +1,10 @@ +from .addon import ( + SubstanceAddon, + SUBSTANCE_HOST_DIR, +) + + +__all__ = ( + "SubstanceAddon", + "SUBSTANCE_HOST_DIR" +) diff --git a/openpype/hosts/substancepainter/addon.py b/openpype/hosts/substancepainter/addon.py new file mode 100644 index 0000000000..bb55f20189 --- /dev/null +++ b/openpype/hosts/substancepainter/addon.py @@ -0,0 +1,34 @@ +import os +from openpype.modules import OpenPypeModule, IHostAddon + +SUBSTANCE_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class SubstanceAddon(OpenPypeModule, IHostAddon): + name = "substancepainter" + host_name = "substancepainter" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(self, env, _app): + # Add requirements to SUBSTANCE_PAINTER_PLUGINS_PATH + plugin_path = os.path.join(SUBSTANCE_HOST_DIR, "deploy") + plugin_path = plugin_path.replace("\\", "/") + if env.get("SUBSTANCE_PAINTER_PLUGINS_PATH"): + plugin_path += os.pathsep + env["SUBSTANCE_PAINTER_PLUGINS_PATH"] + + env["SUBSTANCE_PAINTER_PLUGINS_PATH"] = plugin_path + + # Fix UI scale issue + env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(SUBSTANCE_HOST_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".spp", ".toc"] diff --git a/openpype/hosts/substancepainter/api/__init__.py b/openpype/hosts/substancepainter/api/__init__.py new file mode 100644 index 0000000000..937d0c429e --- /dev/null +++ b/openpype/hosts/substancepainter/api/__init__.py @@ -0,0 +1,8 @@ +from .pipeline import ( + SubstanceHost, + +) + +__all__ = [ + "SubstanceHost", +] diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py new file mode 100644 index 0000000000..3fd081ca1c --- /dev/null +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +"""Pipeline tools for OpenPype Gaffer integration.""" +import os +import sys +import logging +from functools import partial + +# Substance 3D Painter modules +import substance_painter.ui +import substance_painter.event +import substance_painter.export +import substance_painter.project +import substance_painter.textureset + +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost + +import pyblish.api + +from openpype.pipeline import ( + register_creator_plugin_path, + register_loader_plugin_path, + AVALON_CONTAINER_ID +) +from openpype.lib import ( + register_event_callback, + emit_event, +) +from openpype.pipeline.load import any_outdated_containers +from openpype.hosts.substancepainter import SUBSTANCE_HOST_DIR + +log = logging.getLogger("openpype.hosts.substance") + +PLUGINS_DIR = os.path.join(SUBSTANCE_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") + +self = sys.modules[__name__] +self.menu = None +self.callbacks = [] + + +class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): + name = "substancepainter" + + def __init__(self): + super(SubstanceHost, self).__init__() + self._has_been_setup = False + + def install(self): + pyblish.api.register_host("substancepainter") + + pyblish.api.register_plugin_path(PUBLISH_PATH) + register_loader_plugin_path(LOAD_PATH) + register_creator_plugin_path(CREATE_PATH) + + log.info("Installing callbacks ... ") + # register_event_callback("init", on_init) + _register_callbacks() + # register_event_callback("before.save", before_save) + # register_event_callback("save", on_save) + register_event_callback("open", on_open) + # register_event_callback("new", on_new) + + log.info("Installing menu ... ") + _install_menu() + + self._has_been_setup = True + + def uninstall(self): + _uninstall_menu() + _deregister_callbacks() + + def has_unsaved_changes(self): + + if not substance_painter.project.is_open(): + return False + + return substance_painter.project.needs_saving() + + def get_workfile_extensions(self): + return [".spp", ".toc"] + + def save_workfile(self, dst_path=None): + + if not substance_painter.project.is_open(): + return False + + if not dst_path: + dst_path = self.get_current_workfile() + + full_save_mode = substance_painter.project.ProjectSaveMode.Full + substance_painter.project.save_as(dst_path, full_save_mode) + + return dst_path + + def open_workfile(self, filepath): + + if not os.path.exists(filepath): + raise RuntimeError("File does not exist: {}".format(filepath)) + + # We must first explicitly close current project before opening another + if substance_painter.project.is_open(): + substance_painter.project.close() + + substance_painter.project.open(filepath) + return filepath + + def get_current_workfile(self): + if not substance_painter.project.is_open(): + return None + + filepath = substance_painter.project.file_path() + if filepath.endswith(".spt"): + # When currently in a Substance Painter template assume our + # scene isn't saved. This can be the case directly after doing + # "New project", the path will then be the template used. This + # avoids Workfiles tool trying to save as .spt extension if the + # file hasn't been saved before. + return + + return filepath + + def get_containers(self): + return [] + + @staticmethod + def create_context_node(): + pass + + def update_context_data(self, data, changes): + pass + + def get_context_data(self): + pass + + +def _install_menu(): + from PySide2 import QtWidgets + from openpype.tools.utils import host_tools + + parent = substance_painter.ui.get_main_window() + + menu = QtWidgets.QMenu("OpenPype") + + action = menu.addAction("Load...") + action.triggered.connect( + lambda: host_tools.show_loader(parent=parent, use_context=True) + ) + + action = menu.addAction("Publish...") + action.triggered.connect( + lambda: host_tools.show_publisher(parent=parent) + ) + + action = menu.addAction("Manage...") + action.triggered.connect( + lambda: host_tools.show_scene_inventory(parent=parent) + ) + + action = menu.addAction("Library...") + action.triggered.connect( + lambda: host_tools.show_library_loader(parent=parent) + ) + + menu.addSeparator() + action = menu.addAction("Work Files...") + action.triggered.connect( + lambda: host_tools.show_workfiles(parent=parent) + ) + + substance_painter.ui.add_menu(menu) + + def on_menu_destroyed(): + self.menu = None + + menu.destroyed.connect(on_menu_destroyed) + + self.menu = menu + + +def _uninstall_menu(): + if self.menu: + self.menu.destroy() + self.menu = None + + +def _register_callbacks(): + # Prepare emit event callbacks + open_callback = partial(emit_event, "open") + + # Connect to the Substance Painter events + dispatcher = substance_painter.event.DISPATCHER + for event, callback in [ + (substance_painter.event.ProjectOpened, open_callback) + ]: + dispatcher.connect(event, callback) + # Keep a reference so we can deregister if needed + self.callbacks.append((event, callback)) + + +def _deregister_callbacks(): + for event, callback in self.callbacks: + substance_painter.event.DISPATCHER.disconnect(event, callback) + + +def on_open(): + log.info("Running callback on open..") + print("Run") + + if any_outdated_containers(): + from openpype.widgets import popup + + log.warning("Scene has outdated content.") + + # Get main window + parent = substance_painter.ui.get_main_window() + if parent is None: + log.info("Skipping outdated content pop-up " + "because Substance window can't be found.") + else: + + # Show outdated pop-up + def _on_show_inventory(): + from openpype.tools.utils import host_tools + host_tools.show_scene_inventory(parent=parent) + + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Substance scene has outdated content") + dialog.setMessage("There are outdated containers in " + "your Substance scene.") + dialog.on_clicked.connect(_on_show_inventory) + dialog.show() \ No newline at end of file diff --git a/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py b/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py new file mode 100644 index 0000000000..01779156f1 --- /dev/null +++ b/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py @@ -0,0 +1,15 @@ + +def start_plugin(): + from openpype.pipeline import install_host + from openpype.hosts.substancepainter.api import SubstanceHost + + install_host(SubstanceHost()) + + +def close_plugin(): + from openpype.pipeline import uninstall_host + uninstall_host() + + +if __name__ == "__main__": + start_plugin() diff --git a/openpype/resources/app_icons/substancepainter.png b/openpype/resources/app_icons/substancepainter.png new file mode 100644 index 0000000000000000000000000000000000000000..dc46f25d747f6626fd085818bbc593636898eecf GIT binary patch literal 107059 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 Wwf31l8gz< Date: Fri, 6 Jan 2023 04:04:32 +0100 Subject: [PATCH 015/428] Add substance mesh loader --- .../plugins/load/load_mesh.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 openpype/hosts/substancepainter/plugins/load/load_mesh.py diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py new file mode 100644 index 0000000000..7cc5e35912 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -0,0 +1,98 @@ +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.pipeline import legacy_io + +import substance_painter.project +import qargparse + + +class SubstanceLoadProjectMesh(load.LoaderPlugin): + """Load mesh for project""" + + families = ["*"] + representations = ["abc", "fbx", "obj", "gltf"] + + label = "Load mesh" + order = -10 + icon = "code-fork" + color = "orange" + + options = [ + qargparse.Boolean( + "preserve_strokes", + default=True, + help="Preserve strokes positions on mesh.\n" + "(only relevant when loading into existing project)" + ), + qargparse.Boolean( + "import_cameras", + default=True, + help="Import cameras from the mesh file." + ) + ] + + def load(self, context, name, namespace, data): + + if not substance_painter.project.is_open(): + # Allow to 'initialize' a new project + # TODO: preferably these settings would come from the actual + # new project prompt of Substance (or something that is + # visually similar to still allow artist decisions) + settings = substance_painter.project.Settings( + default_texture_resolution=4096, + import_cameras=data.get("import_cameras", True), + ) + + substance_painter.project.create( + mesh_file_path=self.fname, + settings=settings + ) + return + + # Reload the mesh + settings = substance_painter.project.MeshReloadingSettings( + import_cameras=data.get("import_cameras", True), + preserve_strokes=data.get("preserve_strokes", True) + ) + + def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): + if status == substance_painter.project.ReloadMeshStatus.SUCCESS: + print("Reload succeeded") + else: + raise RuntimeError("Reload of mesh failed") + + path = self.fname + substance_painter.project.reload_mesh(path, settings, on_mesh_reload) + + # TODO: Register with the project so host.get_containers() can return + # the loaded content in manager + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + + path = get_representation_path(representation) + + # Reload the mesh + # TODO: Re-use settings from first load? + settings = substance_painter.project.MeshReloadingSettings( + import_cameras=True, + preserve_strokes=True + ) + + def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): + if status == substance_painter.project.ReloadMeshStatus.SUCCESS: + print("Reload succeeded") + else: + raise RuntimeError("Reload of mesh failed") + + substance_painter.project.reload_mesh(path, settings, on_mesh_reload) + + def remove(self, container): + + # Remove OpenPype related settings about what model was loaded + # or close the project? + pass From 3cb797b10a04726183ca740a5f10b593be45aea1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 04:05:13 +0100 Subject: [PATCH 016/428] Add some fixes to stylesheet to avoid very odd looking OpenPype UIs in Substance Painter --- openpype/style/style.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index a7a48cdb9d..ae1b9d2991 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -127,6 +127,7 @@ QPushButton { border-radius: 0.2em; padding: 3px 5px 3px 5px; background: {color:bg-buttons}; + min-width: 0px; /* Substance Painter fix */ } QPushButton:hover { @@ -328,7 +329,15 @@ QTabWidget::tab-bar { alignment: left; } +/* avoid QTabBar overrides in Substance Painter */ +QTabBar { + text-transform: none; + font-weight: normal; +} + QTabBar::tab { + text-transform: none; + font-weight: normal; border-top: 1px solid {color:border}; border-left: 1px solid {color:border}; border-right: 1px solid {color:border}; @@ -368,6 +377,7 @@ QHeaderView { QHeaderView::section { background: {color:bg-view-header}; padding: 4px; + border-top: 0px; /* Substance Painter fix */ border-right: 1px solid {color:bg-view}; border-radius: 0px; text-align: center; From e710a8dc70496e042e000da50c5ad2181376c84a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 05:03:19 +0100 Subject: [PATCH 017/428] Fix bug if file wasn't saved yet, file_path() would return None --- openpype/hosts/substancepainter/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 3fd081ca1c..31c87f079d 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -112,7 +112,7 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return None filepath = substance_painter.project.file_path() - if filepath.endswith(".spt"): + if filepath and filepath.endswith(".spt"): # When currently in a Substance Painter template assume our # scene isn't saved. This can be the case directly after doing # "New project", the path will then be the template used. This From 8468dbce679cc5dfee58e99e4015bb812f47080d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 05:04:53 +0100 Subject: [PATCH 018/428] Implement managing for Load Mesh (draft implementation) --- .../hosts/substancepainter/api/pipeline.py | 47 +++++++++++- .../plugins/load/load_mesh.py | 71 ++++++++++++++----- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 31c87f079d..4d49fa83d7 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -123,7 +123,16 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return filepath def get_containers(self): - return [] + + if not substance_painter.project.is_open(): + return + + metadata = substance_painter.project.Metadata("OpenPype") + containers = metadata.get("containers") + if containers: + for key, container in containers.items(): + container["objectName"] = key + yield container @staticmethod def create_context_node(): @@ -231,4 +240,38 @@ def on_open(): dialog.setMessage("There are outdated containers in " "your Substance scene.") dialog.on_clicked.connect(_on_show_inventory) - dialog.show() \ No newline at end of file + dialog.show() + + +def imprint_container(container, + name, + namespace, + context, + loader): + """Imprint a loaded container with metadata. + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + container (dict): The (substance metadata) dictionary to imprint into. + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (load.LoaderPlugin): loader instance used to produce container. + + Returns: + None + + """ + + data = [ + ("schema", "openpype:container-2.0"), + ("id", AVALON_CONTAINER_ID), + ("name", str(name)), + ("namespace", str(namespace) if namespace else None), + ("loader", str(loader.__class__.__name__)), + ("representation", str(context["representation"]["_id"])), + ] + for key, value in data: + container[key] = value diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 7cc5e35912..519ed3ad4e 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,12 +2,27 @@ from openpype.pipeline import ( load, get_representation_path, ) -from openpype.pipeline import legacy_io +from openpype.hosts.substancepainter.api.pipeline import imprint_container import substance_painter.project import qargparse +def set_container(key, container): + metadata = substance_painter.project.Metadata("OpenPype") + containers = metadata.get("containers") or {} + containers[key] = container + metadata.set("containers", containers) + + +def remove_container(key): + metadata = substance_painter.project.Metadata("OpenPype") + containers = metadata.get("containers") + if containers: + containers.pop(key, None) + metadata.set("containers", containers) + + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -33,6 +48,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) ] + container_key = "ProjectMesh" + def load(self, context, name, namespace, data): if not substance_painter.project.is_open(): @@ -49,25 +66,34 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): mesh_file_path=self.fname, settings=settings ) - return - # Reload the mesh - settings = substance_painter.project.MeshReloadingSettings( - import_cameras=data.get("import_cameras", True), - preserve_strokes=data.get("preserve_strokes", True) - ) + else: + # Reload the mesh + settings = substance_painter.project.MeshReloadingSettings( + import_cameras=data.get("import_cameras", True), + preserve_strokes=data.get("preserve_strokes", True) + ) - def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): - if status == substance_painter.project.ReloadMeshStatus.SUCCESS: - print("Reload succeeded") - else: - raise RuntimeError("Reload of mesh failed") + def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa + if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa + print("Reload succeeded") + else: + raise RuntimeError("Reload of mesh failed") - path = self.fname - substance_painter.project.reload_mesh(path, settings, on_mesh_reload) + path = self.fname + substance_painter.project.reload_mesh(path, + settings, + on_mesh_reload) - # TODO: Register with the project so host.get_containers() can return - # the loaded content in manager + # Store container + container = {} + imprint_container(container, + name=self.container_key, + namespace=self.container_key, + context=context, + loader=self) + container["options"] = data + set_container(self.container_key, container) def switch(self, container, representation): self.update(container, representation) @@ -78,9 +104,10 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Reload the mesh # TODO: Re-use settings from first load? + container_options = container.get("options", {}) settings = substance_painter.project.MeshReloadingSettings( - import_cameras=True, - preserve_strokes=True + import_cameras=container_options.get("import_cameras", True), + preserve_strokes=container_options.get("preserve_strokes", True) ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): @@ -91,8 +118,14 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): substance_painter.project.reload_mesh(path, settings, on_mesh_reload) + # Update container representation + container["representation"] = str(representation["_id"]) + set_container(self.container_key, container) + def remove(self, container): # Remove OpenPype related settings about what model was loaded # or close the project? - pass + # TODO: This is likely best 'hidden' away to the user because + # this will leave the project's mesh unmanaged. + remove_container(self.container_key) From 30764456afa4f92053b61d6a3e39576874c235a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 05:22:59 +0100 Subject: [PATCH 019/428] Add launch with last workfile support for Substance Painter --- openpype/hooks/pre_add_last_workfile_arg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 3609620917..d5a9a41e5a 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -23,6 +23,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "blender", "photoshop", "tvpaint", + "substance", "aftereffects" ] From bcac4d1fafde2a3a2b7ce6f426d603d586b4df05 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 12:17:49 +0100 Subject: [PATCH 020/428] Add draft for workfile Creator --- .../plugins/create/create_workfile.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 openpype/hosts/substancepainter/plugins/create/create_workfile.py diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py new file mode 100644 index 0000000000..cec760040b --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating workfiles.""" + +from openpype.pipeline import CreatedInstance, AutoCreator +from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_name + +import substance_painter.project + + +def set_workfile_data(data, update=False): + if update: + data = get_workfile_data().update(data) + metadata = substance_painter.project.Metadata("OpenPype") + metadata.set("workfile", data) + + +def get_workfile_data(): + metadata = substance_painter.project.Metadata("OpenPype") + return metadata.get("workfile") or {} + + +class CreateWorkfile(AutoCreator): + """Workfile auto-creator.""" + identifier = "io.openpype.creators.substancepainter.workfile" + label = "Workfile" + family = "workfile" + icon = "document" + + default_variant = "Main" + + def create(self): + + variant = self.default_variant + project_name = self.project_name + asset_name = legacy_io.Session["AVALON_ASSET"] + task_name = legacy_io.Session["AVALON_TASK"] + host_name = legacy_io.Session["AVALON_APP"] + + # Workfile instance should always exist and must only exist once. + # As such we'll first check if it already exists and is collected. + current_instance = next( + ( + instance for instance in self.create_context.instances + if instance.creator_identifier == self.identifier + ), None) + + if current_instance is None: + self.log.info("Auto-creating workfile instance...") + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + data = { + "asset": asset_name, + "task": task_name, + "variant": variant + } + current_instance = self.create_instance_in_context(subset_name, + data) + elif ( + current_instance["asset"] != asset_name + or current_instance["task"] != task_name + ): + # Update instance context if is not the same + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + current_instance["asset"] = asset_name + current_instance["task"] = task_name + current_instance["subset"] = subset_name + + set_workfile_data(current_instance.data_to_store()) + + def collect_instances(self): + workfile = get_workfile_data() + if not workfile: + return + self.create_instance_in_context_from_existing(workfile) + + def update_instances(self, update_list): + for instance, _changes in update_list: + set_workfile_data(instance.data_to_store(), update=True) + + # Helper methods (this might get moved into Creator class) + def create_instance_in_context(self, subset_name, data): + instance = CreatedInstance( + self.family, subset_name, data, self + ) + self.create_context.creator_adds_instance(instance) + return instance + + def create_instance_in_context_from_existing(self, data): + instance = CreatedInstance.from_existing(data, self) + self.create_context.creator_adds_instance(instance) + return instance From 1c4ff746adaee6e2ac34f765d57f64bda967765e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 6 Jan 2023 16:10:26 +0100 Subject: [PATCH 021/428] Remove 'fix' which didn't originally fix the UI issue - it was a styleSheet issue --- openpype/hosts/substancepainter/addon.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/substancepainter/addon.py b/openpype/hosts/substancepainter/addon.py index bb55f20189..6288ef1559 100644 --- a/openpype/hosts/substancepainter/addon.py +++ b/openpype/hosts/substancepainter/addon.py @@ -20,9 +20,6 @@ class SubstanceAddon(OpenPypeModule, IHostAddon): env["SUBSTANCE_PAINTER_PLUGINS_PATH"] = plugin_path - # Fix UI scale issue - env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) - def get_launch_hook_paths(self, app): if app.host_name != self.host_name: return [] From 82639e8634587b7f63c703903c947c13f5e6f327 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 7 Jan 2023 16:18:07 +0100 Subject: [PATCH 022/428] Avoid trying to import blessed terminal coloring in Substance Painter --- openpype/hosts/substancepainter/addon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/addon.py b/openpype/hosts/substancepainter/addon.py index 6288ef1559..2fbea139c5 100644 --- a/openpype/hosts/substancepainter/addon.py +++ b/openpype/hosts/substancepainter/addon.py @@ -20,6 +20,9 @@ class SubstanceAddon(OpenPypeModule, IHostAddon): env["SUBSTANCE_PAINTER_PLUGINS_PATH"] = plugin_path + # Log in Substance Painter doesn't support custom terminal colors + env["OPENPYPE_LOG_NO_COLORS"] = "Yes" + def get_launch_hook_paths(self, app): if app.host_name != self.host_name: return [] From c101f6a2cbce65bdf97d8ccc7812f85895f38bdc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 7 Jan 2023 16:19:47 +0100 Subject: [PATCH 023/428] Cleanup OpenPype Qt widgets on Substance Painter shutdown --- .../deploy/plugins/openpype_plugin.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py b/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py index 01779156f1..e7e1849546 100644 --- a/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py +++ b/openpype/hosts/substancepainter/deploy/plugins/openpype_plugin.py @@ -1,13 +1,34 @@ + +def cleanup_openpype_qt_widgets(): + """ + Workaround for Substance failing to shut down correctly + when a Qt window was still open at the time of shutting down. + + This seems to work sometimes, but not all the time. + + """ + # TODO: Create a more reliable method to close down all OpenPype Qt widgets + from PySide2 import QtWidgets + import substance_painter.ui + + # Kill OpenPype Qt widgets + print("Killing OpenPype Qt widgets..") + for widget in QtWidgets.QApplication.topLevelWidgets(): + if widget.__module__.startswith("openpype."): + print(f"Deleting widget: {widget.__class__.__name__}") + substance_painter.ui.delete_ui_element(widget) + + def start_plugin(): from openpype.pipeline import install_host from openpype.hosts.substancepainter.api import SubstanceHost - install_host(SubstanceHost()) def close_plugin(): from openpype.pipeline import uninstall_host + cleanup_openpype_qt_widgets() uninstall_host() From ccb4371641b79275702bc5557fefdf3c8d39c0a6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 7 Jan 2023 17:42:43 +0100 Subject: [PATCH 024/428] Refactor metadata code to allow more structure for future Substance Painter plugins --- .../hosts/substancepainter/api/pipeline.py | 54 ++++++++++++++++- .../plugins/create/create_workfile.py | 27 ++++----- .../plugins/load/load_mesh.py | 58 +++++++++---------- 3 files changed, 91 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 4d49fa83d7..e7dbe5e5eb 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -36,6 +36,10 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + +OPENPYPE_METADATA_KEY = "OpenPype" +OPENPYPE_METADATA_CONTAINERS_KEY = "containers" # child key + self = sys.modules[__name__] self.menu = None self.callbacks = [] @@ -127,8 +131,8 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): if not substance_painter.project.is_open(): return - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) if containers: for key, container in containers.items(): container["objectName"] = key @@ -275,3 +279,49 @@ def imprint_container(container, ] for key, value in data: container[key] = value + + +def set_project_metadata(key, data): + """Set a key in project's OpenPype metadata.""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + metadata.set(key, data) + + +def get_project_metadata(key): + """Get a key from project's OpenPype metadata.""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + return metadata.get(key) + + +def set_container_metadata(object_name, container_data, update=False): + """Helper method to directly set the data for a specific container + + Args: + object_name (str): The unique object name identifier for the container + container_data (dict): The data for the container. + Note 'objectName' data is derived from `object_name` and key in + `container_data` will be ignored. + update (bool): Whether to only update the dict data. + + """ + # The objectName is derived from the key in the metadata so won't be stored + # in the metadata in the container's data. + container_data.pop("objectName", None) + + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) or {} + if update: + existing_data = containers.setdefault(object_name, {}) + existing_data.update(container_data) # mutable dict, in-place update + else: + containers[object_name] = container_data + metadata.set("containers", containers) + + +def remove_container_metadata(object_name): + """Helper method to remove the data for a specific container""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) + if containers: + containers.pop(object_name, None) + metadata.set("containers", containers) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index cec760040b..8b010ebe2c 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -5,20 +5,10 @@ from openpype.pipeline import CreatedInstance, AutoCreator from openpype.pipeline import legacy_io from openpype.client import get_asset_by_name -import substance_painter.project - - -def set_workfile_data(data, update=False): - if update: - data = get_workfile_data().update(data) - metadata = substance_painter.project.Metadata("OpenPype") - metadata.set("workfile", data) - - -def get_workfile_data(): - metadata = substance_painter.project.Metadata("OpenPype") - return metadata.get("workfile") or {} - +from openpype.hosts.substancepainter.api.pipeline import ( + set_project_metadata, + get_project_metadata +) class CreateWorkfile(AutoCreator): """Workfile auto-creator.""" @@ -71,17 +61,20 @@ class CreateWorkfile(AutoCreator): current_instance["task"] = task_name current_instance["subset"] = subset_name - set_workfile_data(current_instance.data_to_store()) + set_project_metadata("workfile", current_instance.data_to_store()) def collect_instances(self): - workfile = get_workfile_data() + workfile = get_project_metadata("workfile") if not workfile: return self.create_instance_in_context_from_existing(workfile) def update_instances(self, update_list): for instance, _changes in update_list: - set_workfile_data(instance.data_to_store(), update=True) + # Update project's workfile metadata + data = get_project_metadata("workfile") or {} + data.update(instance.data_to_store()) + set_project_metadata("workfile", data) # Helper methods (this might get moved into Creator class) def create_instance_in_context(self, subset_name, data): diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 519ed3ad4e..3e62b90988 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,27 +2,16 @@ from openpype.pipeline import ( load, get_representation_path, ) -from openpype.hosts.substancepainter.api.pipeline import imprint_container +from openpype.hosts.substancepainter.api.pipeline import ( + imprint_container, + set_container_metadata, + remove_container_metadata +) import substance_painter.project import qargparse -def set_container(key, container): - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") or {} - containers[key] = container - metadata.set("containers", containers) - - -def remove_container(key): - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") - if containers: - containers.pop(key, None) - metadata.set("containers", containers) - - class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -48,10 +37,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) ] - container_key = "ProjectMesh" - def load(self, context, name, namespace, data): + # Get user inputs + import_cameras = data.get("import_cameras", True) + preserve_strokes = data.get("preserve_strokes", True) + if not substance_painter.project.is_open(): # Allow to 'initialize' a new project # TODO: preferably these settings would come from the actual @@ -59,7 +50,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # visually similar to still allow artist decisions) settings = substance_painter.project.Settings( default_texture_resolution=4096, - import_cameras=data.get("import_cameras", True), + import_cameras=import_cameras, ) substance_painter.project.create( @@ -70,8 +61,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): else: # Reload the mesh settings = substance_painter.project.MeshReloadingSettings( - import_cameras=data.get("import_cameras", True), - preserve_strokes=data.get("preserve_strokes", True) + import_cameras=import_cameras, + preserve_strokes=preserve_strokes ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa @@ -87,13 +78,21 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Store container container = {} + project_mesh_object_name = "_ProjectMesh_" imprint_container(container, - name=self.container_key, - namespace=self.container_key, + name=project_mesh_object_name, + namespace=project_mesh_object_name, context=context, loader=self) - container["options"] = data - set_container(self.container_key, container) + + # We want store some options for updating to keep consistent behavior + # from the user's original choice. We don't store 'preserve_strokes' + # as we always preserve strokes on updates. + container["options"] = { + "import_cameras": import_cameras, + } + + set_container_metadata(project_mesh_object_name, container) def switch(self, container, representation): self.update(container, representation) @@ -107,7 +106,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): container_options = container.get("options", {}) settings = substance_painter.project.MeshReloadingSettings( import_cameras=container_options.get("import_cameras", True), - preserve_strokes=container_options.get("preserve_strokes", True) + preserve_strokes=True ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): @@ -119,8 +118,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): substance_painter.project.reload_mesh(path, settings, on_mesh_reload) # Update container representation - container["representation"] = str(representation["_id"]) - set_container(self.container_key, container) + object_name = container["objectName"] + update_data = {"representation": str(representation["_id"])} + set_container_metadata(object_name, update_data, update=True) def remove(self, container): @@ -128,4 +128,4 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # or close the project? # TODO: This is likely best 'hidden' away to the user because # this will leave the project's mesh unmanaged. - remove_container(self.container_key) + remove_container_metadata(container["objectName"]) From cf92213dd1fde6efb5ab117a1d4e4b7a96b188d5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 7 Jan 2023 17:42:55 +0100 Subject: [PATCH 025/428] Cosmetics --- .../hosts/substancepainter/plugins/create/create_workfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 8b010ebe2c..4b34f4cc8c 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -10,6 +10,7 @@ from openpype.hosts.substancepainter.api.pipeline import ( get_project_metadata ) + class CreateWorkfile(AutoCreator): """Workfile auto-creator.""" identifier = "io.openpype.creators.substancepainter.workfile" From ae496b9712bafc77a0d8350b92b0e84505eee512 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 9 Jan 2023 07:30:29 +0000 Subject: [PATCH 026/428] Use project settings by default. --- openpype/hosts/maya/api/lib.py | 4 +++- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 ++- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4b8b6b1949..9aa2325e25 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -113,7 +113,9 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94} RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"] -DISPLAY_LIGHTS = ["default", "all", "selected", "active", "none"] +DISPLAY_LIGHTS = [ + "project_settings", "default", "all", "selected", "active", "none" +] def get_main_window(): diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index a1e6b2d503..7542785152 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -100,7 +100,8 @@ class ExtractPlayblast(publish.Extractor): # Show lighting mode. display_lights = instance.data["displayLights"] - preset["viewport_options"]["displayLights"] = display_lights + if display_lights != "project_settings": + preset["viewport_options"]["displayLights"] = display_lights # Override transparency if requested. transparency = instance.data.get("transparency", 0) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 80e94303a6..de6bc3895e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -107,7 +107,8 @@ class ExtractThumbnail(publish.Extractor): # Show lighting mode. display_lights = instance.data["displayLights"] - preset["viewport_options"]["displayLights"] = display_lights + if display_lights != "project_settings": + preset["viewport_options"]["displayLights"] = display_lights # Override transparency if requested. transparency = instance.data.get("transparency", 0) From c34f8fed24a7c84ce22a615b5f438798b2f461c4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 10:29:44 +0100 Subject: [PATCH 027/428] Bypass silently if a project was not open when querying metadata --- openpype/hosts/substancepainter/api/pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index e7dbe5e5eb..70353039f5 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -289,6 +289,9 @@ def set_project_metadata(key, data): def get_project_metadata(key): """Get a key from project's OpenPype metadata.""" + if not substance_painter.project.is_open(): + return + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) return metadata.get(key) From 2c544246fd855de080387e1f86a053e5fd31e12f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 10:30:18 +0100 Subject: [PATCH 028/428] Do not auto create workfile instance if project isn't open. --- .../hosts/substancepainter/plugins/create/create_workfile.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 4b34f4cc8c..22e12b4079 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -10,6 +10,8 @@ from openpype.hosts.substancepainter.api.pipeline import ( get_project_metadata ) +import substance_painter.project + class CreateWorkfile(AutoCreator): """Workfile auto-creator.""" @@ -22,6 +24,9 @@ class CreateWorkfile(AutoCreator): def create(self): + if not substance_painter.project.is_open(): + return + variant = self.default_variant project_name = self.project_name asset_name = legacy_io.Session["AVALON_ASSET"] From ec2f10caf383a769fd90a3777ee47568054b6d41 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 10:30:32 +0100 Subject: [PATCH 029/428] Simplify logic --- .../hosts/substancepainter/plugins/create/create_workfile.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 22e12b4079..729cc8f718 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -71,9 +71,8 @@ class CreateWorkfile(AutoCreator): def collect_instances(self): workfile = get_project_metadata("workfile") - if not workfile: - return - self.create_instance_in_context_from_existing(workfile) + if workfile: + self.create_instance_in_context_from_existing(workfile) def update_instances(self, update_list): for instance, _changes in update_list: From c3fca896d48f82026aea0f81055a996c366ea920 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 11:16:23 +0100 Subject: [PATCH 030/428] Implement plug-ins to support workfile publishing --- .../plugins/publish/collect_current_file.py | 17 ++++++++++++ .../collect_workfile_representation.py | 26 +++++++++++++++++++ .../plugins/publish/increment_workfile.py | 23 ++++++++++++++++ .../plugins/publish/save_workfile.py | 23 ++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 openpype/hosts/substancepainter/plugins/publish/collect_current_file.py create mode 100644 openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py create mode 100644 openpype/hosts/substancepainter/plugins/publish/increment_workfile.py create mode 100644 openpype/hosts/substancepainter/plugins/publish/save_workfile.py diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py b/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py new file mode 100644 index 0000000000..dac493bbf1 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py @@ -0,0 +1,17 @@ +import pyblish.api + +from openpype.pipeline import registered_host + + +class CollectCurrentFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder - 0.49 + label = "Current Workfile" + hosts = ["substancepainter"] + + def process(self, context): + host = registered_host() + path = host.get_current_workfile() + context.data["currentFile"] = path + self.log.debug(f"Current workfile: {path}") \ No newline at end of file diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py b/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py new file mode 100644 index 0000000000..563c2d4c07 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py @@ -0,0 +1,26 @@ +import os +import pyblish.api + + +class CollectWorkfileRepresentation(pyblish.api.InstancePlugin): + """Create a publish representation for the current workfile instance.""" + + order = pyblish.api.CollectorOrder + label = "Workfile representation" + hosts = ['substancepainter'] + families = ["workfile"] + + def process(self, instance): + + context = instance.context + current_file = context.data["currentFile"] + + folder, file = os.path.split(current_file) + filename, ext = os.path.splitext(file) + + instance.data['representations'] = [{ + 'name': ext.lstrip("."), + 'ext': ext.lstrip("."), + 'files': file, + "stagingDir": folder, + }] diff --git a/openpype/hosts/substancepainter/plugins/publish/increment_workfile.py b/openpype/hosts/substancepainter/plugins/publish/increment_workfile.py new file mode 100644 index 0000000000..b45d66fbb1 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/increment_workfile.py @@ -0,0 +1,23 @@ +import pyblish.api + +from openpype.lib import version_up +from openpype.pipeline import registered_host + + +class IncrementWorkfileVersion(pyblish.api.ContextPlugin): + """Increment current workfile version.""" + + order = pyblish.api.IntegratorOrder + 1 + label = "Increment Workfile Version" + optional = True + hosts = ["substancepainter"] + + def process(self, context): + + assert all(result["success"] for result in context.data["results"]), ( + "Publishing not successful so version is not increased.") + + host = registered_host() + path = context.data["currentFile"] + self.log.info(f"Incrementing current workfile to: {path}") + host.save_workfile(version_up(path)) diff --git a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py new file mode 100644 index 0000000000..5e86785e0d --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py @@ -0,0 +1,23 @@ +import pyblish.api + +from openpype.pipeline import registered_host + + +class SaveCurrentWorkfile(pyblish.api.ContextPlugin): + """Save current workfile""" + + label = "Save current workfile" + order = pyblish.api.ExtractorOrder - 0.49 + hosts = ["substancepainter"] + + def process(self, context): + + host = registered_host() + assert context.data['currentFile'] == host.get_current_workfile() + + if host.has_unsaved_changes(): + self.log.info("Saving current file..") + host.save_workfile() + else: + self.log.debug("Skipping workfile save because there are no " + "unsaved changes.") From 564e8f4d40febfb08b65fc31e10b710d38cbddc7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 11:17:25 +0100 Subject: [PATCH 031/428] Cosmetics --- .../substancepainter/plugins/publish/collect_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py b/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py index dac493bbf1..9a37eb0d1c 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_current_file.py @@ -14,4 +14,4 @@ class CollectCurrentFile(pyblish.api.ContextPlugin): host = registered_host() path = host.get_current_workfile() context.data["currentFile"] = path - self.log.debug(f"Current workfile: {path}") \ No newline at end of file + self.log.debug(f"Current workfile: {path}") From f9d3c9f77227fef2ddcf43649e69d0fb88d4e2bd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 18:13:49 +0100 Subject: [PATCH 032/428] Early prototype for Texture publishing in Substance Painter (WIP - not functional; doesn't integrate yet) --- .../plugins/create/create_textures.py | 149 ++++++++++++++++++ .../plugins/publish/extract_textures.py | 71 +++++++++ 2 files changed, 220 insertions(+) create mode 100644 openpype/hosts/substancepainter/plugins/create/create_textures.py create mode 100644 openpype/hosts/substancepainter/plugins/publish/extract_textures.py diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py new file mode 100644 index 0000000000..af2e23b3bf --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating textures.""" +import os + +from openpype.pipeline import CreatedInstance, Creator + +from openpype.hosts.substancepainter.api.pipeline import ( + set_project_metadata, + get_project_metadata +) + +from openpype.lib import ( + EnumDef, + UILabelDef, + NumberDef +) + +import substance_painter.project +import substance_painter.resource + + +def get_export_presets(): + import substance_painter.resource + + preset_resources = {} + + # TODO: Find more optimal way to find all export templates + for shelf in substance_painter.resource.Shelves.all(): + shelf_path = os.path.normpath(shelf.path()) + + presets_path = os.path.join(shelf_path, "export-presets") + if not os.path.exists(presets_path): + continue + + for fname in os.listdir(presets_path): + if fname.endswith(".spexp"): + template_name = os.path.splitext(fname)[0] + + resource = substance_painter.resource.ResourceID( + context=shelf.name(), + name=template_name + ) + resource_url = resource.url() + + preset_resources[resource_url] = template_name + + # Sort by template name + export_templates = dict(sorted(preset_resources.items(), + key=lambda x: x[1])) + + return export_templates + + +class CreateTextures(Creator): + """Create a texture set.""" + identifier = "io.openpype.creators.substancepainter.textures" + label = "Textures" + family = "textures" + icon = "picture-o" + + default_variant = "Main" + + def create(self, subset_name, instance_data, pre_create_data): + + if not substance_painter.project.is_open(): + return + + instance = self.create_instance_in_context(subset_name, instance_data) + set_project_metadata("textures", instance.data_to_store()) + + def collect_instances(self): + workfile = get_project_metadata("textures") + if workfile: + self.create_instance_in_context_from_existing(workfile) + + def update_instances(self, update_list): + for instance, _changes in update_list: + # Update project's metadata + data = get_project_metadata("textures") or {} + data.update(instance.data_to_store()) + set_project_metadata("textures", data) + + def remove_instances(self, instances): + for instance in instances: + # TODO: Implement removal + # api.remove_instance(instance) + self._remove_instance_from_context(instance) + + # Helper methods (this might get moved into Creator class) + def create_instance_in_context(self, subset_name, data): + instance = CreatedInstance( + self.family, subset_name, data, self + ) + self.create_context.creator_adds_instance(instance) + return instance + + def create_instance_in_context_from_existing(self, data): + instance = CreatedInstance.from_existing(data, self) + self.create_context.creator_adds_instance(instance) + return instance + + def get_instance_attr_defs(self): + + return [ + EnumDef("exportPresetUrl", + items=get_export_presets(), + label="Output Template"), + EnumDef("exportFileFormat", + items={ + None: "Based on output template", + # TODO: implement extensions + }, + label="File type"), + EnumDef("exportSize", + items={ + None: "Based on each Texture Set's size", + # The key is size of the texture file in log2. + # (i.e. 10 means 2^10 = 1024) + 7: "128", + 8: "256", + 9: "512", + 10: "1024", + 11: "2048", + 12: "4096" + }, + label="Size"), + + EnumDef("exportPadding", + items={ + "passthrough": "No padding (passthrough)", + "infinite": "Dilation infinite", + "transparent": "Dilation + transparent", + "color": "Dilation + default background color", + "diffusion": "Dilation + diffusion" + }, + label="Padding"), + NumberDef("exportDilationDistance", + minimum=0, + maximum=256, + decimals=0, + default=16, + label="Dilation Distance"), + UILabelDef("Note: Dilation Distance is only used with " + "'Dilation + ' padding options"), + ] + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes + return self.get_instance_attr_defs() diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py new file mode 100644 index 0000000000..93e0c8cb31 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -0,0 +1,71 @@ +from openpype.pipeline import KnownPublishError, publish + +import substance_painter.export + + +class ExtractTextures(publish.Extractor): + """Extract Textures using an output template config""" + + label = "Extract Texture Sets" + hosts = ['substancepainter'] + families = ["textures"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + + # See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa + creator_attrs = instance.data["creator_attributes"] + config = { + "exportShaderParams": True, + "exportPath": staging_dir, + "defaultExportPreset": creator_attrs["exportPresetUrl"], + + # Custom overrides to the exporter + "exportParameters": [ + { + "parameters": { + "fileFormat": creator_attrs["exportFileFormat"], + "sizeLog2": creator_attrs["exportSize"], + "paddingAlgorithm": creator_attrs["exportPadding"], + "dilationDistance": creator_attrs["exportDilationDistance"] # noqa + } + } + ] + } + + # Create the list of Texture Sets to export. + config["exportList"] = [] + for texture_set in substance_painter.textureset.all_texture_sets(): + # stack = texture_set.get_stack() + config["exportList"].append({"rootPath": texture_set.name()}) + + # Consider None values optionals + for override in config["exportParameters"]: + parameters = override.get("parameters") + for key, value in dict(parameters).items(): + if value is None: + parameters.pop(key) + + result = substance_painter.export.export_project_textures(config) + + if result.status != substance_painter.export.ExportStatus.Success: + raise KnownPublishError( + "Failed to export texture set: {}".format(result.message) + ) + + files = [] + for stack, maps in result.textures.items(): + for texture_map in maps: + self.log.info(f"Exported texture: {texture_map}") + files.append(texture_map) + + # TODO: add the representations so they integrate the way we'd want + """ + instance.data['representations'] = [{ + 'name': ext.lstrip("."), + 'ext': ext.lstrip("."), + 'files': file, + "stagingDir": folder, + }] + """ From 0741c9850861779974e95cf764c3a7d2f0b097cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Jan 2023 18:15:06 +0100 Subject: [PATCH 033/428] Cosmetics --- .../hosts/substancepainter/plugins/publish/extract_textures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index 93e0c8cb31..d72d9920fd 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -55,7 +55,7 @@ class ExtractTextures(publish.Extractor): ) files = [] - for stack, maps in result.textures.items(): + for _stack, maps in result.textures.items(): for texture_map in maps: self.log.info(f"Exported texture: {texture_map}") files.append(texture_map) From 87f23c978d44d587e74adfb2d517da798dfecafe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 00:52:07 +0100 Subject: [PATCH 034/428] Add the built-in `export-preset-generator` template entries --- .../plugins/create/create_textures.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index af2e23b3bf..41de2ad946 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -48,7 +48,20 @@ def get_export_presets(): export_templates = dict(sorted(preset_resources.items(), key=lambda x: x[1])) - return export_templates + # Add default built-ins at the start + # TODO: find the built-ins automatically; scraped with https://gist.github.com/BigRoy/97150c7c6f0a0c916418207b9a2bc8f1 # noqa + result = { + "export-preset-generator://viewport2d": "2D View", # noqa + "export-preset-generator://doc-channel-normal-no-alpha": "Document channels + Normal + AO (No Alpha)", # noqa + "export-preset-generator://doc-channel-normal-with-alpha": "Document channels + Normal + AO (With Alpha)", # noqa + "export-preset-generator://sketchfab": "Sketchfab", # noqa + "export-preset-generator://adobe-standard-material": "Substance 3D Stager", # noqa + "export-preset-generator://usd": "USD PBR Metal Roughness", # noqa + "export-preset-generator://gltf": "glTF PBR Metal Roughness", # noqa + "export-preset-generator://gltf-displacement": "glTF PBR Metal Roughness + Displacement texture (experimental)" # noqa + } + result.update(export_templates) + return result class CreateTextures(Creator): From 9a4f5650199000658e93e189810cca7b1482e9ed Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 01:21:08 +0100 Subject: [PATCH 035/428] Shorten label --- .../hosts/substancepainter/plugins/create/create_textures.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index 41de2ad946..c1d907a974 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -153,8 +153,8 @@ class CreateTextures(Creator): decimals=0, default=16, label="Dilation Distance"), - UILabelDef("Note: Dilation Distance is only used with " - "'Dilation + ' padding options"), + UILabelDef("*only used with " + "'Dilation + ' padding"), ] def get_pre_create_attr_defs(self): From 139eafb5c7e951dcc08fa1c1a8e7e5bf2a4928d1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 01:21:31 +0100 Subject: [PATCH 036/428] Debug log used Substance Painter export preset --- .../substancepainter/plugins/publish/extract_textures.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index d72d9920fd..8ebad3193f 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -14,12 +14,15 @@ class ExtractTextures(publish.Extractor): staging_dir = self.staging_dir(instance) - # See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa creator_attrs = instance.data["creator_attributes"] + preset_url = creator_attrs["exportPresetUrl"] + self.log.debug(f"Exporting using preset: {preset_url}") + + # See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa config = { "exportShaderParams": True, "exportPath": staging_dir, - "defaultExportPreset": creator_attrs["exportPresetUrl"], + "defaultExportPreset": preset_url, # Custom overrides to the exporter "exportParameters": [ From 391ba1ada24ffb275443a47f008b6afce2feba52 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 11:21:55 +0100 Subject: [PATCH 037/428] Remove unusued imports --- openpype/hosts/substancepainter/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 70353039f5..aae1f39a3e 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -8,9 +8,7 @@ from functools import partial # Substance 3D Painter modules import substance_painter.ui import substance_painter.event -import substance_painter.export import substance_painter.project -import substance_painter.textureset from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost From c1abd00bba43cb98501efd649462c990414f720c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 16:33:17 +0100 Subject: [PATCH 038/428] Store menu and callbacks on the SubstanceHost instance --- .../hosts/substancepainter/api/pipeline.py | 120 +++++++++--------- 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index aae1f39a3e..db4bb47401 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -34,14 +34,9 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") - OPENPYPE_METADATA_KEY = "OpenPype" OPENPYPE_METADATA_CONTAINERS_KEY = "containers" # child key -self = sys.modules[__name__] -self.menu = None -self.callbacks = [] - class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): name = "substancepainter" @@ -49,6 +44,8 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def __init__(self): super(SubstanceHost, self).__init__() self._has_been_setup = False + self.menu = None + self.callbacks = [] def install(self): pyblish.api.register_host("substancepainter") @@ -59,20 +56,20 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): log.info("Installing callbacks ... ") # register_event_callback("init", on_init) - _register_callbacks() + self._register_callbacks() # register_event_callback("before.save", before_save) # register_event_callback("save", on_save) register_event_callback("open", on_open) # register_event_callback("new", on_new) log.info("Installing menu ... ") - _install_menu() + self._install_menu() self._has_been_setup = True def uninstall(self): - _uninstall_menu() - _deregister_callbacks() + self._uninstall_menu() + self._deregister_callbacks() def has_unsaved_changes(self): @@ -146,74 +143,71 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def get_context_data(self): pass + def _install_menu(self): + from PySide2 import QtWidgets + from openpype.tools.utils import host_tools -def _install_menu(): - from PySide2 import QtWidgets - from openpype.tools.utils import host_tools + parent = substance_painter.ui.get_main_window() - parent = substance_painter.ui.get_main_window() + menu = QtWidgets.QMenu("OpenPype") - menu = QtWidgets.QMenu("OpenPype") + action = menu.addAction("Load...") + action.triggered.connect( + lambda: host_tools.show_loader(parent=parent, use_context=True) + ) - action = menu.addAction("Load...") - action.triggered.connect( - lambda: host_tools.show_loader(parent=parent, use_context=True) - ) + action = menu.addAction("Publish...") + action.triggered.connect( + lambda: host_tools.show_publisher(parent=parent) + ) - action = menu.addAction("Publish...") - action.triggered.connect( - lambda: host_tools.show_publisher(parent=parent) - ) + action = menu.addAction("Manage...") + action.triggered.connect( + lambda: host_tools.show_scene_inventory(parent=parent) + ) - action = menu.addAction("Manage...") - action.triggered.connect( - lambda: host_tools.show_scene_inventory(parent=parent) - ) + action = menu.addAction("Library...") + action.triggered.connect( + lambda: host_tools.show_library_loader(parent=parent) + ) - action = menu.addAction("Library...") - action.triggered.connect( - lambda: host_tools.show_library_loader(parent=parent) - ) + menu.addSeparator() + action = menu.addAction("Work Files...") + action.triggered.connect( + lambda: host_tools.show_workfiles(parent=parent) + ) - menu.addSeparator() - action = menu.addAction("Work Files...") - action.triggered.connect( - lambda: host_tools.show_workfiles(parent=parent) - ) + substance_painter.ui.add_menu(menu) - substance_painter.ui.add_menu(menu) + def on_menu_destroyed(): + self.menu = None - def on_menu_destroyed(): - self.menu = None + menu.destroyed.connect(on_menu_destroyed) - menu.destroyed.connect(on_menu_destroyed) + self.menu = menu - self.menu = menu + def _uninstall_menu(self): + if self.menu: + self.menu.destroy() + self.menu = None + + def _register_callbacks(self): + # Prepare emit event callbacks + open_callback = partial(emit_event, "open") + + # Connect to the Substance Painter events + dispatcher = substance_painter.event.DISPATCHER + for event, callback in [ + (substance_painter.event.ProjectOpened, open_callback) + ]: + dispatcher.connect(event, callback) + # Keep a reference so we can deregister if needed + self.callbacks.append((event, callback)) -def _uninstall_menu(): - if self.menu: - self.menu.destroy() - self.menu = None - - -def _register_callbacks(): - # Prepare emit event callbacks - open_callback = partial(emit_event, "open") - - # Connect to the Substance Painter events - dispatcher = substance_painter.event.DISPATCHER - for event, callback in [ - (substance_painter.event.ProjectOpened, open_callback) - ]: - dispatcher.connect(event, callback) - # Keep a reference so we can deregister if needed - self.callbacks.append((event, callback)) - - -def _deregister_callbacks(): - for event, callback in self.callbacks: - substance_painter.event.DISPATCHER.disconnect(event, callback) + def _deregister_callbacks(self): + for event, callback in self.callbacks: + substance_painter.event.DISPATCHER.disconnect(event, callback) def on_open(): From df5300ed32a0a4cff5af52a930c535773238deda Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 16:33:33 +0100 Subject: [PATCH 039/428] Cosmetics --- openpype/hosts/substancepainter/api/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index db4bb47401..48adc107e2 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -204,7 +204,6 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): # Keep a reference so we can deregister if needed self.callbacks.append((event, callback)) - def _deregister_callbacks(self): for event, callback in self.callbacks: substance_painter.event.DISPATCHER.disconnect(event, callback) From 3b4f9feaadfaaee4ae763a78744a274cd467e744 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 16:34:20 +0100 Subject: [PATCH 040/428] Remove unused import --- openpype/hosts/substancepainter/api/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 48adc107e2..df705bb010 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Pipeline tools for OpenPype Gaffer integration.""" import os -import sys import logging from functools import partial From 5a7c5762847ed22f89a26d09f062a0948c34397b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 16:44:09 +0100 Subject: [PATCH 041/428] Remove debug print message --- openpype/hosts/substancepainter/api/pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index df705bb010..3a68a7fa86 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -210,7 +210,6 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def on_open(): log.info("Running callback on open..") - print("Run") if any_outdated_containers(): from openpype.widgets import popup From 24b6583c63ea14920bc6a56649c7db6ed1e3176c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Jan 2023 17:58:47 +0100 Subject: [PATCH 042/428] Set explicit defaults for creator --- .../hosts/substancepainter/plugins/create/create_textures.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index c1d907a974..6d4f816961 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -123,6 +123,7 @@ class CreateTextures(Creator): None: "Based on output template", # TODO: implement extensions }, + default=None, label="File type"), EnumDef("exportSize", items={ @@ -136,6 +137,7 @@ class CreateTextures(Creator): 11: "2048", 12: "4096" }, + default=None, label="Size"), EnumDef("exportPadding", @@ -146,6 +148,7 @@ class CreateTextures(Creator): "color": "Dilation + default background color", "diffusion": "Dilation + diffusion" }, + default="infinite", label="Padding"), NumberDef("exportDilationDistance", minimum=0, From 61710d614d5753b2287c9c5be5110147bd4612b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 12 Jan 2023 13:23:51 +0100 Subject: [PATCH 043/428] TODO was already resolved --- openpype/hosts/substancepainter/plugins/load/load_mesh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 3e62b90988..00f808199f 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -102,7 +102,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): path = get_representation_path(representation) # Reload the mesh - # TODO: Re-use settings from first load? container_options = container.get("options", {}) settings = substance_painter.project.MeshReloadingSettings( import_cameras=container_options.get("import_cameras", True), From 2177877713f538f70217a944014212fc183c7412 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 12 Jan 2023 14:47:38 +0100 Subject: [PATCH 044/428] Load OpenPype plug-in on first run of Substance Painter through OpenPype --- .../startup/openpype_load_on_first_run.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py diff --git a/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py b/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py new file mode 100644 index 0000000000..90b1ec6bbd --- /dev/null +++ b/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py @@ -0,0 +1,43 @@ +"""Ease the OpenPype on-boarding process by loading the plug-in on first run""" + +OPENPYPE_PLUGIN_NAME = "openpype_plugin" + + +def start_plugin(): + try: + # This isn't exposed in the official API so we keep it in a try-except + from painter_plugins_ui import ( + get_settings, + LAUNCH_AT_START_KEY, + ON_STATE, + PLUGINS_MENU, + plugin_manager + ) + + # The `painter_plugins_ui` plug-in itself is also a startup plug-in + # we need to take into account that it could run either earlier or + # later than this startup script, we check whether its menu initialized + is_before_plugins_menu = PLUGINS_MENU is None + + settings = get_settings(OPENPYPE_PLUGIN_NAME) + if settings.value(LAUNCH_AT_START_KEY, None) is not None: + print("Initializing OpenPype plug-in on first run...") + if is_before_plugins_menu: + print("- running before 'painter_plugins_ui'") + # Delay the launch to the painter_plugins_ui initialization + settings.setValue(LAUNCH_AT_START_KEY, ON_STATE) + else: + # Launch now + print("- running after 'painter_plugins_ui'") + plugin_manager(OPENPYPE_PLUGIN_NAME)(True) + + # Set the checked state in the menu to avoid confusion + action = next(action for action in PLUGINS_MENU._menu.actions() + if action.text() == OPENPYPE_PLUGIN_NAME) + if action is not None: + action.blockSignals(True) + action.setChecked(True) + action.blockSignals(False) + + except Exception as exc: + print(exc) From d1d15683983db8d3d9ca9e1a121b794b9b0acf3e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 12 Jan 2023 14:54:07 +0100 Subject: [PATCH 045/428] Fix logic --- .../deploy/startup/openpype_load_on_first_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py b/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py index 90b1ec6bbd..04b610b4df 100644 --- a/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py +++ b/openpype/hosts/substancepainter/deploy/startup/openpype_load_on_first_run.py @@ -20,7 +20,7 @@ def start_plugin(): is_before_plugins_menu = PLUGINS_MENU is None settings = get_settings(OPENPYPE_PLUGIN_NAME) - if settings.value(LAUNCH_AT_START_KEY, None) is not None: + if settings.value(LAUNCH_AT_START_KEY, None) is None: print("Initializing OpenPype plug-in on first run...") if is_before_plugins_menu: print("- running before 'painter_plugins_ui'") From d2baa5ec4d9f92c143172f95719bb7b319ae79a2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 12 Jan 2023 15:38:22 +0100 Subject: [PATCH 046/428] Allow to configure custom shelves for Substance Painter in project settings --- openpype/hosts/substancepainter/api/lib.py | 57 +++++++++++++++++++ .../hosts/substancepainter/api/pipeline.py | 28 +++++++++ .../project_settings/substancepainter.json | 3 + .../schemas/projects_schema/schema_main.json | 4 ++ .../schema_project_substancepainter.json | 18 ++++++ 5 files changed, 110 insertions(+) create mode 100644 openpype/hosts/substancepainter/api/lib.py create mode 100644 openpype/settings/defaults/project_settings/substancepainter.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py new file mode 100644 index 0000000000..d468f6cc45 --- /dev/null +++ b/openpype/hosts/substancepainter/api/lib.py @@ -0,0 +1,57 @@ +import os +import re +import substance_painter.resource + + +def load_shelf(path, name=None): + """Add shelf to substance painter (for current application session) + + This will dynamically add a Shelf for the current session. It's good + to note however that these will *not* persist on restart of the host. + + Note: + Consider the loaded shelf a static library of resources. + + The shelf will *not* be visible in application preferences in + Edit > Settings > Libraries. + + The shelf will *not* show in the Assets browser if it has no existing + assets + + The shelf will *not* be a selectable option for selecting it as a + destination to import resources too. + + """ + + # Ensure expanded path with forward slashes + path = os.path.expandvars(path) + path = os.path.abspath(path) + path = path.replace("\\", "/") + + # Path must exist + if not os.path.isdir(path): + raise ValueError(f"Path is not an existing folder: {path}") + + # This name must be unique and must only contain lowercase letters, + # numbers, underscores or hyphens. + if name is None: + name = os.path.basename(path) + + name = name.lower() + name = re.sub(r"[^a-z0-9_\-]", "_", name) # sanitize to underscores + + if substance_painter.resource.Shelves.exists(name): + shelf = next( + shelf for shelf in substance_painter.resource.Shelves.all() + if shelf.name() == name + ) + if os.path.normpath(shelf.path()) != os.path.normpath(path): + raise ValueError(f"Shelf with name '{name}' already exists " + f"for a different path: '{shelf.path()}") + + return + + print(f"Adding Shelf '{name}' to path: {path}") + substance_painter.resource.Shelves.add(name, path) + + return name diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 3a68a7fa86..f4d4c5b00c 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -10,6 +10,7 @@ import substance_painter.event import substance_painter.project from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost +from openpype.settings import get_current_project_settings import pyblish.api @@ -25,6 +26,8 @@ from openpype.lib import ( from openpype.pipeline.load import any_outdated_containers from openpype.hosts.substancepainter import SUBSTANCE_HOST_DIR +from . import lib + log = logging.getLogger("openpype.hosts.substance") PLUGINS_DIR = os.path.join(SUBSTANCE_HOST_DIR, "plugins") @@ -45,6 +48,7 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): self._has_been_setup = False self.menu = None self.callbacks = [] + self.shelves = [] def install(self): pyblish.api.register_host("substancepainter") @@ -64,9 +68,13 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): log.info("Installing menu ... ") self._install_menu() + project_settings = get_current_project_settings() + self._install_shelves(project_settings) + self._has_been_setup = True def uninstall(self): + self._uninstall_shelves() self._uninstall_menu() self._deregister_callbacks() @@ -206,6 +214,26 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def _deregister_callbacks(self): for event, callback in self.callbacks: substance_painter.event.DISPATCHER.disconnect(event, callback) + self.callbacks.clear() + + def _install_shelves(self, project_settings): + + shelves = project_settings["substancepainter"].get("shelves", {}) + for name, path in shelves.items(): + # TODO: Allow formatting with anatomy for the paths + shelf_name = None + try: + shelf_name = lib.load_shelf(path, name=name) + except ValueError as exc: + print(f"Failed to load shelf -> {exc}") + + if shelf_name: + self.shelves.append(shelf_name) + + def _uninstall_shelves(self): + for shelf_name in self.shelves: + substance_painter.resource.Shelves.remove(shelf_name) + self.shelves.clear() def on_open(): diff --git a/openpype/settings/defaults/project_settings/substancepainter.json b/openpype/settings/defaults/project_settings/substancepainter.json new file mode 100644 index 0000000000..a424a923da --- /dev/null +++ b/openpype/settings/defaults/project_settings/substancepainter.json @@ -0,0 +1,3 @@ +{ + "shelves": {} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 0b9fbf7470..b3c5c62a89 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -114,6 +114,10 @@ "type": "schema", "name": "schema_project_photoshop" }, + { + "type": "schema", + "name": "schema_project_substancepainter" + }, { "type": "schema", "name": "schema_project_harmony" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json new file mode 100644 index 0000000000..4a02a9d8ca --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json @@ -0,0 +1,18 @@ +{ + "type": "dict", + "collapsible": true, + "key": "substancepainter", + "label": "Substance Painter", + "is_file": true, + "children": [ + { + "type": "dict-modifiable", + "key": "shelves", + "label": "Shelves", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + } + ] +} From 42b207445ed49dab7d5ce23556d7cbd0e7316ba3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 12:32:38 +0100 Subject: [PATCH 047/428] Implement working WIP draft for Texture Publishing --- .../hosts/substancepainter/api/colorspace.py | 157 +++++++++++++ openpype/hosts/substancepainter/api/lib.py | 139 ++++++++++++ .../plugins/create/create_textures.py | 71 +----- .../publish/collect_textureset_images.py | 207 ++++++++++++++++++ .../plugins/publish/extract_textures.py | 87 +++----- 5 files changed, 548 insertions(+), 113 deletions(-) create mode 100644 openpype/hosts/substancepainter/api/colorspace.py create mode 100644 openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py diff --git a/openpype/hosts/substancepainter/api/colorspace.py b/openpype/hosts/substancepainter/api/colorspace.py new file mode 100644 index 0000000000..f7b9f7694a --- /dev/null +++ b/openpype/hosts/substancepainter/api/colorspace.py @@ -0,0 +1,157 @@ +"""Substance Painter OCIO management + +Adobe Substance 3D Painter supports OCIO color management using a per project +configuration. Output color spaces are defined at the project level + +More information see: + - https://substance3d.adobe.com/documentation/spdoc/color-management-223053233.html # noqa + - https://substance3d.adobe.com/documentation/spdoc/color-management-with-opencolorio-225969419.html # noqa + +""" +import substance_painter.export +import substance_painter.js +import json + +from .lib import ( + get_document_structure, + get_channel_format +) + + +def _iter_document_stack_channels(): + """Yield all stack paths and channels project""" + + for material in get_document_structure()["materials"]: + material_name = material["name"] + for stack in material["stacks"]: + stack_name = stack["name"] + for channel in stack["channels"]: + if stack_name: + stack_path = [material_name, stack_name] + else: + stack_path = material_name + yield stack_path, channel + + +def _get_first_color_and_data_stack_and_channel(): + """Return first found color channel and data channel.""" + color_channel = None + data_channel = None + for stack_path, channel in _iter_document_stack_channels(): + channel_format = get_channel_format(stack_path, channel) + if channel_format["color"]: + color_channel = (stack_path, channel) + else: + data_channel = (stack_path, channel) + + if color_channel and data_channel: + return color_channel, data_channel + + return color_channel, data_channel + + +def get_project_channel_data(): + """Return colorSpace settings for the current substance painter project. + + In Substance Painter only color channels have Color Management enabled + whereas data channels have no color management applied. This can't be + changed. The artist can only customize the export color space for color + channels per bit-depth for 8 bpc, 16 bpc and 32 bpc. + + As such this returns the color space for 'data' and for per bit-depth + for color channels. + + Example output: + { + "data": {'colorSpace': 'Utility - Raw'}, + "8": {"colorSpace": "ACES - AcesCG"}, + "16": {"colorSpace": "ACES - AcesCG"}, + "16f": {"colorSpace": "ACES - AcesCG"}, + "32f": {"colorSpace": "ACES - AcesCG"} + } + + """ + + keys = ["colorSpace"] + query = {key: f"${key}" for key in keys} + + config = { + "exportPath": "/", + "exportShaderParams": False, + "defaultExportPreset": "query_preset", + + "exportPresets": [{ + "name": "query_preset", + + # List of maps making up this export preset. + "maps": [{ + "fileName": json.dumps(query), + # List of source/destination defining which channels will + # make up the texture file. + "channels": [], + "parameters": { + "fileFormat": "exr", + "bitDepth": "32f", + "dithering": False, + "sizeLog2": 4, + "paddingAlgorithm": "passthrough", + "dilationDistance": 16 + } + }] + }], + } + + def _get_query_output(config): + # Return the basename of the single output path we defined + result = substance_painter.export.list_project_textures(config) + path = next(iter(result.values()))[0] + # strip extension and slash since we know relevant json data starts + # and ends with { and } characters + path = path.strip("/\\.exr") + return json.loads(path) + + # Query for each type of channel (color and data) + color_channel, data_channel = _get_first_color_and_data_stack_and_channel() + colorspaces = {} + for key, channel_data in { + "data": data_channel, + "color": color_channel + }.items(): + if channel_data is None: + # No channel of that datatype anywhere in the Stack. We're + # unable to identify the output color space of the project + colorspaces[key] = None + continue + + stack, channel = channel_data + + # Stack must be a string + if not isinstance(stack, str): + # Assume iterable + stack = "/".join(stack) + + # Define the temp output config + config["exportList"] = [{"rootPath": stack}] + config_map = config["exportPresets"][0]["maps"][0] + config_map["channels"] = [ + { + "destChannel": x, + "srcChannel": x, + "srcMapType": "documentMap", + "srcMapName": channel + } for x in "RGB" + ] + + if key == "color": + # Query for each bit depth + # Color space definition can have a different OCIO config set + # for 8-bit, 16-bit and 32-bit outputs so we need to check each + # bit depth + for depth in ["8", "16", "16f", "32f"]: + config_map["parameters"]["bitDepth"] = depth # noqa + colorspaces[key + depth] = _get_query_output(config) + else: + # Data channel (not color managed) + colorspaces[key] = _get_query_output(config) + + return colorspaces diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index d468f6cc45..b929f881a8 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -1,6 +1,145 @@ import os import re +import json + import substance_painter.resource +import substance_painter.js + + +def get_export_presets(): + """Return Export Preset resource URLs for all available Export Presets. + + Returns: + dict: {Resource url: GUI Label} + + """ + # TODO: Find more optimal way to find all export templates + + preset_resources = {} + for shelf in substance_painter.resource.Shelves.all(): + shelf_path = os.path.normpath(shelf.path()) + + presets_path = os.path.join(shelf_path, "export-presets") + if not os.path.exists(presets_path): + continue + + for filename in os.listdir(presets_path): + if filename.endswith(".spexp"): + template_name = os.path.splitext(filename)[0] + + resource = substance_painter.resource.ResourceID( + context=shelf.name(), + name=template_name + ) + resource_url = resource.url() + + preset_resources[resource_url] = template_name + + # Sort by template name + export_templates = dict(sorted(preset_resources.items(), + key=lambda x: x[1])) + + # Add default built-ins at the start + # TODO: find the built-ins automatically; scraped with https://gist.github.com/BigRoy/97150c7c6f0a0c916418207b9a2bc8f1 # noqa + result = { + "export-preset-generator://viewport2d": "2D View", # noqa + "export-preset-generator://doc-channel-normal-no-alpha": "Document channels + Normal + AO (No Alpha)", # noqa + "export-preset-generator://doc-channel-normal-with-alpha": "Document channels + Normal + AO (With Alpha)", # noqa + "export-preset-generator://sketchfab": "Sketchfab", # noqa + "export-preset-generator://adobe-standard-material": "Substance 3D Stager", # noqa + "export-preset-generator://usd": "USD PBR Metal Roughness", # noqa + "export-preset-generator://gltf": "glTF PBR Metal Roughness", # noqa + "export-preset-generator://gltf-displacement": "glTF PBR Metal Roughness + Displacement texture (experimental)" # noqa + } + result.update(export_templates) + return result + + +def _convert_stack_path_to_cmd_str(stack_path): + """Convert stack path `str` or `[str, str]` for javascript query + + Example usage: + >>> stack_path = _convert_stack_path_to_cmd_str(stack_path) + >>> cmd = f"alg.mapexport.channelIdentifiers({stack_path})" + >>> substance_painter.js.evaluate(cmd) + + Args: + stack_path (list or str): Path to the stack, could be + "Texture set name" or ["Texture set name", "Stack name"] + + Returns: + str: Stack path usable as argument in javascript query. + + """ + return json.dumps(stack_path) + + +def get_channel_identifiers(stack_path=None): + """Return the list of channel identifiers. + + If a context is passed (texture set/stack), + return only used channels with resolved user channels. + + Channel identifiers are: + basecolor, height, specular, opacity, emissive, displacement, + glossiness, roughness, anisotropylevel, anisotropyangle, transmissive, + scattering, reflection, ior, metallic, normal, ambientOcclusion, + diffuse, specularlevel, blendingmask, [custom user names]. + + Args: + stack_path (list or str, Optional): Path to the stack, could be + "Texture set name" or ["Texture set name", "Stack name"] + + Returns: + list: List of channel identifiers. + + """ + if stack_path is None: + stack_path = "" + else: + stack_path = _convert_stack_path_to_cmd_str(stack_path) + cmd = f"alg.mapexport.channelIdentifiers({stack_path})" + return substance_painter.js.evaluate(cmd) + + +def get_channel_format(stack_path, channel): + """Retrieve the channel format of a specific stack channel. + + See `alg.mapexport.channelFormat` (javascript API) for more details. + + The channel format data is: + "label" (str): The channel format label: could be one of + [sRGB8, L8, RGB8, L16, RGB16, L16F, RGB16F, L32F, RGB32F] + "color" (bool): True if the format is in color, False is grayscale + "floating" (bool): True if the format uses floating point + representation, false otherwise + "bitDepth" (int): Bit per color channel (could be 8, 16 or 32 bpc) + + Args: + stack_path (list or str): Path to the stack, could be + "Texture set name" or ["Texture set name", "Stack name"] + channel (str): Identifier of the channel to export + (see `get_channel_identifiers`) + + Returns: + dict: The channel format data. + + """ + stack_path = _convert_stack_path_to_cmd_str(stack_path) + cmd = f"alg.mapexport.channelFormat({stack_path}, '{channel}')" + return substance_painter.js.evaluate(cmd) + + +def get_document_structure(): + """Dump the document structure. + + See `alg.mapexport.documentStructure` (javascript API) for more details. + + Returns: + dict: Document structure or None when no project is open + + """ + return substance_painter.js.evaluate("alg.mapexport.documentStructure()") def load_shelf(path, name=None): diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index 6d4f816961..9d641215dc 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -1,74 +1,27 @@ # -*- coding: utf-8 -*- """Creator plugin for creating textures.""" -import os from openpype.pipeline import CreatedInstance, Creator - -from openpype.hosts.substancepainter.api.pipeline import ( - set_project_metadata, - get_project_metadata -) - from openpype.lib import ( EnumDef, UILabelDef, NumberDef ) +from openpype.hosts.substancepainter.api.pipeline import ( + set_project_metadata, + get_project_metadata +) +from openpype.hosts.substancepainter.api.lib import get_export_presets + import substance_painter.project -import substance_painter.resource - - -def get_export_presets(): - import substance_painter.resource - - preset_resources = {} - - # TODO: Find more optimal way to find all export templates - for shelf in substance_painter.resource.Shelves.all(): - shelf_path = os.path.normpath(shelf.path()) - - presets_path = os.path.join(shelf_path, "export-presets") - if not os.path.exists(presets_path): - continue - - for fname in os.listdir(presets_path): - if fname.endswith(".spexp"): - template_name = os.path.splitext(fname)[0] - - resource = substance_painter.resource.ResourceID( - context=shelf.name(), - name=template_name - ) - resource_url = resource.url() - - preset_resources[resource_url] = template_name - - # Sort by template name - export_templates = dict(sorted(preset_resources.items(), - key=lambda x: x[1])) - - # Add default built-ins at the start - # TODO: find the built-ins automatically; scraped with https://gist.github.com/BigRoy/97150c7c6f0a0c916418207b9a2bc8f1 # noqa - result = { - "export-preset-generator://viewport2d": "2D View", # noqa - "export-preset-generator://doc-channel-normal-no-alpha": "Document channels + Normal + AO (No Alpha)", # noqa - "export-preset-generator://doc-channel-normal-with-alpha": "Document channels + Normal + AO (With Alpha)", # noqa - "export-preset-generator://sketchfab": "Sketchfab", # noqa - "export-preset-generator://adobe-standard-material": "Substance 3D Stager", # noqa - "export-preset-generator://usd": "USD PBR Metal Roughness", # noqa - "export-preset-generator://gltf": "glTF PBR Metal Roughness", # noqa - "export-preset-generator://gltf-displacement": "glTF PBR Metal Roughness + Displacement texture (experimental)" # noqa - } - result.update(export_templates) - return result class CreateTextures(Creator): """Create a texture set.""" - identifier = "io.openpype.creators.substancepainter.textures" + identifier = "io.openpype.creators.substancepainter.textureset" label = "Textures" - family = "textures" + family = "textureSet" icon = "picture-o" default_variant = "Main" @@ -79,19 +32,19 @@ class CreateTextures(Creator): return instance = self.create_instance_in_context(subset_name, instance_data) - set_project_metadata("textures", instance.data_to_store()) + set_project_metadata("textureSet", instance.data_to_store()) def collect_instances(self): - workfile = get_project_metadata("textures") + workfile = get_project_metadata("textureSet") if workfile: self.create_instance_in_context_from_existing(workfile) def update_instances(self, update_list): for instance, _changes in update_list: # Update project's metadata - data = get_project_metadata("textures") or {} + data = get_project_metadata("textureSet") or {} data.update(instance.data_to_store()) - set_project_metadata("textures", data) + set_project_metadata("textureSet", data) def remove_instances(self, instances): for instance in instances: diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py new file mode 100644 index 0000000000..96f2daa525 --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -0,0 +1,207 @@ +import os +import copy +import clique +import pyblish.api + +from openpype.pipeline import publish + +import substance_painter.export +from openpype.hosts.substancepainter.api.colorspace import ( + get_project_channel_data, +) + + +def get_project_color_spaces(): + """Return unique color space names used for exports. + + This is based on the Color Management preferences of the project. + + See also: + func:`get_project_channel_data` + + """ + return set( + data["colorSpace"] for data in get_project_channel_data().values() + ) + + +def _get_channel_name(path, + texture_set_name, + project_colorspaces): + """Return expected 'name' for the output image. + + This will be used as a suffix to the separate image publish subsets. + + """ + # TODO: This will require improvement before being production ready. + # TODO(Question): Should we preserve the texture set name in the suffix + # TODO so that exports with multiple texture sets can work within a single + # TODO parent textureSet, like `texture{Variant}.{TextureSet}{Channel}` + name = os.path.basename(path) # filename + name = os.path.splitext(name)[0] # no extension + # Usually the channel identifier comes after $textureSet in + # the export preset. Unfortunately getting the export maps + # and channels explicitly is not trivial so for now we just + # assume this will generate a nice identifier for the end user + name = name.split(f"{texture_set_name}_", 1)[-1] + + # TODO: We need more explicit ways to detect the color space part + for colorspace in project_colorspaces: + if name.endswith(f"_{colorspace}"): + name = name[:-len(f"_{colorspace}")] + break + + return name + + +class CollectTextureSet(pyblish.api.InstancePlugin): + """Extract Textures using an output template config""" + # TODO: More explicitly detect UDIM tiles + # TODO: Get color spaces + # TODO: Detect what source data channels end up in each file + + label = "Collect Texture Set images" + hosts = ['substancepainter'] + families = ["textureSet"] + order = pyblish.api.CollectorOrder + + def process(self, instance): + + config = self.get_export_config(instance) + textures = substance_painter.export.list_project_textures(config) + + instance.data["exportConfig"] = config + + colorspaces = get_project_color_spaces() + + outputs = {} + for (texture_set_name, stack_name), maps in textures.items(): + + # Log our texture outputs + self.log.debug(f"Processing stack: {stack_name}") + for texture_map in maps: + self.log.debug(f"Expecting texture: {texture_map}") + + # For now assume the UDIM textures end with .. and + # when no trailing number is present before the extension then it's + # considered to *not* be a UDIM export. + collections, remainder = clique.assemble( + maps, + patterns=[clique.PATTERNS["frames"]], + minimum_items=True + ) + + outputs = {} + if collections: + # UDIM tile sequence + for collection in collections: + name = _get_channel_name(collection.head, + texture_set_name=texture_set_name, + project_colorspaces=colorspaces) + outputs[name] = collection + self.log.info(f"UDIM Collection: {collection}") + else: + # Single file per channel without UDIM number + for path in remainder: + name = _get_channel_name(path, + texture_set_name=texture_set_name, + project_colorspaces=colorspaces) + outputs[name] = path + self.log.info(f"Single file: {path}") + + # Let's break the instance into multiple instances to integrate + # a subset per generated texture or texture UDIM sequence + context = instance.context + for map_name, map_output in outputs.items(): + + is_udim = isinstance(map_output, clique.Collection) + if is_udim: + first_file = list(map_output)[0] + map_fnames = [os.path.basename(path) for path in map_output] + else: + first_file = map_output + map_fnames = map_output + + ext = os.path.splitext(first_file)[1] + assert ext.lstrip('.'), f"No extension: {ext}" + + # Define the suffix we want to give this particular texture + # set and set up a remapped subset naming for it. + suffix = f".{map_name}" + image_subset = instance.data["subset"][len("textureSet"):] + image_subset = "texture" + image_subset + suffix + + # TODO: Retrieve and store color space with the representation + + # Clone the instance + image_instance = context.create_instance(instance.name) + image_instance[:] = instance[:] + image_instance.data.update(copy.deepcopy(instance.data)) + image_instance.data["name"] = image_subset + image_instance.data["label"] = image_subset + image_instance.data["subset"] = image_subset + image_instance.data["family"] = "image" + image_instance.data["families"] = ["image", "textures"] + image_instance.data['representations'] = [{ + 'name': ext.lstrip("."), + 'ext': ext.lstrip("."), + 'files': map_fnames, + }] + + instance.append(image_instance) + + def get_export_config(self, instance): + """Return an export configuration dict for texture exports. + + This config can be supplied to: + - `substance_painter.export.export_project_textures` + - `substance_painter.export.list_project_textures` + + See documentation on substance_painter.export module about the + formatting of the configuration dictionary. + + Args: + instance (pyblish.api.Instance): Texture Set instance to be + published. + + Returns: + dict: Export config + + """ + + creator_attrs = instance.data["creator_attributes"] + preset_url = creator_attrs["exportPresetUrl"] + self.log.debug(f"Exporting using preset: {preset_url}") + + # See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa + config = { # noqa + "exportShaderParams": True, + "exportPath": publish.get_instance_staging_dir(instance), + "defaultExportPreset": preset_url, + + # Custom overrides to the exporter + "exportParameters": [ + { + "parameters": { + "fileFormat": creator_attrs["exportFileFormat"], + "sizeLog2": creator_attrs["exportSize"], + "paddingAlgorithm": creator_attrs["exportPadding"], + "dilationDistance": creator_attrs["exportDilationDistance"] # noqa + } + } + ] + } + + # Create the list of Texture Sets to export. + config["exportList"] = [] + for texture_set in substance_painter.textureset.all_texture_sets(): + config["exportList"].append({"rootPath": texture_set.name()}) + + # Consider None values from the creator attributes optionals + for override in config["exportParameters"]: + parameters = override.get("parameters") + for key, value in dict(parameters).items(): + if value is None: + parameters.pop(key) + + return config diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index 8ebad3193f..e99b93cac9 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -1,55 +1,28 @@ from openpype.pipeline import KnownPublishError, publish - import substance_painter.export class ExtractTextures(publish.Extractor): - """Extract Textures using an output template config""" + """Extract Textures using an output template config. - label = "Extract Texture Sets" + Note: + This Extractor assumes that `collect_textureset_images` has prepared + the relevant export config and has also collected the individual image + instances for publishing including its representation. That is why this + particular Extractor doesn't specify representations to integrate. + + """ + # TODO: More explicitly detect UDIM tiles + # TODO: Get color spaces + # TODO: Detect what source data channels end up in each file + + label = "Extract Texture Set" hosts = ['substancepainter'] - families = ["textures"] + families = ["textureSet"] def process(self, instance): - staging_dir = self.staging_dir(instance) - - creator_attrs = instance.data["creator_attributes"] - preset_url = creator_attrs["exportPresetUrl"] - self.log.debug(f"Exporting using preset: {preset_url}") - - # See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa - config = { - "exportShaderParams": True, - "exportPath": staging_dir, - "defaultExportPreset": preset_url, - - # Custom overrides to the exporter - "exportParameters": [ - { - "parameters": { - "fileFormat": creator_attrs["exportFileFormat"], - "sizeLog2": creator_attrs["exportSize"], - "paddingAlgorithm": creator_attrs["exportPadding"], - "dilationDistance": creator_attrs["exportDilationDistance"] # noqa - } - } - ] - } - - # Create the list of Texture Sets to export. - config["exportList"] = [] - for texture_set in substance_painter.textureset.all_texture_sets(): - # stack = texture_set.get_stack() - config["exportList"].append({"rootPath": texture_set.name()}) - - # Consider None values optionals - for override in config["exportParameters"]: - parameters = override.get("parameters") - for key, value in dict(parameters).items(): - if value is None: - parameters.pop(key) - + config = instance.data["exportConfig"] result = substance_painter.export.export_project_textures(config) if result.status != substance_painter.export.ExportStatus.Success: @@ -57,18 +30,24 @@ class ExtractTextures(publish.Extractor): "Failed to export texture set: {}".format(result.message) ) - files = [] - for _stack, maps in result.textures.items(): + for (texture_set_name, stack_name), maps in result.textures.items(): + # Log our texture outputs + self.log.info(f"Processing stack: {stack_name}") for texture_map in maps: self.log.info(f"Exported texture: {texture_map}") - files.append(texture_map) - # TODO: add the representations so they integrate the way we'd want - """ - instance.data['representations'] = [{ - 'name': ext.lstrip("."), - 'ext': ext.lstrip("."), - 'files': file, - "stagingDir": folder, - }] - """ + # TODO: Confirm outputs match what we collected + # TODO: Confirm the files indeed exist + # TODO: make sure representations are registered + + # Add a fake representation which won't be integrated so the + # Integrator leaves us alone - otherwise it would error + # TODO: Add `instance.data["integrate"] = False` support in Integrator? + instance.data["representations"] = [ + { + "name": "_fake", + "ext": "_fake", + "delete": True, + "files": [] + } + ] From bd73709463440b520deafb6e9ac82995b6e6e430 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 12:33:43 +0100 Subject: [PATCH 048/428] Fix indentation --- openpype/hosts/substancepainter/api/colorspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/api/colorspace.py b/openpype/hosts/substancepainter/api/colorspace.py index f7b9f7694a..a9df3eb066 100644 --- a/openpype/hosts/substancepainter/api/colorspace.py +++ b/openpype/hosts/substancepainter/api/colorspace.py @@ -135,10 +135,10 @@ def get_project_channel_data(): config_map = config["exportPresets"][0]["maps"][0] config_map["channels"] = [ { - "destChannel": x, - "srcChannel": x, - "srcMapType": "documentMap", - "srcMapName": channel + "destChannel": x, + "srcChannel": x, + "srcMapType": "documentMap", + "srcMapName": channel } for x in "RGB" ] From fbcb88b457faa1e468b71104a158da03558a4c23 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 12:35:00 +0100 Subject: [PATCH 049/428] Include texture set name in the logging --- .../hosts/substancepainter/plugins/publish/extract_textures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index e99b93cac9..a32a81db48 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -32,7 +32,7 @@ class ExtractTextures(publish.Extractor): for (texture_set_name, stack_name), maps in result.textures.items(): # Log our texture outputs - self.log.info(f"Processing stack: {stack_name}") + self.log.info(f"Processing stack: {texture_set_name} {stack_name}") for texture_map in maps: self.log.info(f"Exported texture: {texture_map}") From 78c4875dcb26488cae3e8ccb27b6bc7f6f8c4350 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 18:03:34 +0100 Subject: [PATCH 050/428] Add support for thumbnail generation of extracted textures from Substance Painter --- .../plugins/publish/collect_textureset_images.py | 6 ++++++ .../substancepainter/plugins/publish/extract_textures.py | 3 +++ openpype/plugins/publish/extract_thumbnail.py | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 96f2daa525..5a179f7526 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -148,6 +148,12 @@ class CollectTextureSet(pyblish.api.InstancePlugin): 'files': map_fnames, }] + # Set up the representation for thumbnail generation + # TODO: Simplify this once thumbnail extraction is refactored + staging_dir = os.path.dirname(first_file) + image_instance.data["representations"][0]["tags"] = ["review"] + image_instance.data["representations"][0]["stagingDir"] = staging_dir # noqa + instance.append(image_instance) def get_export_config(self, instance): diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index a32a81db48..22acf07284 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -20,6 +20,9 @@ class ExtractTextures(publish.Extractor): hosts = ['substancepainter'] families = ["textureSet"] + # Run before thumbnail extractors + order = publish.Extractor.order - 0.1 + def process(self, instance): config = instance.data["exportConfig"] diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 14b43beae8..dcdb8341ba 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -19,9 +19,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", "prerender", - "source", "clip", "take" + "source", "clip", "take", "image" ] - hosts = ["shell", "fusion", "resolve", "traypublisher"] + hosts = ["shell", "fusion", "resolve", "traypublisher", "substancepainter"] enabled = False # presetable attribute From 5c0a7e30ed59b63bd177ff64c07c5f55417556f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 18:14:18 +0100 Subject: [PATCH 051/428] Group textures together to look like a package/textureSet --- .../plugins/publish/collect_textureset_images.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 5a179f7526..3832f724d4 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -148,6 +148,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): 'files': map_fnames, }] + # Group the textures together in the loader + image_instance.data["subsetGroup"] = instance.data["subset"] + # Set up the representation for thumbnail generation # TODO: Simplify this once thumbnail extraction is refactored staging_dir = os.path.dirname(first_file) From cba71b9e0d22da265429fe2fcbcba1d77dd63a3e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Jan 2023 18:29:59 +0100 Subject: [PATCH 052/428] Fix full path in representation for single images (non-UDIM) --- .../plugins/publish/collect_textureset_images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 3832f724d4..851a22c1ee 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -120,7 +120,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): map_fnames = [os.path.basename(path) for path in map_output] else: first_file = map_output - map_fnames = map_output + map_fnames = os.path.basename(map_output) ext = os.path.splitext(first_file)[1] assert ext.lstrip('.'), f"No extension: {ext}" From b17ca1efeac834d9038555f522c8602bc4701035 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 15:38:22 +0100 Subject: [PATCH 053/428] More explicit parsing of extracted textures, prepare for color space data --- openpype/hosts/substancepainter/api/lib.py | 328 +++++++++++++++++- .../publish/collect_textureset_images.py | 177 +++------- .../plugins/publish/extract_textures.py | 3 - 3 files changed, 379 insertions(+), 129 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index b929f881a8..2406680a68 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -1,7 +1,9 @@ import os import re import json +from collections import defaultdict +import substance_painter.project import substance_painter.resource import substance_painter.js @@ -115,7 +117,7 @@ def get_channel_format(stack_path, channel): representation, false otherwise "bitDepth" (int): Bit per color channel (could be 8, 16 or 32 bpc) - Args: + Arguments: stack_path (list or str): Path to the stack, could be "Texture set name" or ["Texture set name", "Stack name"] channel (str): Identifier of the channel to export @@ -142,6 +144,330 @@ def get_document_structure(): return substance_painter.js.evaluate("alg.mapexport.documentStructure()") +def get_export_templates(config, format="png", strip_folder=True): + """Return export config outputs. + + This use the Javascript API `alg.mapexport.getPathsExportDocumentMaps` + which returns a different output than using the Python equivalent + `substance_painter.export.list_project_textures(config)`. + + The nice thing about the Javascript API version is that it returns the + output textures grouped by filename template. + + A downside is that it doesn't return all the UDIM tiles but per template + always returns a single file. + + Note: + The file format needs to be explicitly passed to the Javascript API + but upon exporting through the Python API the file format can be based + on the output preset. So it's likely the file extension will mismatch + + Warning: + Even though the function appears to solely get the expected outputs + the Javascript API will actually create the config's texture output + folder if it does not exist yet. As such, a valid path must be set. + + Example output: + { + "DefaultMaterial": { + "$textureSet_BaseColor(_$colorSpace)(.$udim)": "DefaultMaterial_BaseColor_ACES - ACEScg.1002.png", # noqa + "$textureSet_Emissive(_$colorSpace)(.$udim)": "DefaultMaterial_Emissive_ACES - ACEScg.1002.png", # noqa + "$textureSet_Height(_$colorSpace)(.$udim)": "DefaultMaterial_Height_Utility - Raw.1002.png", # noqa + "$textureSet_Metallic(_$colorSpace)(.$udim)": "DefaultMaterial_Metallic_Utility - Raw.1002.png", # noqa + "$textureSet_Normal(_$colorSpace)(.$udim)": "DefaultMaterial_Normal_Utility - Raw.1002.png", # noqa + "$textureSet_Roughness(_$colorSpace)(.$udim)": "DefaultMaterial_Roughness_Utility - Raw.1002.png" # noqa + } + } + + Arguments: + config (dict) Export config + format (str, Optional): Output format to write to, defaults to 'png' + strip_folder (bool, Optional): Whether to strip the output folder + from the output filenames. + + Returns: + dict: The expected output maps. + + """ + folder = config["exportPath"] + preset = config["defaultExportPreset"] + cmd = f'alg.mapexport.getPathsExportDocumentMaps("{preset}", "{folder}", "{format}")' # noqa + result = substance_painter.js.evaluate(cmd) + + if strip_folder: + for stack, maps in result.items(): + for map_template, map_filepath in maps.items(): + map_filename = map_filepath[len(folder):].lstrip("/") + maps[map_template] = map_filename + + return result + + +def _templates_to_regex(templates, + texture_set, + colorspaces, + project, + mesh): + """Return regex based on a Substance Painter expot filename template. + + This converts Substance Painter export filename templates like + `$mesh_$textureSet_BaseColor(_$colorSpace)(.$udim)` into a regex + which can be used to query an output filename to help retrieve: + + - Which template filename the file belongs to. + - Which color space the file is written with. + - Which udim tile it is exactly. + + This is used by `get_parsed_export_maps` which tries to as explicitly + as possible match the filename pattern against the known possible outputs. + That's why Texture Set name, Color spaces, Project path and mesh path must + be provided. By doing so we get the best shot at correctly matching the + right template because otherwise $texture_set could basically be any string + and thus match even that of a color space or mesh. + + Arguments: + templates (list): List of templates to convert to regex. + texture_set (str): The texture set to match against. + colorspaces (list): The colorspaces defined in the current project. + project (str): Filepath of current substance project. + mesh (str): Path to mesh file used in current project. + + Returns: + dict: Template: Template regex pattern + + """ + def _filename_no_ext(path): + return os.path.splitext(os.path.basename(path))[0] + + if colorspaces and any(colorspaces): + colorspace_match = ( + "(" + "|".join(re.escape(c) for c in colorspaces) + ")" + ) + else: + # No colorspace support enabled + colorspace_match = "" + + # Key to regex valid search values + key_matches = { + "$project": re.escape(_filename_no_ext(project)), + "$mesh": re.escape(_filename_no_ext(mesh)), + "$textureSet": re.escape(texture_set), + "$colorSpace": colorspace_match, + "$udim": "([0-9]{4})" + } + + # Turn the templates into regexes + regexes = {} + for template in templates: + + # We need to tweak a temp + search_regex = re.escape(template) + + # Let's assume that any ( and ) character in the file template was + # intended as an optional template key and do a simple `str.replace` + # Note: we are matching against re.escape(template) so will need to + # search for the escaped brackets. + search_regex = search_regex.replace(re.escape("("), "(") + search_regex = search_regex.replace(re.escape(")"), ")?") + + # Substitute each key into a named group + for key, key_expected_regex in key_matches.items(): + + # We want to use the template as a regex basis in the end so will + # escape the whole thing first. Note that thus we'll need to + # search for the escaped versions of the keys too. + escaped_key = re.escape(key) + key_label = key[1:] # key without $ prefix + + key_expected_grp_regex = f"(?P<{key_label}>{key_expected_regex})" + search_regex = search_regex.replace(escaped_key, + key_expected_grp_regex) + + # The filename templates don't include the extension so we add it + # to be able to match the out filename beginning to end + ext_regex = "(?P\.[A-Za-z][A-Za-z0-9-]*)" + search_regex = rf"^{search_regex}{ext_regex}$" + + regexes[template] = search_regex + + return regexes + + +def strip_template(template, strip="._ "): + """Return static characters in a substance painter filename template. + + >>> strip_template("$textureSet_HELLO(.$udim)") + # HELLO + >>> strip_template("$mesh_$textureSet_HELLO_WORLD_$colorSpace(.$udim)") + # HELLO_WORLD + >>> strip_template("$textureSet_HELLO(.$udim)", strip=None) + # _HELLO + >>> strip_template("$mesh_$textureSet_$colorSpace(.$udim)", strip=None) + # _HELLO_ + >>> strip_template("$textureSet_HELLO(.$udim)") + # _HELLO + + Arguments: + template (str): Filename template to strip. + strip (str, optional): Characters to strip from beginning and end + of the static string in template. Defaults to: `._ `. + + Returns: + str: The static string in filename template. + + """ + # Return only characters that were part of the template that were static. + # Remove all keys + keys = ["$project", "$mesh", "$textureSet", "$udim", "$colorSpace"] + stripped_template = template + for key in keys: + stripped_template = stripped_template.replace(key, "") + + # Everything inside an optional bracket space is excluded since it's not + # static. We keep a counter to track whether we are currently iterating + # over parts of the template that are inside an 'optional' group or not. + counter = 0 + result = "" + for char in stripped_template: + if char == "(": + counter += 1 + elif char == ")": + counter -= 1 + if counter < 0: + counter = 0 + else: + if counter == 0: + result += char + + if strip: + # Strip of any trailing start/end characters. Technically these are + # static but usually start and end separators like space or underscore + # aren't wanted. + result = result.strip(strip) + + return result + + +def get_parsed_export_maps(config): + """ + + This tries to parse the texture outputs using a Python API export config. + + Parses template keys: $project, $mesh, $textureSet, $colorSpace, $udim + + Example: + {("DefaultMaterial", ""): { + "$mesh_$textureSet_BaseColor(_$colorSpace)(.$udim)": [ + { + // OUTPUT DATA FOR FILE #1 OF THE TEMPLATE + }, + { + // OUTPUT DATA FOR FILE #2 OF THE TEMPLATE + }, + ] + }, + }} + + File output data (all outputs are `str`). + 1) Parsed tokens: These are parsed tokens from the template, they will + only exist if found in the filename template and output filename. + + project: Workfile filename without extension + mesh: Filename of the loaded mesh without extension + textureSet: The texture set, e.g. "DefaultMaterial", + colorSpace: The color space, e.g. "ACES - ACEScg", + udim: The udim tile, e.g. "1001" + + 2) Template and file outputs + + filepath: Full path to the resulting texture map, e.g. + "/path/to/mesh_DefaultMaterial_BaseColor_ACES - ACEScg.1002.png", + output: "mesh_DefaultMaterial_BaseColor_ACES - ACEScg.1002.png" + Note: if template had slashes (folders) then `output` will too. + So `output` might include a folder. + + channel: The stripped static characters of the filename template which + usually look like an identifier for that map, e.g. "BaseColor". + See `_stripped_template` + + Returns: + dict: [texture_set, stack]: {template: [file1_data, file2_data]} + + """ + import substance_painter.export + from .colorspace import get_project_channel_data + + outputs = substance_painter.export.list_project_textures(config) + templates = get_export_templates(config) + + # Get all color spaces set for the current project + project_colorspaces = set( + data["colorSpace"] for data in get_project_channel_data().values() + ) + + # Get current project mesh path and project path to explicitly match + # the $mesh and $project tokens + project_mesh_path = substance_painter.project.last_imported_mesh_path() + project_path = substance_painter.project.file_path() + + # Get the current export path to strip this of the beginning of filepath + # results, since filename templates don't have these we'll match without + # that part of the filename. + export_path = config["exportPath"] + export_path = export_path.replace("\\", "/") + if not export_path.endswith("/"): + export_path += "/" + + # Parse the outputs + result = {} + for key, filepaths in outputs.items(): + texture_set, stack = key + + if stack: + stack_path = f"{texture_set}/{stack}" + else: + stack_path = texture_set + + stack_templates = list(templates[stack_path].keys()) + + template_regex = _templates_to_regex(stack_templates, + texture_set=texture_set, + colorspaces=project_colorspaces, + mesh=project_mesh_path, + project=project_path) + + # Let's precompile the regexes + for template, regex in template_regex.items(): + template_regex[template] = re.compile(regex) + + stack_results = defaultdict(list) + for filepath in sorted(filepaths): + # We strip explicitly using the full parent export path instead of + # using `os.path.basename` because export template is allowed to + # have subfolders in its template which we want to match against + assert filepath.startswith(export_path) + filename = filepath[len(export_path):] + + for template, regex in template_regex.items(): + match = regex.match(filename) + if match: + parsed = match.groupdict(default={}) + + # Include some special outputs for convenience + parsed["filepath"] = filepath + parsed["output"] = filename + + stack_results[template].append(parsed) + break + else: + raise ValueError(f"Unable to match {filename} against any " + f"template in: {list(template_regex.keys())}") + + result[key] = dict(stack_results) + + return result + + def load_shelf(path, name=None): """Add shelf to substance painter (for current application session) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 851a22c1ee..6928bdb36c 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -1,63 +1,19 @@ import os import copy -import clique import pyblish.api from openpype.pipeline import publish -import substance_painter.export -from openpype.hosts.substancepainter.api.colorspace import ( - get_project_channel_data, +import substance_painter.textureset +from openpype.hosts.substancepainter.api.lib import ( + get_parsed_export_maps, + strip_template ) -def get_project_color_spaces(): - """Return unique color space names used for exports. - - This is based on the Color Management preferences of the project. - - See also: - func:`get_project_channel_data` - - """ - return set( - data["colorSpace"] for data in get_project_channel_data().values() - ) - - -def _get_channel_name(path, - texture_set_name, - project_colorspaces): - """Return expected 'name' for the output image. - - This will be used as a suffix to the separate image publish subsets. - - """ - # TODO: This will require improvement before being production ready. - # TODO(Question): Should we preserve the texture set name in the suffix - # TODO so that exports with multiple texture sets can work within a single - # TODO parent textureSet, like `texture{Variant}.{TextureSet}{Channel}` - name = os.path.basename(path) # filename - name = os.path.splitext(name)[0] # no extension - # Usually the channel identifier comes after $textureSet in - # the export preset. Unfortunately getting the export maps - # and channels explicitly is not trivial so for now we just - # assume this will generate a nice identifier for the end user - name = name.split(f"{texture_set_name}_", 1)[-1] - - # TODO: We need more explicit ways to detect the color space part - for colorspace in project_colorspaces: - if name.endswith(f"_{colorspace}"): - name = name[:-len(f"_{colorspace}")] - break - - return name - - class CollectTextureSet(pyblish.api.InstancePlugin): """Extract Textures using an output template config""" - # TODO: More explicitly detect UDIM tiles - # TODO: Get color spaces + # TODO: Production-test usage of color spaces # TODO: Detect what source data channels end up in each file label = "Collect Texture Set images" @@ -68,96 +24,67 @@ class CollectTextureSet(pyblish.api.InstancePlugin): def process(self, instance): config = self.get_export_config(instance) - textures = substance_painter.export.list_project_textures(config) instance.data["exportConfig"] = config - - colorspaces = get_project_color_spaces() - - outputs = {} - for (texture_set_name, stack_name), maps in textures.items(): - - # Log our texture outputs - self.log.debug(f"Processing stack: {stack_name}") - for texture_map in maps: - self.log.debug(f"Expecting texture: {texture_map}") - - # For now assume the UDIM textures end with .. and - # when no trailing number is present before the extension then it's - # considered to *not* be a UDIM export. - collections, remainder = clique.assemble( - maps, - patterns=[clique.PATTERNS["frames"]], - minimum_items=True - ) - - outputs = {} - if collections: - # UDIM tile sequence - for collection in collections: - name = _get_channel_name(collection.head, - texture_set_name=texture_set_name, - project_colorspaces=colorspaces) - outputs[name] = collection - self.log.info(f"UDIM Collection: {collection}") - else: - # Single file per channel without UDIM number - for path in remainder: - name = _get_channel_name(path, - texture_set_name=texture_set_name, - project_colorspaces=colorspaces) - outputs[name] = path - self.log.info(f"Single file: {path}") + maps = get_parsed_export_maps(config) # Let's break the instance into multiple instances to integrate # a subset per generated texture or texture UDIM sequence + for (texture_set_name, stack_name), template_maps in maps.items(): + self.log.info(f"Processing {texture_set_name}/{stack_name}") + for template, outputs in template_maps.items(): + self.log.info(f"Processing {template}") + self.create_image_instance(instance, template, outputs) + + def create_image_instance(self, instance, template, outputs): + context = instance.context - for map_name, map_output in outputs.items(): + first_filepath = outputs[0]["filepath"] + fnames = [os.path.basename(output["filepath"]) for output in outputs] + ext = os.path.splitext(first_filepath)[1] + assert ext.lstrip('.'), f"No extension: {ext}" - is_udim = isinstance(map_output, clique.Collection) - if is_udim: - first_file = list(map_output)[0] - map_fnames = [os.path.basename(path) for path in map_output] - else: - first_file = map_output - map_fnames = os.path.basename(map_output) + map_identifier = strip_template(template) - ext = os.path.splitext(first_file)[1] - assert ext.lstrip('.'), f"No extension: {ext}" + # Define the suffix we want to give this particular texture + # set and set up a remapped subset naming for it. + suffix = f".{map_identifier}" + image_subset = instance.data["subset"][len("textureSet"):] + image_subset = "texture" + image_subset + suffix + # Prepare representation + representation = { + 'name': ext.lstrip("."), + 'ext': ext.lstrip("."), + 'files': fnames, + } - # Define the suffix we want to give this particular texture - # set and set up a remapped subset naming for it. - suffix = f".{map_name}" - image_subset = instance.data["subset"][len("textureSet"):] - image_subset = "texture" + image_subset + suffix + # Mark as UDIM explicitly if it has UDIM tiles. + if bool(outputs[0].get("udim")): + representation["udim"] = True - # TODO: Retrieve and store color space with the representation + # TODO: Store color space with the representation - # Clone the instance - image_instance = context.create_instance(instance.name) - image_instance[:] = instance[:] - image_instance.data.update(copy.deepcopy(instance.data)) - image_instance.data["name"] = image_subset - image_instance.data["label"] = image_subset - image_instance.data["subset"] = image_subset - image_instance.data["family"] = "image" - image_instance.data["families"] = ["image", "textures"] - image_instance.data['representations'] = [{ - 'name': ext.lstrip("."), - 'ext': ext.lstrip("."), - 'files': map_fnames, - }] + # Clone the instance + image_instance = context.create_instance(instance.name) + image_instance[:] = instance[:] + image_instance.data.update(copy.deepcopy(instance.data)) + image_instance.data["name"] = image_subset + image_instance.data["label"] = image_subset + image_instance.data["subset"] = image_subset + image_instance.data["family"] = "image" + image_instance.data["families"] = ["image", "textures"] + image_instance.data['representations'] = [representation] - # Group the textures together in the loader - image_instance.data["subsetGroup"] = instance.data["subset"] + # Group the textures together in the loader + image_instance.data["subsetGroup"] = instance.data["subset"] - # Set up the representation for thumbnail generation - # TODO: Simplify this once thumbnail extraction is refactored - staging_dir = os.path.dirname(first_file) - image_instance.data["representations"][0]["tags"] = ["review"] - image_instance.data["representations"][0]["stagingDir"] = staging_dir # noqa + # Set up the representation for thumbnail generation + # TODO: Simplify this once thumbnail extraction is refactored + staging_dir = os.path.dirname(first_filepath) + image_instance.data["representations"][0]["tags"] = ["review"] + image_instance.data["representations"][0]["stagingDir"] = staging_dir - instance.append(image_instance) + instance.append(image_instance) def get_export_config(self, instance): """Return an export configuration dict for texture exports. diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index 22acf07284..a5bb274b78 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -12,9 +12,6 @@ class ExtractTextures(publish.Extractor): particular Extractor doesn't specify representations to integrate. """ - # TODO: More explicitly detect UDIM tiles - # TODO: Get color spaces - # TODO: Detect what source data channels end up in each file label = "Extract Texture Set" hosts = ['substancepainter'] From 04b32350202e17877ddce8832767668e34e95715 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:32:05 +0100 Subject: [PATCH 054/428] Cosmetics --- .../plugins/publish/collect_textureset_images.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 6928bdb36c..f85861d0eb 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -37,13 +37,17 @@ class CollectTextureSet(pyblish.api.InstancePlugin): self.create_image_instance(instance, template, outputs) def create_image_instance(self, instance, template, outputs): + f"""Create a new instance per image or UDIM sequence. + + The new instances will be of family `image`. + + """ context = instance.context first_filepath = outputs[0]["filepath"] fnames = [os.path.basename(output["filepath"]) for output in outputs] ext = os.path.splitext(first_filepath)[1] assert ext.lstrip('.'), f"No extension: {ext}" - map_identifier = strip_template(template) # Define the suffix we want to give this particular texture @@ -51,6 +55,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): suffix = f".{map_identifier}" image_subset = instance.data["subset"][len("textureSet"):] image_subset = "texture" + image_subset + suffix + # Prepare representation representation = { 'name': ext.lstrip("."), @@ -84,6 +89,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): image_instance.data["representations"][0]["tags"] = ["review"] image_instance.data["representations"][0]["stagingDir"] = staging_dir + # Store the instance in the original instance as a member instance.append(image_instance) def get_export_config(self, instance): From d80e20482b96b388ab91edece375f067f2b9e6b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:33:19 +0100 Subject: [PATCH 055/428] Cosmetics + add assertion --- openpype/hosts/substancepainter/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 2406680a68..bf4415af8a 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -195,8 +195,9 @@ def get_export_templates(config, format="png", strip_folder=True): result = substance_painter.js.evaluate(cmd) if strip_folder: - for stack, maps in result.items(): + for _stack, maps in result.items(): for map_template, map_filepath in maps.items(): + assert map_filepath.startswith(folder) map_filename = map_filepath[len(folder):].lstrip("/") maps[map_template] = map_filename From 196b91896bf9f55414ef766eb2e72631ef066e51 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:35:43 +0100 Subject: [PATCH 056/428] Shush hound --- openpype/hosts/substancepainter/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index bf4415af8a..5b32e3a9aa 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -241,9 +241,8 @@ def _templates_to_regex(templates, return os.path.splitext(os.path.basename(path))[0] if colorspaces and any(colorspaces): - colorspace_match = ( - "(" + "|".join(re.escape(c) for c in colorspaces) + ")" - ) + colorspace_match = "|".join(re.escape(c) for c in set(colorspaces)) + colorspace_match = f"({colorspace_match})" else: # No colorspace support enabled colorspace_match = "" From 5bfb010fbfc0211c7266993fb1b9ddbc2d21162d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:36:23 +0100 Subject: [PATCH 057/428] Shush hound - fix invalid escape sequence --- openpype/hosts/substancepainter/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 5b32e3a9aa..278a23ce01 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -285,7 +285,7 @@ def _templates_to_regex(templates, # The filename templates don't include the extension so we add it # to be able to match the out filename beginning to end - ext_regex = "(?P\.[A-Za-z][A-Za-z0-9-]*)" + ext_regex = r"(?P\.[A-Za-z][A-Za-z0-9-]*)" search_regex = rf"^{search_regex}{ext_regex}$" regexes[template] = search_regex From 2335facfff9d800b32bd3b09f71cbb4daf57035e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:37:35 +0100 Subject: [PATCH 058/428] Fix docstring --- openpype/hosts/substancepainter/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 278a23ce01..7a10ae1eb6 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -349,7 +349,7 @@ def strip_template(template, strip="._ "): def get_parsed_export_maps(config): - """ + """Return Export Config's expected output textures with parsed data. This tries to parse the texture outputs using a Python API export config. From aa0c62b4d7e73d10e63f7384a9d534a12c8fd16e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:38:56 +0100 Subject: [PATCH 059/428] Cleanup --- .../plugins/publish/collect_textureset_images.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index f85861d0eb..53319ba96d 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -37,10 +37,10 @@ class CollectTextureSet(pyblish.api.InstancePlugin): self.create_image_instance(instance, template, outputs) def create_image_instance(self, instance, template, outputs): - f"""Create a new instance per image or UDIM sequence. - + """Create a new instance per image or UDIM sequence. + The new instances will be of family `image`. - + """ context = instance.context From cb04f6bb8b07b776544ed0666fe8440ff52a2ce1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 14 Jan 2023 20:56:29 +0100 Subject: [PATCH 060/428] Fix/Cleanup docstring --- openpype/hosts/substancepainter/api/lib.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 7a10ae1eb6..22dc3059fc 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -378,7 +378,7 @@ def get_parsed_export_maps(config): colorSpace: The color space, e.g. "ACES - ACEScg", udim: The udim tile, e.g. "1001" - 2) Template and file outputs + 2) Template output and filepath filepath: Full path to the resulting texture map, e.g. "/path/to/mesh_DefaultMaterial_BaseColor_ACES - ACEScg.1002.png", @@ -386,10 +386,6 @@ def get_parsed_export_maps(config): Note: if template had slashes (folders) then `output` will too. So `output` might include a folder. - channel: The stripped static characters of the filename template which - usually look like an identifier for that map, e.g. "BaseColor". - See `_stripped_template` - Returns: dict: [texture_set, stack]: {template: [file1_data, file2_data]} From 33aafc3ff6f7e1b4f213345e7baa80f50d4e1f51 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 01:30:43 +0100 Subject: [PATCH 061/428] Implement OCIO support for Substance Painter + publish color space with textures --- openpype/hooks/pre_host_set_ocio.py | 37 +++++++++++++++++++ .../publish/collect_textureset_images.py | 9 ++++- .../plugins/publish/extract_textures.py | 19 +++++++++- .../project_settings/substancepainter.json | 10 +++++ .../schema_project_substancepainter.json | 17 +++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 openpype/hooks/pre_host_set_ocio.py diff --git a/openpype/hooks/pre_host_set_ocio.py b/openpype/hooks/pre_host_set_ocio.py new file mode 100644 index 0000000000..b9e2b79bf4 --- /dev/null +++ b/openpype/hooks/pre_host_set_ocio.py @@ -0,0 +1,37 @@ +from openpype.lib import PreLaunchHook + +from openpype.pipeline.colorspace import get_imageio_config +from openpype.pipeline.template_data import get_template_data_with_names + + +class PreLaunchHostSetOCIO(PreLaunchHook): + """Set OCIO environment for the host""" + + order = 0 + app_groups = ["substancepainter"] + + def execute(self): + """Hook entry method.""" + + anatomy_data = get_template_data_with_names( + project_name=self.data["project_doc"]["name"], + asset_name=self.data["asset_doc"]["name"], + task_name=self.data["task_name"], + host_name=self.host_name, + system_settings=self.data["system_settings"] + ) + + ocio_config = get_imageio_config( + project_name=self.data["project_doc"]["name"], + host_name=self.host_name, + project_settings=self.data["project_settings"], + anatomy_data=anatomy_data, + anatomy=self.data["anatomy"] + ) + + if ocio_config: + ocio_path = ocio_config["path"] + self.log.info(f"Setting OCIO config path: {ocio_path}") + self.launch_context.env["OCIO"] = ocio_path + else: + self.log.debug("OCIO not set or enabled") diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 53319ba96d..0e445c9c1c 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -67,8 +67,6 @@ class CollectTextureSet(pyblish.api.InstancePlugin): if bool(outputs[0].get("udim")): representation["udim"] = True - # TODO: Store color space with the representation - # Clone the instance image_instance = context.create_instance(instance.name) image_instance[:] = instance[:] @@ -83,6 +81,13 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Group the textures together in the loader image_instance.data["subsetGroup"] = instance.data["subset"] + # Store color space with the instance + # Note: The extractor will assign it to the representation + colorspace = outputs[0].get("colorSpace") + if colorspace: + self.log.debug(f"{image_subset} colorspace: {colorspace}") + image_instance.data["colorspace"] = colorspace + # Set up the representation for thumbnail generation # TODO: Simplify this once thumbnail extraction is refactored staging_dir = os.path.dirname(first_filepath) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index a5bb274b78..e66ce6dbf6 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -2,7 +2,7 @@ from openpype.pipeline import KnownPublishError, publish import substance_painter.export -class ExtractTextures(publish.Extractor): +class ExtractTextures(publish.ExtractorColormanaged): """Extract Textures using an output template config. Note: @@ -40,6 +40,23 @@ class ExtractTextures(publish.Extractor): # TODO: Confirm the files indeed exist # TODO: make sure representations are registered + # We'll insert the color space data for each image instance that we + # added into this texture set. The collector couldn't do so because + # some anatomy and other instance data needs to be collected prior + context = instance.context + for image_instance in instance: + + colorspace = image_instance.data.get("colorspace") + if not colorspace: + self.log.debug("No color space data present for instance: " + f"{image_instance}") + continue + + for representation in image_instance.data["representations"]: + self.set_representation_colorspace(representation, + context=context, + colorspace=colorspace) + # Add a fake representation which won't be integrated so the # Integrator leaves us alone - otherwise it would error # TODO: Add `instance.data["integrate"] = False` support in Integrator? diff --git a/openpype/settings/defaults/project_settings/substancepainter.json b/openpype/settings/defaults/project_settings/substancepainter.json index a424a923da..0f9f1af71e 100644 --- a/openpype/settings/defaults/project_settings/substancepainter.json +++ b/openpype/settings/defaults/project_settings/substancepainter.json @@ -1,3 +1,13 @@ { + "imageio": { + "ocio_config": { + "enabled": true, + "filepath": [] + }, + "file_rules": { + "enabled": true, + "rules": {} + } + }, "shelves": {} } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json index 4a02a9d8ca..79a39b8e6e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json @@ -5,6 +5,23 @@ "label": "Substance Painter", "is_file": true, "children": [ + { + "key": "imageio", + "type": "dict", + "label": "Color Management (ImageIO)", + "is_group": true, + "children": [ + { + "type": "schema", + "name": "schema_imageio_config" + }, + { + "type": "schema", + "name": "schema_imageio_file_rules" + } + + ] + }, { "type": "dict-modifiable", "key": "shelves", From eecf109cab26ab34940ece267e7b26ecd6dc6177 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 01:32:42 +0100 Subject: [PATCH 062/428] Support single image (otherwise integrator will fail) --- .../plugins/publish/collect_textureset_images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 53319ba96d..18d1e59c4c 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -60,7 +60,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): representation = { 'name': ext.lstrip("."), 'ext': ext.lstrip("."), - 'files': fnames, + 'files': fnames if len(fnames) > 1 else fnames[0], } # Mark as UDIM explicitly if it has UDIM tiles. From 30ae52770d551bca7d35c0b1cdd9893140cf6db7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 01:33:21 +0100 Subject: [PATCH 063/428] Rename application group to substancepainter for consistency and clarity --- openpype/hooks/pre_add_last_workfile_arg.py | 2 +- openpype/settings/defaults/system_settings/applications.json | 2 +- .../system_schema/host_settings/schema_substancepainter.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index d5a9a41e5a..49fb54d263 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -23,7 +23,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "blender", "photoshop", "tvpaint", - "substance", + "substancepainter", "aftereffects" ] diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 30c692d0e6..d78b54fa05 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -1315,7 +1315,7 @@ } } }, - "substance": { + "substancepainter": { "enabled": true, "label": "Substance Painter", "icon": "app_icons/substancepainter.png", diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_substancepainter.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_substancepainter.json index 513f98c610..fb3b21e63f 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_substancepainter.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_substancepainter.json @@ -1,6 +1,6 @@ { "type": "dict", - "key": "substance", + "key": "substancepainter", "label": "Substance Painter", "collapsible": true, "checkbox_key": "enabled", From 313cb0d550174bacb0a9377829a62283f3520523 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 01:34:00 +0100 Subject: [PATCH 064/428] Ensure safeguarding against forward/backslashes differences --- openpype/hosts/substancepainter/api/lib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 22dc3059fc..9bd408f0f2 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -189,7 +189,7 @@ def get_export_templates(config, format="png", strip_folder=True): dict: The expected output maps. """ - folder = config["exportPath"] + folder = config["exportPath"].replace("\\", "/") preset = config["defaultExportPreset"] cmd = f'alg.mapexport.getPathsExportDocumentMaps("{preset}", "{folder}", "{format}")' # noqa result = substance_painter.js.evaluate(cmd) @@ -197,6 +197,7 @@ def get_export_templates(config, format="png", strip_folder=True): if strip_folder: for _stack, maps in result.items(): for map_template, map_filepath in maps.items(): + map_filepath = map_filepath.replace("\\", "/") assert map_filepath.startswith(folder) map_filename = map_filepath[len(folder):].lstrip("/") maps[map_template] = map_filename @@ -441,7 +442,10 @@ def get_parsed_export_maps(config): # We strip explicitly using the full parent export path instead of # using `os.path.basename` because export template is allowed to # have subfolders in its template which we want to match against - assert filepath.startswith(export_path) + filepath = filepath.replace("\\", "/") + assert filepath.startswith(export_path), ( + f"Filepath {filepath} must start with folder {export_path}" + ) filename = filepath[len(export_path):] for template, regex in template_regex.items(): From ece0e7ded2d721dfe92849a8d246bfb4ef0464cd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 01:36:04 +0100 Subject: [PATCH 065/428] No need to strip folder for the templates, we're not using the filename values of the result. --- openpype/hosts/substancepainter/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 9bd408f0f2..754f8a2bd6 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -395,7 +395,7 @@ def get_parsed_export_maps(config): from .colorspace import get_project_channel_data outputs = substance_painter.export.list_project_textures(config) - templates = get_export_templates(config) + templates = get_export_templates(config, strip_folder=False) # Get all color spaces set for the current project project_colorspaces = set( From 31e37e5a33298718c541bb1969e464ff7ae930e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Jan 2023 02:07:00 +0100 Subject: [PATCH 066/428] Use project doc and asset doc directly for `get_template_data` --- openpype/hooks/pre_host_set_ocio.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hooks/pre_host_set_ocio.py b/openpype/hooks/pre_host_set_ocio.py index b9e2b79bf4..3620d88db6 100644 --- a/openpype/hooks/pre_host_set_ocio.py +++ b/openpype/hooks/pre_host_set_ocio.py @@ -1,7 +1,7 @@ from openpype.lib import PreLaunchHook from openpype.pipeline.colorspace import get_imageio_config -from openpype.pipeline.template_data import get_template_data_with_names +from openpype.pipeline.template_data import get_template_data class PreLaunchHostSetOCIO(PreLaunchHook): @@ -13,9 +13,9 @@ class PreLaunchHostSetOCIO(PreLaunchHook): def execute(self): """Hook entry method.""" - anatomy_data = get_template_data_with_names( - project_name=self.data["project_doc"]["name"], - asset_name=self.data["asset_doc"]["name"], + anatomy_data = get_template_data( + project_doc=self.data["project_doc"], + asset_doc=self.data["asset_doc"], task_name=self.data["task_name"], host_name=self.host_name, system_settings=self.data["system_settings"] From 9329ff28d57f75d54dec1ba5aa25f390e02f7f3d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 Jan 2023 15:39:59 +0100 Subject: [PATCH 067/428] Show new project prompt with mesh preloaded --- openpype/hosts/substancepainter/api/lib.py | 126 ++++++++++++++++++ .../plugins/load/load_mesh.py | 17 +-- 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 754f8a2bd6..e552caee6d 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -520,3 +520,129 @@ def load_shelf(path, name=None): substance_painter.resource.Shelves.add(name, path) return name + + +def _get_new_project_action(): + """Return QAction which triggers Substance Painter's new project dialog""" + from PySide2 import QtGui + + main_window = substance_painter.ui.get_main_window() + + # Find the file menu's New file action + menubar = main_window.menuBar() + new_action = None + for action in menubar.actions(): + menu = action.menu() + if not menu: + continue + + if menu.objectName() != "file": + continue + + # Find the action with the CTRL+N key sequence + new_action = next(action for action in menu.actions() + if action.shortcut() == QtGui.QKeySequence.New) + break + + return new_action + + +def prompt_new_file_with_mesh(mesh_filepath): + """Prompts the user for a new file using Substance Painter's own dialog. + + This will set the mesh path to load to the given mesh and disables the + dialog box to disallow the user to change the path. This way we can allow + user configuration of a project but set the mesh path ourselves. + + Warning: + This is very hacky and experimental. + + Note: + If a project is currently open using the same mesh filepath it can't + accurately detect whether the user had actually accepted the new project + dialog or whether the project afterwards is still the original project, + for example when the user might have cancelled the operation. + + """ + from PySide2 import QtWidgets, QtCore + + app = QtWidgets.QApplication.instance() + assert os.path.isfile(mesh_filepath), \ + f"Mesh filepath does not exist: {mesh_filepath}" + + def _setup_file_dialog(): + """Set filepath in QFileDialog and trigger accept result""" + file_dialog = app.activeModalWidget() + assert isinstance(file_dialog, QtWidgets.QFileDialog) + + # Quickly hide the dialog + file_dialog.hide() + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) + + file_dialog.setDirectory(os.path.dirname(mesh_filepath)) + url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) + file_dialog.selectUrl(url) + + # Give the explorer window time to refresh to the folder and select + # the file + while not file_dialog.selectedFiles(): + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) + print(f"Selected: {file_dialog.selectedFiles()}") + + # Set it again now we know the path is refreshed - without this + # accepting the dialog will often not trigger the correct filepath + file_dialog.setDirectory(os.path.dirname(mesh_filepath)) + url = QtCore.QUrl.fromLocalFile(os.path.basename(mesh_filepath)) + file_dialog.selectUrl(url) + + file_dialog.done(file_dialog.Accepted) + app.processEvents(QtCore.QEventLoop.AllEvents) + + def _setup_prompt(): + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) + dialog = app.activeModalWidget() + assert dialog.objectName() == "NewProjectDialog" + + # Set the window title + mesh = os.path.basename(mesh_filepath) + dialog.setWindowTitle(f"New Project with mesh: {mesh}") + + # Get the select mesh file button + mesh_select = dialog.findChild(QtWidgets.QPushButton, "meshSelect") + + # Hide the select mesh button to the user to block changing of mesh + mesh_select.setVisible(False) + + # Ensure UI is visually up-to-date + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) + + # Trigger the 'select file' dialog to set the path and have the + # new file dialog to use the path. + QtCore.QTimer.singleShot(10, _setup_file_dialog) + mesh_select.click() + + app.processEvents(QtCore.QEventLoop.AllEvents, 5000) + + mesh_filename = dialog.findChild(QtWidgets.QFrame, "meshFileName") + mesh_filename_label = mesh_filename.findChild(QtWidgets.QLabel) + if not mesh_filename_label.text(): + dialog.close() + raise RuntimeError(f"Failed to set mesh path: {mesh_filepath}") + + new_action = _get_new_project_action() + if not new_action: + raise RuntimeError("Unable to detect new file action..") + + QtCore.QTimer.singleShot(0, _setup_prompt) + new_action.trigger() + app.processEvents(QtCore.QEventLoop.AllEvents, 5000) + + if not substance_painter.project.is_open(): + return + + # Confirm mesh was set as expected + project_mesh = substance_painter.project.last_imported_mesh_path() + if os.path.normpath(project_mesh) != os.path.normpath(mesh_filepath): + return + + return project_mesh diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 00f808199f..4e800bd623 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -7,6 +7,7 @@ from openpype.hosts.substancepainter.api.pipeline import ( set_container_metadata, remove_container_metadata ) +from openpype.hosts.substancepainter.api.lib import prompt_new_file_with_mesh import substance_painter.project import qargparse @@ -45,18 +46,10 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if not substance_painter.project.is_open(): # Allow to 'initialize' a new project - # TODO: preferably these settings would come from the actual - # new project prompt of Substance (or something that is - # visually similar to still allow artist decisions) - settings = substance_painter.project.Settings( - default_texture_resolution=4096, - import_cameras=import_cameras, - ) - - substance_painter.project.create( - mesh_file_path=self.fname, - settings=settings - ) + result = prompt_new_file_with_mesh(mesh_filepath=self.fname) + if not result: + self.log.info("User cancelled new project prompt.") + return else: # Reload the mesh From 033d37ca283e6fed6d9a9337e4001e5978b12271 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 Jan 2023 17:01:59 +0100 Subject: [PATCH 068/428] Early draft for Substance Painter documentation --- website/docs/artist_hosts_substancepainter.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 website/docs/artist_hosts_substancepainter.md diff --git a/website/docs/artist_hosts_substancepainter.md b/website/docs/artist_hosts_substancepainter.md new file mode 100644 index 0000000000..9ed83421af --- /dev/null +++ b/website/docs/artist_hosts_substancepainter.md @@ -0,0 +1,80 @@ +--- +id: artist_hosts_substancepainter +title: Substance Painter +sidebar_label: Substance Painter +--- + +## OpenPype global tools + +- [Work Files](artist_tools.md#workfiles) +- [Load](artist_tools.md#loader) +- [Manage (Inventory)](artist_tools.md#inventory) +- [Publish](artist_tools.md#publisher) +- [Library Loader](artist_tools.md#library-loader) + +## Working with OpenPype in Substance Painter + +The Substance Painter OpenPype integration allows you to: + +- Set the project mesh and easily keep it in sync with updates of the model +- Easily export your textures as versioned publishes for others to load and update. + +## Setting the project mesh + +Substance Painter requires a project file to have a mesh path configured. +As such, you can't start a workfile without choosing a mesh path. + +To start a new project using a published model you can _without an open project_ +use OpenPype > Load.. > Load Mesh on a supported publish. This will prompt you +with a New Project prompt preset to that particular mesh file. + +If you already have a project open, you can also replace (reload) your mesh +using the same Load Mesh functionality. + +After having the project mesh loaded or reloaded through the loader +tool the mesh will be _managed_ by OpenPype. For example, you'll be notified +on workfile open whether the mesh in your workfile is outdated. You can also +set it to specific version using OpenPype > Manage.. where you can right click +on the project mesh to perform _Set Version_ + +:::info +A Substance Painter project will always have only one mesh set. Whenever you +trigger _Load Mesh_ from the loader this will **replace** your currently loaded +mesh for your open project. +::: + +## Publishing textures + +To publish your textures we must first create a `textureSet` +publish instance. + +To create a **TextureSet instance** we will use OpenPype's publisher tool. Go +to **OpenPype → Publish... → TextureSet** + +The texture set instance will define what Substance Painter export template `.spexp` to +use and thus defines what texture maps will be exported from your workfile. + +:::info +The TextureSet instance gets saved with your Substance Painter project. As such, +you will only need to configure this once for your workfile. Next time you can +just click **OpenPype → Publish...** and start publishing directly with the +same settings. +::: + + +### Known issues + +#### Can't see the OpenPype menu? + +If you're unable to see the OpenPype top level menu in Substance Painter make +sure you have launched Substance Painter through OpenPype and that the OpenPype +Integration plug-in is loaded inside Substance Painter: **Python > openpype_plugin** + +#### Substance Painter + Steam + +Running the steam version of Substance Painter within OpenPype will require you +to close the Steam executable before launching Substance Painter through OpenPype. +Otherwise the Substance Painter process is launched using Steam's existing +environment and thus will not be able to pick up the pipeline integration. + +This appears to be a limitation of how Steam works. \ No newline at end of file From 1c77d2b002527a450c8be21d93040bccd588413e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 10:18:01 +0100 Subject: [PATCH 069/428] Fix UDIM integration --- .../plugins/publish/collect_textureset_images.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 18d1e59c4c..5f06880663 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -65,7 +65,10 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Mark as UDIM explicitly if it has UDIM tiles. if bool(outputs[0].get("udim")): - representation["udim"] = True + # The representation for a UDIM sequence should have a `udim` key + # that is a list of all udim tiles (str) like: ["1001", "1002"] + # strings. See CollectTextures plug-in and Integrators. + representation["udim"] = [output["udim"] for output in outputs] # TODO: Store color space with the representation From f9f95b84e68da86ce53f9881ee59b98acb6d9aef Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 25 Jan 2023 11:09:19 +0000 Subject: [PATCH 070/428] Basic implementation of the new Creator --- openpype/hosts/unreal/api/__init__.py | 6 +- openpype/hosts/unreal/api/pipeline.py | 53 ++++++- openpype/hosts/unreal/api/plugin.py | 209 +++++++++++++++++++++++++- 3 files changed, 262 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py index ca9db259e6..2618a7677c 100644 --- a/openpype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- """Unreal Editor OpenPype host API.""" -from .plugin import Loader +from .plugin import ( + UnrealActorCreator, + UnrealAssetCreator, + Loader +) from .pipeline import ( install, diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 2081c8fd13..7a21effcbc 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import os +import json import logging from typing import List from contextlib import contextmanager @@ -16,13 +17,14 @@ from openpype.pipeline import ( ) from openpype.tools.utils import host_tools import openpype.hosts.unreal -from openpype.host import HostBase, ILoadHost +from openpype.host import HostBase, ILoadHost, IPublishHost import unreal # noqa - logger = logging.getLogger("openpype.hosts.unreal") + OPENPYPE_CONTAINERS = "OpenPypeContainers" +CONTEXT_CONTAINER = "OpenPype/context.json" UNREAL_VERSION = semver.VersionInfo( *os.getenv("OPENPYPE_UNREAL_VERSION").split(".") ) @@ -35,7 +37,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -class UnrealHost(HostBase, ILoadHost): +class UnrealHost(HostBase, ILoadHost, IPublishHost): """Unreal host implementation. For some time this class will re-use functions from module based @@ -60,6 +62,26 @@ class UnrealHost(HostBase, ILoadHost): show_tools_dialog() + def update_context_data(self, data, changes): + unreal.log_warning("update_context_data") + unreal.log_warning(data) + content_path = unreal.Paths.project_content_dir() + op_ctx = content_path + CONTEXT_CONTAINER + with open(op_ctx, "w+") as f: + json.dump(data, f) + with open(op_ctx, "r") as fp: + test = eval(json.load(fp)) + unreal.log_warning(test) + + def get_context_data(self): + content_path = unreal.Paths.project_content_dir() + op_ctx = content_path + CONTEXT_CONTAINER + if not os.path.isfile(op_ctx): + return {} + with open(op_ctx, "r") as fp: + data = eval(json.load(fp)) + return data + def install(): """Install Unreal configuration for OpenPype.""" @@ -133,6 +155,31 @@ def ls(): yield data +def lsinst(): + ar = unreal.AssetRegistryHelpers.get_asset_registry() + # UE 5.1 changed how class name is specified + class_name = [ + "/Script/OpenPype", + "OpenPypePublishInstance" + ] if ( + UNREAL_VERSION.major == 5 + and UNREAL_VERSION.minor > 0 + ) else "OpenPypePublishInstance" # noqa + instances = ar.get_assets_by_class(class_name, True) + + # get_asset_by_class returns AssetData. To get all metadata we need to + # load asset. get_tag_values() work only on metadata registered in + # Asset Registry Project settings (and there is no way to set it with + # python short of editing ini configuration file). + for asset_data in instances: + asset = asset_data.get_asset() + data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset) + data["objectName"] = asset_data.asset_name + data = cast_map_to_str_dict(data) + + yield data + + def parse_container(container): """To get data from container, AssetContainer must be loaded. diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index 6fc00cb71c..f89ff153b1 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -1,7 +1,212 @@ # -*- coding: utf-8 -*- -from abc import ABC +import sys +import six +from abc import ( + ABC, + ABCMeta, + abstractmethod +) -from openpype.pipeline import LoaderPlugin +import unreal + +from .pipeline import ( + create_publish_instance, + imprint, + lsinst +) +from openpype.lib import BoolDef +from openpype.pipeline import ( + Creator, + LoaderPlugin, + CreatorError, + CreatedInstance +) + + +class OpenPypeCreatorError(CreatorError): + pass + + +@six.add_metaclass(ABCMeta) +class UnrealBaseCreator(Creator): + """Base class for Unreal creator plugins.""" + root = "/Game/OpenPype/PublishInstances" + suffix = "_INS" + + @staticmethod + def cache_subsets(shared_data): + """Cache instances for Creators to shared data. + + Create `unreal_cached_subsets` key when needed in shared data and + fill it with all collected instances from the scene under its + respective creator identifiers. + + If legacy instances are detected in the scene, create + `unreal_cached_legacy_subsets` there and fill it with + all legacy subsets under family as a key. + + Args: + Dict[str, Any]: Shared data. + + Return: + Dict[str, Any]: Shared data dictionary. + + """ + if shared_data.get("unreal_cached_subsets") is None: + shared_data["unreal_cached_subsets"] = {} + if shared_data.get("unreal_cached_legacy_subsets") is None: + shared_data["unreal_cached_legacy_subsets"] = {} + cached_instances = lsinst() + for i in cached_instances: + if not i.get("creator_identifier"): + # we have legacy instance + family = i.get("family") + if (family not in + shared_data["unreal_cached_legacy_subsets"]): + shared_data[ + "unreal_cached_legacy_subsets"][family] = [i] + else: + shared_data[ + "unreal_cached_legacy_subsets"][family].append(i) + continue + + creator_id = i.get("creator_identifier") + if creator_id not in shared_data["unreal_cached_subsets"]: + shared_data["unreal_cached_subsets"][creator_id] = [i] + else: + shared_data["unreal_cached_subsets"][creator_id].append(i) + return shared_data + + @abstractmethod + def create(self, subset_name, instance_data, pre_create_data): + pass + + def collect_instances(self): + # cache instances if missing + self.cache_subsets(self.collection_shared_data) + for instance in self.collection_shared_data[ + "unreal_cached_subsets"].get(self.identifier, []): + created_instance = CreatedInstance.from_existing(instance, self) + self._add_instance_to_context(created_instance) + + def update_instances(self, update_list): + unreal.log_warning(f"Update instances: {update_list}") + for created_inst, _changes in update_list: + instance_node = created_inst.get("instance_path", "") + + if not instance_node: + unreal.log_warning( + f"Instance node not found for {created_inst}") + + new_values = { + key: new_value + for key, (_old_value, new_value) in _changes.items() + } + imprint( + instance_node, + new_values + ) + + def remove_instances(self, instances): + for instance in instances: + instance_node = instance.data.get("instance_path", "") + if instance_node: + unreal.EditorAssetLibrary.delete_asset(instance_node) + + self._remove_instance_from_context(instance) + + def get_pre_create_attr_defs(self): + return [ + BoolDef("use_selection", label="Use selection") + ] + + +@six.add_metaclass(ABCMeta) +class UnrealAssetCreator(UnrealBaseCreator): + """Base class for Unreal creator plugins based on assets.""" + + def create(self, subset_name, instance_data, pre_create_data): + """Create instance of the asset. + + Args: + subset_name (str): Name of the subset. + instance_data (dict): Data for the instance. + pre_create_data (dict): Data for the instance. + + Returns: + CreatedInstance: Created instance. + """ + try: + selection = [] + + if pre_create_data.get("use_selection"): + sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + selection = [a.get_path_name() for a in sel_objects] + + instance_name = f"{subset_name}{self.suffix}" + create_publish_instance(instance_name, self.root) + instance_data["members"] = selection + instance_data["subset"] = subset_name + instance_data["instance_path"] = f"{self.root}/{instance_name}" + instance = CreatedInstance( + self.family, + subset_name, + instance_data, + self) + self._add_instance_to_context(instance) + + imprint(f"{self.root}/{instance_name}", instance_data) + + except Exception as er: + six.reraise( + OpenPypeCreatorError, + OpenPypeCreatorError(f"Creator error: {er}"), + sys.exc_info()[2]) + + +@six.add_metaclass(ABCMeta) +class UnrealActorCreator(UnrealBaseCreator): + """Base class for Unreal creator plugins based on actors.""" + + def create(self, subset_name, instance_data, pre_create_data): + """Create instance of the asset. + + Args: + subset_name (str): Name of the subset. + instance_data (dict): Data for the instance. + pre_create_data (dict): Data for the instance. + + Returns: + CreatedInstance: Created instance. + """ + try: + selection = [] + + if pre_create_data.get("use_selection"): + sel_objects = unreal.EditorUtilityLibrary.get_selected_actors() + selection = [a.get_path_name() for a in sel_objects] + + instance_name = f"{subset_name}{self.suffix}" + create_publish_instance(instance_name, self.root) + instance_data["members"] = selection + instance_data[ + "level"] = unreal.EditorLevelLibrary.get_editor_world() + instance_data["subset"] = subset_name + instance_data["instance_path"] = f"{self.root}/{instance_name}" + instance = CreatedInstance( + self.family, + subset_name, + instance_data, + self) + self._add_instance_to_context(instance) + + imprint(f"{self.root}/{instance_name}", instance_data) + + except Exception as er: + six.reraise( + OpenPypeCreatorError, + OpenPypeCreatorError(f"Creator error: {er}"), + sys.exc_info()[2]) class Loader(LoaderPlugin, ABC): From 36eeb976f78cd3a038454caeebdad0703f673c4f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 26 Jan 2023 16:04:41 +0100 Subject: [PATCH 071/428] New style creator --- .../houdini/plugins/create/create_review.py | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 36d87ba8de..a1bcfc23ed 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -1,9 +1,14 @@ from openpype.hosts.houdini.api import plugin +from openpype.pipeline import ( + CreatedInstance, + OpenPypePyblishPluginMixin +) -class CreateReview(plugin.Creator): +class CreateReview(plugin.HoudiniCreator, OpenPypePyblishPluginMixin): """Review with OpenGL ROP""" + identifier = "io.openpype.creators.houdini.review" label = "Review" family = "review" icon = "video-camera" @@ -15,27 +20,26 @@ class CreateReview(plugin.Creator): height = 720 aspect = 1.0 - def __init__(self, *args, **kwargs): - super(CreateReview, self).__init__(*args, **kwargs) + def create(self, subset_name, instance_data, pre_create_data): + + import hou # Remove the active, we are checking the bypass flag of the nodes - self.data.pop("active", None) + instance_data.pop("active", None) - self.data.update({"node_type": "opengl"}) + instance_data["node_type"] = "opengl" - def _process(self, instance): - """Creator main entry point. + instance = super(CreateReview, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance - Args: - instance (hou.Node): Created Houdini instance. - - """ - import hou + instance_node = hou.node(instance.get("instance_node")) frame_range = hou.playbar.frameRange() parms = { - "picture": "$HIP/pyblish/{0}/{0}.$F4.png".format(self.name), + "picture": '$HIP/pyblish/`chs("subset")`/`chs("subset")`.$F4.png', # Render frame range "trange": 1, @@ -55,19 +59,16 @@ class CreateReview(plugin.Creator): "aspect": self.aspect }) - if self.nodes: + if self.selected_nodes: # todo: allow only object paths? - node_paths = " ".join(node.path() for node in self.nodes) + node_paths = " ".join(node.path() for node in self.selected_nodes) parms.update({"scenepath": node_paths}) - instance.setParms(parms) + instance_node.setParms(parms) # Lock any parameters in this list to_lock = [ - # Lock some Avalon attributes "family", - "id", + "id" ] - for name in to_lock: - parm = instance.parm(name) - parm.lock(True) + self.lock_parameters(instance_node, to_lock) From e02b6f7073861c20b26782eac480a2a1e7f10f54 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 26 Jan 2023 16:07:11 +0100 Subject: [PATCH 072/428] Fix extractor for new style creator --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index f99c987634..8357e188a8 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -4,6 +4,8 @@ import pyblish.api import openpype.api from openpype.hosts.houdini.api.lib import render_rop +import hou + class ExtractOpenGL(openpype.api.Extractor): @@ -15,7 +17,7 @@ class ExtractOpenGL(openpype.api.Extractor): def process(self, instance): - ropnode = instance[0] + ropnode = hou.node(instance.data["instance_node"]) # Get the filename from the filename parameter # `.evalParm(parameter)` will make sure all tokens are resolved From d89f09177a83759c26a1fb3b3423145df3d2257e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 26 Jan 2023 16:42:52 +0100 Subject: [PATCH 073/428] Do not automatically force populate instance data with frame ranges of the asset --- .../publish/collect_anatomy_instance_data.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 48171aa957..4fbb93324b 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -50,7 +50,6 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): project_name = context.data["projectName"] self.fill_missing_asset_docs(context, project_name) - self.fill_instance_data_from_asset(context) self.fill_latest_versions(context, project_name) self.fill_anatomy_data(context) @@ -115,23 +114,6 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "Not found asset documents with names \"{}\"." ).format(joined_asset_names)) - def fill_instance_data_from_asset(self, context): - for instance in context: - asset_doc = instance.data.get("assetEntity") - if not asset_doc: - continue - - asset_data = asset_doc["data"] - for key in ( - "fps", - "frameStart", - "frameEnd", - "handleStart", - "handleEnd", - ): - if key not in instance.data and key in asset_data: - instance.data[key] = asset_data[key] - def fill_latest_versions(self, context, project_name): """Try to find latest version for each instance's subset. From fc09f0b532cf3a1ee496a9f74ae22d55753e7841 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 26 Jan 2023 17:35:11 +0000 Subject: [PATCH 074/428] Improved basic creator --- openpype/hosts/unreal/api/plugin.py | 95 ++++++++++++++++++----------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index f89ff153b1..6a561420fa 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -4,7 +4,6 @@ import six from abc import ( ABC, ABCMeta, - abstractmethod ) import unreal @@ -12,7 +11,8 @@ import unreal from .pipeline import ( create_publish_instance, imprint, - lsinst + lsinst, + UNREAL_VERSION ) from openpype.lib import BoolDef from openpype.pipeline import ( @@ -77,9 +77,28 @@ class UnrealBaseCreator(Creator): shared_data["unreal_cached_subsets"][creator_id].append(i) return shared_data - @abstractmethod def create(self, subset_name, instance_data, pre_create_data): - pass + try: + instance_name = f"{subset_name}{self.suffix}" + create_publish_instance(instance_name, self.root) + + instance_data["subset"] = subset_name + instance_data["instance_path"] = f"{self.root}/{instance_name}" + + instance = CreatedInstance( + self.family, + subset_name, + instance_data, + self) + self._add_instance_to_context(instance) + + imprint(f"{self.root}/{instance_name}", instance_data) + + except Exception as er: + six.reraise( + OpenPypeCreatorError, + OpenPypeCreatorError(f"Creator error: {er}"), + sys.exc_info()[2]) def collect_instances(self): # cache instances if missing @@ -117,7 +136,7 @@ class UnrealBaseCreator(Creator): def get_pre_create_attr_defs(self): return [ - BoolDef("use_selection", label="Use selection") + BoolDef("use_selection", label="Use selection", default=True) ] @@ -137,25 +156,21 @@ class UnrealAssetCreator(UnrealBaseCreator): CreatedInstance: Created instance. """ try: - selection = [] + # Check if instance data has members, filled by the plugin. + # If not, use selection. + if not instance_data.get("members"): + selection = [] - if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() - selection = [a.get_path_name() for a in sel_objects] + if pre_create_data.get("use_selection"): + sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + selection = [a.get_path_name() for a in sel_objects] - instance_name = f"{subset_name}{self.suffix}" - create_publish_instance(instance_name, self.root) - instance_data["members"] = selection - instance_data["subset"] = subset_name - instance_data["instance_path"] = f"{self.root}/{instance_name}" - instance = CreatedInstance( - self.family, + instance_data["members"] = selection + + super(UnrealAssetCreator, self).create( subset_name, instance_data, - self) - self._add_instance_to_context(instance) - - imprint(f"{self.root}/{instance_name}", instance_data) + pre_create_data) except Exception as er: six.reraise( @@ -180,27 +195,33 @@ class UnrealActorCreator(UnrealBaseCreator): CreatedInstance: Created instance. """ try: - selection = [] + if UNREAL_VERSION.major == 5: + world = unreal.UnrealEditorSubsystem().get_editor_world() + else: + world = unreal.EditorLevelLibrary.get_editor_world() - if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_actors() - selection = [a.get_path_name() for a in sel_objects] + # Check if the level is saved + if world.get_path_name().startswith("/Temp/"): + raise OpenPypeCreatorError( + "Level must be saved before creating instances.") - instance_name = f"{subset_name}{self.suffix}" - create_publish_instance(instance_name, self.root) - instance_data["members"] = selection - instance_data[ - "level"] = unreal.EditorLevelLibrary.get_editor_world() - instance_data["subset"] = subset_name - instance_data["instance_path"] = f"{self.root}/{instance_name}" - instance = CreatedInstance( - self.family, + # Check if instance data has members, filled by the plugin. + # If not, use selection. + if not instance_data.get("members"): + selection = [] + + if pre_create_data.get("use_selection"): + sel_objects = unreal.EditorUtilityLibrary.get_selected_actors() + selection = [a.get_path_name() for a in sel_objects] + + instance_data["members"] = selection + + instance_data["level"] = world.get_path_name() + + super(UnrealActorCreator, self).create( subset_name, instance_data, - self) - self._add_instance_to_context(instance) - - imprint(f"{self.root}/{instance_name}", instance_data) + pre_create_data) except Exception as er: six.reraise( From f57a6775cc0c0a88ec85002432fbbcaa394cf8ca Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 26 Jan 2023 17:35:42 +0000 Subject: [PATCH 075/428] Updated creators to be compatible with new publisher --- .../unreal/plugins/create/create_camera.py | 44 +++---------- .../unreal/plugins/create/create_layout.py | 39 ++--------- .../unreal/plugins/create/create_look.py | 64 +++++++++---------- .../plugins/create/create_staticmeshfbx.py | 34 ++-------- .../unreal/plugins/create/create_uasset.py | 44 ++++--------- 5 files changed, 65 insertions(+), 160 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index bf1489d688..239dc87db5 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -1,41 +1,13 @@ -import unreal -from unreal import EditorAssetLibrary as eal -from unreal import EditorLevelLibrary as ell - -from openpype.hosts.unreal.api.pipeline import instantiate -from openpype.pipeline import LegacyCreator +# -*- coding: utf-8 -*- +from openpype.hosts.unreal.api.plugin import ( + UnrealActorCreator, +) -class CreateCamera(LegacyCreator): - """Layout output for character rigs""" +class CreateCamera(UnrealActorCreator): + """Create Camera.""" - name = "layoutMain" + identifier = "io.openpype.creators.unreal.camera" label = "Camera" family = "camera" - icon = "cubes" - - root = "/Game/OpenPype/Instances" - suffix = "_INS" - - def __init__(self, *args, **kwargs): - super(CreateCamera, self).__init__(*args, **kwargs) - - def process(self): - data = self.data - - name = data["subset"] - - data["level"] = ell.get_editor_world().get_path_name() - - if not eal.does_directory_exist(self.root): - eal.make_directory(self.root) - - factory = unreal.LevelSequenceFactoryNew() - tools = unreal.AssetToolsHelpers().get_asset_tools() - 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) + icon = "camera" diff --git a/openpype/hosts/unreal/plugins/create/create_layout.py b/openpype/hosts/unreal/plugins/create/create_layout.py index c1067b00d9..1d2e800a13 100644 --- a/openpype/hosts/unreal/plugins/create/create_layout.py +++ b/openpype/hosts/unreal/plugins/create/create_layout.py @@ -1,42 +1,13 @@ # -*- coding: utf-8 -*- -from unreal import EditorLevelLibrary - -from openpype.pipeline import LegacyCreator -from openpype.hosts.unreal.api.pipeline import instantiate +from openpype.hosts.unreal.api.plugin import ( + UnrealActorCreator, +) -class CreateLayout(LegacyCreator): +class CreateLayout(UnrealActorCreator): """Layout output for character rigs.""" - name = "layoutMain" + identifier = "io.openpype.creators.unreal.layout" label = "Layout" family = "layout" icon = "cubes" - - root = "/Game" - suffix = "_INS" - - def __init__(self, *args, **kwargs): - super(CreateLayout, 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"] = EditorLevelLibrary.get_editor_world().get_path_name() - - data["members"] = [] - - if (self.options or {}).get("useSelection"): - # Set as members the selected actors - for actor in EditorLevelLibrary.get_selected_level_actors(): - data["members"].append("{}.{}".format( - actor.get_outer().get_name(), actor.get_name())) - - instantiate(self.root, name, data, selection, self.suffix) diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py index 4abf3f6095..08d61ab9f8 100644 --- a/openpype/hosts/unreal/plugins/create/create_look.py +++ b/openpype/hosts/unreal/plugins/create/create_look.py @@ -1,56 +1,53 @@ # -*- coding: utf-8 -*- -"""Create look in Unreal.""" -import unreal # noqa -from openpype.hosts.unreal.api import pipeline, plugin -from openpype.pipeline import LegacyCreator +import unreal + +from openpype.hosts.unreal.api.pipeline import ( + create_folder +) +from openpype.hosts.unreal.api.plugin import ( + UnrealAssetCreator +) -class CreateLook(LegacyCreator): +class CreateLook(UnrealAssetCreator): """Shader connections defining shape look.""" - name = "unrealLook" - label = "Unreal - Look" + identifier = "io.openpype.creators.unreal.look" + label = "Look" family = "look" icon = "paint-brush" - root = "/Game/Avalon/Assets" - suffix = "_INS" - - def __init__(self, *args, **kwargs): - super(CreateLook, self).__init__(*args, **kwargs) - - def process(self): - name = self.data["subset"] - + def create(self, subset_name, instance_data, pre_create_data): selection = [] - if (self.options or {}).get("useSelection"): + if pre_create_data.get("use_selection"): sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() selection = [a.get_path_name() for a in sel_objects] + if len(selection) != 1: + raise RuntimeError("Please select only one asset.") + + selected_asset = selection[0] + + look_directory = "/Game/OpenPype/Looks" + # Create the folder - path = f"{self.root}/{self.data['asset']}" - new_name = pipeline.create_folder(path, name) - full_path = f"{path}/{new_name}" + folder_name = create_folder(look_directory, subset_name) + path = f"{look_directory}/{folder_name}" # Create a new cube static mesh ar = unreal.AssetRegistryHelpers.get_asset_registry() cube = ar.get_asset_by_object_path("/Engine/BasicShapes/Cube.Cube") - # Create the avalon publish instance object - container_name = f"{name}{self.suffix}" - pipeline.create_publish_instance( - instance=container_name, path=full_path) - # Get the mesh of the selected object - original_mesh = ar.get_asset_by_object_path(selection[0]).get_asset() - materials = original_mesh.get_editor_property('materials') + original_mesh = ar.get_asset_by_object_path(selected_asset).get_asset() + materials = original_mesh.get_editor_property('static_materials') - self.data["members"] = [] + instance_data["members"] = [] # Add the materials to the cube for material in materials: - name = material.get_editor_property('material_slot_name') - object_path = f"{full_path}/{name}.{name}" + mat_name = material.get_editor_property('material_slot_name') + object_path = f"{path}/{mat_name}.{mat_name}" unreal_object = unreal.EditorAssetLibrary.duplicate_loaded_asset( cube.get_asset(), object_path ) @@ -61,8 +58,11 @@ class CreateLook(LegacyCreator): unreal_object.add_material( material.get_editor_property('material_interface')) - self.data["members"].append(object_path) + instance_data["members"].append(object_path) unreal.EditorAssetLibrary.save_asset(object_path) - pipeline.imprint(f"{full_path}/{container_name}", self.data) + super(CreateLook, self).create( + subset_name, + instance_data, + pre_create_data) diff --git a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py index 45d517d27d..1acf7084d1 100644 --- a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py @@ -1,35 +1,13 @@ # -*- coding: utf-8 -*- -"""Create Static Meshes as FBX geometry.""" -import unreal # noqa -from openpype.hosts.unreal.api.pipeline import ( - instantiate, +from openpype.hosts.unreal.api.plugin import ( + UnrealAssetCreator, ) -from openpype.pipeline import LegacyCreator -class CreateStaticMeshFBX(LegacyCreator): - """Static FBX geometry.""" +class CreateStaticMeshFBX(UnrealAssetCreator): + """Create Static Meshes as FBX geometry.""" - name = "unrealStaticMeshMain" - label = "Unreal - Static Mesh" + identifier = "io.openpype.creators.unreal.staticmeshfbx" + label = "Static Mesh (FBX)" family = "unrealStaticMesh" icon = "cube" - asset_types = ["StaticMesh"] - - root = "/Game" - suffix = "_INS" - - def __init__(self, *args, **kwargs): - super(CreateStaticMeshFBX, self).__init__(*args, **kwargs) - - def process(self): - - name = self.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] - - unreal.log("selection: {}".format(selection)) - instantiate(self.root, name, self.data, selection, self.suffix) diff --git a/openpype/hosts/unreal/plugins/create/create_uasset.py b/openpype/hosts/unreal/plugins/create/create_uasset.py index ee584ac00c..2d6fcc1d59 100644 --- a/openpype/hosts/unreal/plugins/create/create_uasset.py +++ b/openpype/hosts/unreal/plugins/create/create_uasset.py @@ -1,36 +1,25 @@ -"""Create UAsset.""" +# -*- coding: utf-8 -*- from pathlib import Path import unreal -from openpype.hosts.unreal.api import pipeline -from openpype.pipeline import LegacyCreator +from openpype.hosts.unreal.api.plugin import ( + UnrealAssetCreator, +) -class CreateUAsset(LegacyCreator): - """UAsset.""" +class CreateUAsset(UnrealAssetCreator): + """Create UAsset.""" - name = "UAsset" + identifier = "io.openpype.creators.unreal.uasset" label = "UAsset" family = "uasset" icon = "cube" - root = "/Game/OpenPype" - suffix = "_INS" + def create(self, subset_name, instance_data, pre_create_data): + if pre_create_data.get("use_selection"): + ar = unreal.AssetRegistryHelpers.get_asset_registry() - def __init__(self, *args, **kwargs): - super(CreateUAsset, self).__init__(*args, **kwargs) - - def process(self): - ar = unreal.AssetRegistryHelpers.get_asset_registry() - - subset = self.data["subset"] - path = f"{self.root}/PublishInstances/" - - unreal.EditorAssetLibrary.make_directory(path) - - selection = [] - if (self.options or {}).get("useSelection"): sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() selection = [a.get_path_name() for a in sel_objects] @@ -50,12 +39,7 @@ class CreateUAsset(LegacyCreator): if Path(sys_path).suffix != ".uasset": raise RuntimeError(f"{Path(sys_path).name} is not a UAsset.") - unreal.log("selection: {}".format(selection)) - container_name = f"{subset}{self.suffix}" - pipeline.create_publish_instance( - instance=container_name, path=path) - - data = self.data.copy() - data["members"] = selection - - pipeline.imprint(f"{path}/{container_name}", data) + super(CreateUAsset, self).create( + subset_name, + instance_data, + pre_create_data) From e411e197379e487a5dd5342e867bba2501ad8442 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 27 Jan 2023 16:53:39 +0000 Subject: [PATCH 076/428] Updated render creator --- .../unreal/plugins/create/create_render.py | 174 ++++++++++-------- 1 file changed, 94 insertions(+), 80 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index a85d17421b..de3efdad74 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -1,117 +1,131 @@ +# -*- coding: utf-8 -*- import unreal -from openpype.hosts.unreal.api import pipeline -from openpype.pipeline import LegacyCreator +from openpype.hosts.unreal.api.pipeline import ( + get_subsequences +) +from openpype.hosts.unreal.api.plugin import ( + UnrealAssetCreator, +) -class CreateRender(LegacyCreator): +class CreateRender(UnrealAssetCreator): """Create instance for sequence for rendering""" - name = "unrealRender" - label = "Unreal - Render" + identifier = "io.openpype.creators.unreal.render" + label = "Render" family = "render" - icon = "cube" - asset_types = ["LevelSequence"] - - root = "/Game/OpenPype/PublishInstances" - suffix = "_INS" - - def process(self): - subset = self.data["subset"] + icon = "eye" + def create(self, subset_name, instance_data, pre_create_data): ar = unreal.AssetRegistryHelpers.get_asset_registry() - # The asset name is the the third element of the path which contains - # the map. - # The index of the split path is 3 because the first element is an - # empty string, as the path begins with "/Content". - a = unreal.EditorUtilityLibrary.get_selected_assets()[0] - asset_name = a.get_path_name().split("/")[3] - - # Get the master sequence and the master level. - # There should be only one sequence and one level in the directory. - filter = unreal.ARFilter( - class_names=["LevelSequence"], - package_paths=[f"/Game/OpenPype/{asset_name}"], - recursive_paths=False) - sequences = ar.get_assets(filter) - ms = sequences[0].get_editor_property('object_path') - filter = unreal.ARFilter( - class_names=["World"], - package_paths=[f"/Game/OpenPype/{asset_name}"], - recursive_paths=False) - levels = ar.get_assets(filter) - ml = levels[0].get_editor_property('object_path') - - selection = [] - if (self.options or {}).get("useSelection"): + if pre_create_data.get("use_selection"): sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() selection = [ a.get_path_name() for a in sel_objects - if a.get_class().get_name() in self.asset_types] + if a.get_class().get_name() == "LevelSequence"] else: - selection.append(self.data['sequence']) + selection = [instance_data['sequence']] - unreal.log(f"selection: {selection}") + seq_data = None - path = f"{self.root}" - unreal.EditorAssetLibrary.make_directory(path) + for sel in selection: + selected_asset = ar.get_asset_by_object_path(sel).get_asset() + selected_asset_path = selected_asset.get_path_name() - ar = unreal.AssetRegistryHelpers.get_asset_registry() + # Check if the selected asset is a level sequence asset. + if selected_asset.get_class().get_name() != "LevelSequence": + unreal.log_warning( + f"Skipping {selected_asset.get_name()}. It isn't a Level " + "Sequence.") - for a in selection: - ms_obj = ar.get_asset_by_object_path(ms).get_asset() + # The asset name is the the third element of the path which + # contains the map. + # To take the asset name, we remove from the path the prefix + # "/Game/OpenPype/" and then we split the path by "/". + sel_path = selected_asset_path + asset_name = sel_path.replace("/Game/OpenPype/", "").split("/")[0] - seq_data = None + # Get the master sequence and the master level. + # There should be only one sequence and one level in the directory. + ar_filter = unreal.ARFilter( + class_names=["LevelSequence"], + package_paths=[f"/Game/OpenPype/{asset_name}"], + recursive_paths=False) + sequences = ar.get_assets(ar_filter) + master_seq = sequences[0].get_asset().get_path_name() + master_seq_obj = sequences[0].get_asset() + ar_filter = unreal.ARFilter( + class_names=["World"], + package_paths=[f"/Game/OpenPype/{asset_name}"], + recursive_paths=False) + levels = ar.get_assets(ar_filter) + master_lvl = levels[0].get_asset().get_path_name() - if a == ms: - seq_data = { - "sequence": ms_obj, - "output": f"{ms_obj.get_name()}", - "frame_range": ( - ms_obj.get_playback_start(), ms_obj.get_playback_end()) - } + # If the selected asset is the master sequence, we get its data + # and then we create the instance for the master sequence. + # Otherwise, we cycle from the master sequence to find the selected + # sequence and we get its data. This data will be used to create + # the instance for the selected sequence. In particular, + # we get the frame range of the selected sequence and its final + # output path. + master_seq_data = { + "sequence": master_seq_obj, + "output": f"{master_seq_obj.get_name()}", + "frame_range": ( + master_seq_obj.get_playback_start(), + master_seq_obj.get_playback_end())} + + if selected_asset_path == master_seq: + seq_data = master_seq_data else: - seq_data_list = [{ - "sequence": ms_obj, - "output": f"{ms_obj.get_name()}", - "frame_range": ( - ms_obj.get_playback_start(), ms_obj.get_playback_end()) - }] + seq_data_list = [master_seq_data] - for s in seq_data_list: - subscenes = pipeline.get_subsequences(s.get('sequence')) + for seq in seq_data_list: + subscenes = get_subsequences(seq.get('sequence')) - for ss in subscenes: + for sub_seq in subscenes: + sub_seq_obj = sub_seq.get_sequence() curr_data = { - "sequence": ss.get_sequence(), - "output": (f"{s.get('output')}/" - f"{ss.get_sequence().get_name()}"), + "sequence": sub_seq_obj, + "output": (f"{seq.get('output')}/" + f"{sub_seq_obj.get_name()}"), "frame_range": ( - ss.get_start_frame(), ss.get_end_frame() - 1) - } + sub_seq.get_start_frame(), + sub_seq.get_end_frame() - 1)} - if ss.get_sequence().get_path_name() == a: + # If the selected asset is the current sub-sequence, + # we get its data and we break the loop. + # Otherwise, we add the current sub-sequence data to + # the list of sequences to check. + if sub_seq_obj.get_path_name() == selected_asset_path: seq_data = curr_data break + seq_data_list.append(curr_data) + # If we found the selected asset, we break the loop. if seq_data is not None: break + # If we didn't find the selected asset, we don't create the + # instance. if not seq_data: + unreal.log_warning( + f"Skipping {selected_asset.get_name()}. It isn't a " + "sub-sequence of the master sequence.") continue - d = self.data.copy() - d["members"] = [a] - d["sequence"] = a - d["master_sequence"] = ms - d["master_level"] = ml - d["output"] = seq_data.get('output') - d["frameStart"] = seq_data.get('frame_range')[0] - d["frameEnd"] = seq_data.get('frame_range')[1] + instance_data["members"] = [selected_asset_path] + instance_data["sequence"] = selected_asset_path + instance_data["master_sequence"] = master_seq + instance_data["master_level"] = master_lvl + instance_data["output"] = seq_data.get('output') + instance_data["frameStart"] = seq_data.get('frame_range')[0] + instance_data["frameEnd"] = seq_data.get('frame_range')[1] - container_name = f"{subset}{self.suffix}" - pipeline.create_publish_instance( - instance=container_name, path=path) - pipeline.imprint(f"{path}/{container_name}", d) + super(CreateRender, self).create( + subset_name, + instance_data, + pre_create_data) From 575eb50c03e02227e2c9dedf8fc7c2a32f558c85 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 30 Jan 2023 11:17:21 +0000 Subject: [PATCH 077/428] Hound fixes --- openpype/hosts/unreal/api/plugin.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index 6a561420fa..71ce0c18a7 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -104,7 +104,7 @@ class UnrealBaseCreator(Creator): # cache instances if missing self.cache_subsets(self.collection_shared_data) for instance in self.collection_shared_data[ - "unreal_cached_subsets"].get(self.identifier, []): + "unreal_cached_subsets"].get(self.identifier, []): created_instance = CreatedInstance.from_existing(instance, self) self._add_instance_to_context(created_instance) @@ -162,7 +162,8 @@ class UnrealAssetCreator(UnrealBaseCreator): selection = [] if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + utility_lib = unreal.EditorUtilityLibrary + sel_objects = utility_lib.get_selected_assets() selection = [a.get_path_name() for a in sel_objects] instance_data["members"] = selection @@ -211,7 +212,8 @@ class UnrealActorCreator(UnrealBaseCreator): selection = [] if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_actors() + utility_lib = unreal.EditorUtilityLibrary + sel_objects = utility_lib.get_selected_assets() selection = [a.get_path_name() for a in sel_objects] instance_data["members"] = selection From af2737a99f608ef6598d54ae8d098a3509a6223b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 31 Jan 2023 16:05:01 +0000 Subject: [PATCH 078/428] Collect instances is no longer needed with the new publisher --- .../plugins/publish/collect_instances.py | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 openpype/hosts/unreal/plugins/publish/collect_instances.py diff --git a/openpype/hosts/unreal/plugins/publish/collect_instances.py b/openpype/hosts/unreal/plugins/publish/collect_instances.py deleted file mode 100644 index 27b711cad6..0000000000 --- a/openpype/hosts/unreal/plugins/publish/collect_instances.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collect publishable instances in Unreal.""" -import ast -import unreal # noqa -import pyblish.api -from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION -from openpype.pipeline.publish import KnownPublishError - - -class CollectInstances(pyblish.api.ContextPlugin): - """Gather instances by OpenPypePublishInstance class - - This collector finds all paths containing `OpenPypePublishInstance` class - asset - - Identifier: - id (str): "pyblish.avalon.instance" - - """ - - label = "Collect Instances" - order = pyblish.api.CollectorOrder - 0.1 - hosts = ["unreal"] - - def process(self, context): - - ar = unreal.AssetRegistryHelpers.get_asset_registry() - class_name = [ - "/Script/OpenPype", - "OpenPypePublishInstance" - ] if ( - UNREAL_VERSION.major == 5 - and UNREAL_VERSION.minor > 0 - ) else "OpenPypePublishInstance" # noqa - instance_containers = ar.get_assets_by_class(class_name, True) - - for container_data in instance_containers: - asset = container_data.get_asset() - data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset) - data["objectName"] = container_data.asset_name - # convert to strings - data = {str(key): str(value) for (key, value) in data.items()} - if not data.get("family"): - raise KnownPublishError("instance has no family") - - # content of container - members = ast.literal_eval(data.get("members")) - self.log.debug(members) - self.log.debug(asset.get_path_name()) - # remove instance container - self.log.info("Creating instance for {}".format(asset.get_name())) - - instance = context.create_instance(asset.get_name()) - instance[:] = members - - # Store the exact members of the object set - instance.data["setMembers"] = members - instance.data["families"] = [data.get("family")] - instance.data["level"] = data.get("level") - instance.data["parent"] = data.get("parent") - - label = "{0} ({1})".format(asset.get_name()[:-4], - data["asset"]) - - instance.data["label"] = label - - instance.data.update(data) From c93fc9aad0743d4252d6bb58c33ac21365b7eac7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Feb 2023 11:22:36 +0000 Subject: [PATCH 079/428] Use External Data in the Unreal Publish Instance to store members Not possible with all the families. Some families require to store actors in a scenes, and we cannot store them in the External Data. --- openpype/hosts/unreal/api/plugin.py | 24 ++++++--- .../unreal/plugins/create/create_look.py | 6 ++- .../publish/collect_instance_members.py | 49 +++++++++++++++++++ .../unreal/plugins/publish/extract_look.py | 4 +- .../unreal/plugins/publish/extract_uasset.py | 8 ++- 5 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 openpype/hosts/unreal/plugins/publish/collect_instance_members.py diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index 71ce0c18a7..da571af9be 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -80,7 +80,7 @@ class UnrealBaseCreator(Creator): def create(self, subset_name, instance_data, pre_create_data): try: instance_name = f"{subset_name}{self.suffix}" - create_publish_instance(instance_name, self.root) + pub_instance = create_publish_instance(instance_name, self.root) instance_data["subset"] = subset_name instance_data["instance_path"] = f"{self.root}/{instance_name}" @@ -92,6 +92,15 @@ class UnrealBaseCreator(Creator): self) self._add_instance_to_context(instance) + pub_instance.set_editor_property('add_external_assets', True) + assets = pub_instance.get_editor_property('asset_data_external') + + ar = unreal.AssetRegistryHelpers.get_asset_registry() + + for member in pre_create_data.get("members", []): + obj = ar.get_asset_by_object_path(member).get_asset() + assets.add(obj) + imprint(f"{self.root}/{instance_name}", instance_data) except Exception as er: @@ -158,15 +167,14 @@ class UnrealAssetCreator(UnrealBaseCreator): try: # Check if instance data has members, filled by the plugin. # If not, use selection. - if not instance_data.get("members"): - selection = [] + if not pre_create_data.get("members"): + pre_create_data["members"] = [] if pre_create_data.get("use_selection"): - utility_lib = unreal.EditorUtilityLibrary - sel_objects = utility_lib.get_selected_assets() - selection = [a.get_path_name() for a in sel_objects] - - instance_data["members"] = selection + utilib = unreal.EditorUtilityLibrary + sel_objects = utilib.get_selected_assets() + pre_create_data["members"] = [ + a.get_path_name() for a in sel_objects] super(UnrealAssetCreator, self).create( subset_name, diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py index 08d61ab9f8..047764ef2a 100644 --- a/openpype/hosts/unreal/plugins/create/create_look.py +++ b/openpype/hosts/unreal/plugins/create/create_look.py @@ -34,6 +34,8 @@ class CreateLook(UnrealAssetCreator): folder_name = create_folder(look_directory, subset_name) path = f"{look_directory}/{folder_name}" + instance_data["look"] = path + # Create a new cube static mesh ar = unreal.AssetRegistryHelpers.get_asset_registry() cube = ar.get_asset_by_object_path("/Engine/BasicShapes/Cube.Cube") @@ -42,7 +44,7 @@ class CreateLook(UnrealAssetCreator): original_mesh = ar.get_asset_by_object_path(selected_asset).get_asset() materials = original_mesh.get_editor_property('static_materials') - instance_data["members"] = [] + pre_create_data["members"] = [] # Add the materials to the cube for material in materials: @@ -58,7 +60,7 @@ class CreateLook(UnrealAssetCreator): unreal_object.add_material( material.get_editor_property('material_interface')) - instance_data["members"].append(object_path) + pre_create_data["members"].append(object_path) unreal.EditorAssetLibrary.save_asset(object_path) diff --git a/openpype/hosts/unreal/plugins/publish/collect_instance_members.py b/openpype/hosts/unreal/plugins/publish/collect_instance_members.py new file mode 100644 index 0000000000..74969f5033 --- /dev/null +++ b/openpype/hosts/unreal/plugins/publish/collect_instance_members.py @@ -0,0 +1,49 @@ +import unreal + +import pyblish.api + + +class CollectInstanceMembers(pyblish.api.InstancePlugin): + """ + Collect members of instance. + + This collector will collect the assets for the families that support to + have them included as External Data, and will add them to the instance + as members. + """ + + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["unreal"] + families = ["look", "unrealStaticMesh", "uasset"] + label = "Collect Instance Members" + + def process(self, instance): + """Collect members of instance.""" + self.log.info("Collecting instance members") + + ar = unreal.AssetRegistryHelpers.get_asset_registry() + + inst_path = instance.data.get('instance_path') + inst_name = instance.data.get('objectName') + + pub_instance = ar.get_asset_by_object_path( + f"{inst_path}.{inst_name}").get_asset() + + if not pub_instance: + self.log.error(f"{inst_path}.{inst_name}") + raise RuntimeError(f"Instance {instance} not found.") + + if not pub_instance.get_editor_property("add_external_assets"): + # No external assets in the instance + return + + assets = pub_instance.get_editor_property('asset_data_external') + + members = [] + + for asset in assets: + members.append(asset.get_path_name()) + + self.log.debug(f"Members: {members}") + + instance.data["members"] = members diff --git a/openpype/hosts/unreal/plugins/publish/extract_look.py b/openpype/hosts/unreal/plugins/publish/extract_look.py index f999ad8651..4b32b4eb95 100644 --- a/openpype/hosts/unreal/plugins/publish/extract_look.py +++ b/openpype/hosts/unreal/plugins/publish/extract_look.py @@ -29,13 +29,13 @@ class ExtractLook(publish.Extractor): for member in instance: asset = ar.get_asset_by_object_path(member) - object = asset.get_asset() + obj = asset.get_asset() name = asset.get_editor_property('asset_name') json_element = {'material': str(name)} - material_obj = object.get_editor_property('static_materials')[0] + material_obj = obj.get_editor_property('static_materials')[0] material = material_obj.material_interface base_color = mat_lib.get_material_property_input_node( diff --git a/openpype/hosts/unreal/plugins/publish/extract_uasset.py b/openpype/hosts/unreal/plugins/publish/extract_uasset.py index 89d779d368..f719df2a82 100644 --- a/openpype/hosts/unreal/plugins/publish/extract_uasset.py +++ b/openpype/hosts/unreal/plugins/publish/extract_uasset.py @@ -22,7 +22,13 @@ class ExtractUAsset(publish.Extractor): staging_dir = self.staging_dir(instance) filename = "{}.uasset".format(instance.name) - obj = instance[0] + members = instance.data.get("members", []) + + if not members: + raise RuntimeError("No members found in instance.") + + # UAsset publishing supports only one member + obj = members[0] asset = ar.get_asset_by_object_path(obj).get_asset() sys_path = unreal.SystemLibrary.get_system_path(asset) From 20227c686d5339968b3f1e3c4fc8119b0dd8a8df Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Feb 2023 12:18:03 +0000 Subject: [PATCH 080/428] Improved attributes for the creators --- openpype/hosts/unreal/api/plugin.py | 20 +++++++++++++------ .../unreal/plugins/create/create_render.py | 6 ++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index da571af9be..7121aea20b 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -14,7 +14,10 @@ from .pipeline import ( lsinst, UNREAL_VERSION ) -from openpype.lib import BoolDef +from openpype.lib import ( + BoolDef, + UILabelDef +) from openpype.pipeline import ( Creator, LoaderPlugin, @@ -143,11 +146,6 @@ class UnrealBaseCreator(Creator): self._remove_instance_from_context(instance) - def get_pre_create_attr_defs(self): - return [ - BoolDef("use_selection", label="Use selection", default=True) - ] - @six.add_metaclass(ABCMeta) class UnrealAssetCreator(UnrealBaseCreator): @@ -187,6 +185,11 @@ class UnrealAssetCreator(UnrealBaseCreator): OpenPypeCreatorError(f"Creator error: {er}"), sys.exc_info()[2]) + def get_pre_create_attr_defs(self): + return [ + BoolDef("use_selection", label="Use selection", default=True) + ] + @six.add_metaclass(ABCMeta) class UnrealActorCreator(UnrealBaseCreator): @@ -239,6 +242,11 @@ class UnrealActorCreator(UnrealBaseCreator): OpenPypeCreatorError(f"Creator error: {er}"), sys.exc_info()[2]) + def get_pre_create_attr_defs(self): + return [ + UILabelDef("Select actors to create instance from them.") + ] + class Loader(LoaderPlugin, ABC): """This serves as skeleton for future OpenPype specific functionality""" diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index de3efdad74..8100a5016c 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -7,6 +7,7 @@ from openpype.hosts.unreal.api.pipeline import ( from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator, ) +from openpype.lib import UILabelDef class CreateRender(UnrealAssetCreator): @@ -129,3 +130,8 @@ class CreateRender(UnrealAssetCreator): subset_name, instance_data, pre_create_data) + + def get_pre_create_attr_defs(self): + return [ + UILabelDef("Select the sequence to render.") + ] From 65e08973fe423c5f456a5a9654fc59d711e06adb Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Feb 2023 16:15:03 +0000 Subject: [PATCH 081/428] Fix render creator problem with selection --- .../hosts/unreal/plugins/create/create_render.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index 8100a5016c..a1e3e43a78 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -6,6 +6,7 @@ from openpype.hosts.unreal.api.pipeline import ( ) from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator, + OpenPypeCreatorError ) from openpype.lib import UILabelDef @@ -21,13 +22,13 @@ class CreateRender(UnrealAssetCreator): def create(self, subset_name, instance_data, pre_create_data): ar = unreal.AssetRegistryHelpers.get_asset_registry() - if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() - selection = [ - a.get_path_name() for a in sel_objects - if a.get_class().get_name() == "LevelSequence"] - else: - selection = [instance_data['sequence']] + sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + selection = [ + a.get_path_name() for a in sel_objects + if a.get_class().get_name() == "LevelSequence"] + + if len(selection) == 0: + raise RuntimeError("Please select at least one Level Sequence.") seq_data = None From 106f9ca2bb750ebed02016264e3f46b199aa494f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 2 Feb 2023 16:17:23 +0000 Subject: [PATCH 082/428] Hound fixes --- openpype/hosts/unreal/plugins/create/create_render.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index a1e3e43a78..c957e50e29 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -5,8 +5,7 @@ from openpype.hosts.unreal.api.pipeline import ( get_subsequences ) from openpype.hosts.unreal.api.plugin import ( - UnrealAssetCreator, - OpenPypeCreatorError + UnrealAssetCreator ) from openpype.lib import UILabelDef From 8e30e565fdefb3a567567cd9651182eb0da2f68d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 15 Feb 2023 11:41:57 +0000 Subject: [PATCH 083/428] Implemented suggestions from review --- openpype/hosts/unreal/api/pipeline.py | 3 - openpype/hosts/unreal/api/plugin.py | 69 +++++++------------ .../unreal/plugins/create/create_camera.py | 2 +- .../unreal/plugins/create/create_look.py | 14 ++-- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 7a21effcbc..0fe8c02ec5 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -69,9 +69,6 @@ class UnrealHost(HostBase, ILoadHost, IPublishHost): op_ctx = content_path + CONTEXT_CONTAINER with open(op_ctx, "w+") as f: json.dump(data, f) - with open(op_ctx, "r") as fp: - test = eval(json.load(fp)) - unreal.log_warning(test) def get_context_data(self): content_path = unreal.Paths.project_content_dir() diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index 7121aea20b..fc724105b6 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- +import collections import sys import six -from abc import ( - ABC, - ABCMeta, -) +from abc import ABC import unreal @@ -26,11 +24,6 @@ from openpype.pipeline import ( ) -class OpenPypeCreatorError(CreatorError): - pass - - -@six.add_metaclass(ABCMeta) class UnrealBaseCreator(Creator): """Base class for Unreal creator plugins.""" root = "/Game/OpenPype/PublishInstances" @@ -56,28 +49,20 @@ class UnrealBaseCreator(Creator): """ if shared_data.get("unreal_cached_subsets") is None: - shared_data["unreal_cached_subsets"] = {} - if shared_data.get("unreal_cached_legacy_subsets") is None: - shared_data["unreal_cached_legacy_subsets"] = {} - cached_instances = lsinst() - for i in cached_instances: - if not i.get("creator_identifier"): - # we have legacy instance - family = i.get("family") - if (family not in - shared_data["unreal_cached_legacy_subsets"]): - shared_data[ - "unreal_cached_legacy_subsets"][family] = [i] - else: - shared_data[ - "unreal_cached_legacy_subsets"][family].append(i) - continue - - creator_id = i.get("creator_identifier") - if creator_id not in shared_data["unreal_cached_subsets"]: - shared_data["unreal_cached_subsets"][creator_id] = [i] + unreal_cached_subsets = collections.defaultdict(list) + unreal_cached_legacy_subsets = collections.defaultdict(list) + for instance in lsinst(): + creator_id = instance.get("creator_identifier") + if creator_id: + unreal_cached_subsets[creator_id].append(instance) else: - shared_data["unreal_cached_subsets"][creator_id].append(i) + family = instance.get("family") + unreal_cached_legacy_subsets[family].append(instance) + + shared_data["unreal_cached_subsets"] = unreal_cached_subsets + shared_data["unreal_cached_legacy_subsets"] = ( + unreal_cached_legacy_subsets + ) return shared_data def create(self, subset_name, instance_data, pre_create_data): @@ -108,8 +93,8 @@ class UnrealBaseCreator(Creator): except Exception as er: six.reraise( - OpenPypeCreatorError, - OpenPypeCreatorError(f"Creator error: {er}"), + CreatorError, + CreatorError(f"Creator error: {er}"), sys.exc_info()[2]) def collect_instances(self): @@ -121,17 +106,17 @@ class UnrealBaseCreator(Creator): self._add_instance_to_context(created_instance) def update_instances(self, update_list): - unreal.log_warning(f"Update instances: {update_list}") - for created_inst, _changes in update_list: + for created_inst, changes in update_list: instance_node = created_inst.get("instance_path", "") if not instance_node: unreal.log_warning( f"Instance node not found for {created_inst}") + continue new_values = { - key: new_value - for key, (_old_value, new_value) in _changes.items() + key: changes[key].new_value + for key in changes.changed_keys } imprint( instance_node, @@ -147,7 +132,6 @@ class UnrealBaseCreator(Creator): self._remove_instance_from_context(instance) -@six.add_metaclass(ABCMeta) class UnrealAssetCreator(UnrealBaseCreator): """Base class for Unreal creator plugins based on assets.""" @@ -181,8 +165,8 @@ class UnrealAssetCreator(UnrealBaseCreator): except Exception as er: six.reraise( - OpenPypeCreatorError, - OpenPypeCreatorError(f"Creator error: {er}"), + CreatorError, + CreatorError(f"Creator error: {er}"), sys.exc_info()[2]) def get_pre_create_attr_defs(self): @@ -191,7 +175,6 @@ class UnrealAssetCreator(UnrealBaseCreator): ] -@six.add_metaclass(ABCMeta) class UnrealActorCreator(UnrealBaseCreator): """Base class for Unreal creator plugins based on actors.""" @@ -214,7 +197,7 @@ class UnrealActorCreator(UnrealBaseCreator): # Check if the level is saved if world.get_path_name().startswith("/Temp/"): - raise OpenPypeCreatorError( + raise CreatorError( "Level must be saved before creating instances.") # Check if instance data has members, filled by the plugin. @@ -238,8 +221,8 @@ class UnrealActorCreator(UnrealBaseCreator): except Exception as er: six.reraise( - OpenPypeCreatorError, - OpenPypeCreatorError(f"Creator error: {er}"), + CreatorError, + CreatorError(f"Creator error: {er}"), sys.exc_info()[2]) def get_pre_create_attr_defs(self): diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index 239dc87db5..00815e1ed4 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -10,4 +10,4 @@ class CreateCamera(UnrealActorCreator): identifier = "io.openpype.creators.unreal.camera" label = "Camera" family = "camera" - icon = "camera" + icon = "fa.camera" diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py index 047764ef2a..cecb88bca3 100644 --- a/openpype/hosts/unreal/plugins/create/create_look.py +++ b/openpype/hosts/unreal/plugins/create/create_look.py @@ -7,6 +7,7 @@ from openpype.hosts.unreal.api.pipeline import ( from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator ) +from openpype.lib import UILabelDef class CreateLook(UnrealAssetCreator): @@ -18,10 +19,10 @@ class CreateLook(UnrealAssetCreator): icon = "paint-brush" def create(self, subset_name, instance_data, pre_create_data): - selection = [] - if pre_create_data.get("use_selection"): - sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() - selection = [a.get_path_name() for a in sel_objects] + # We need to set this to True for the parent class to work + pre_create_data["use_selection"] = True + sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() + selection = [a.get_path_name() for a in sel_objects] if len(selection) != 1: raise RuntimeError("Please select only one asset.") @@ -68,3 +69,8 @@ class CreateLook(UnrealAssetCreator): subset_name, instance_data, pre_create_data) + + def get_pre_create_attr_defs(self): + return [ + UILabelDef("Select the asset from which to create the look.") + ] From fa3a7419409598ba0b3b2c9cb42d1c42be20822b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 15 Feb 2023 11:45:30 +0000 Subject: [PATCH 084/428] Fixed problem with the instance metadata --- openpype/hosts/unreal/api/pipeline.py | 2 +- openpype/hosts/unreal/api/plugin.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 0fe8c02ec5..0810ec7c07 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -76,7 +76,7 @@ class UnrealHost(HostBase, ILoadHost, IPublishHost): if not os.path.isfile(op_ctx): return {} with open(op_ctx, "r") as fp: - data = eval(json.load(fp)) + data = json.load(fp) return data diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index fc724105b6..a852ed9bb1 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import ast import collections import sys import six @@ -89,7 +90,9 @@ class UnrealBaseCreator(Creator): obj = ar.get_asset_by_object_path(member).get_asset() assets.add(obj) - imprint(f"{self.root}/{instance_name}", instance_data) + imprint(f"{self.root}/{instance_name}", instance.data_to_store()) + + return instance except Exception as er: six.reraise( @@ -102,6 +105,11 @@ class UnrealBaseCreator(Creator): self.cache_subsets(self.collection_shared_data) for instance in self.collection_shared_data[ "unreal_cached_subsets"].get(self.identifier, []): + # Unreal saves metadata as string, so we need to convert it back + instance['creator_attributes'] = ast.literal_eval( + instance.get('creator_attributes', '{}')) + instance['publish_attributes'] = ast.literal_eval( + instance.get('publish_attributes', '{}')) created_instance = CreatedInstance.from_existing(instance, self) self._add_instance_to_context(created_instance) From 614bcb320c3a6bde5e717000065b5c17088ccdc6 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 15 Feb 2023 16:56:21 +0000 Subject: [PATCH 085/428] Creator allows to create a new level sequence with render instance --- .../unreal/plugins/create/create_render.py | 126 +++++++++++++++--- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index c957e50e29..bc39b43802 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -2,12 +2,17 @@ import unreal from openpype.hosts.unreal.api.pipeline import ( - get_subsequences + UNREAL_VERSION, + create_folder, + get_subsequences, ) from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator ) -from openpype.lib import UILabelDef +from openpype.lib import ( + BoolDef, + UILabelDef +) class CreateRender(UnrealAssetCreator): @@ -18,7 +23,88 @@ class CreateRender(UnrealAssetCreator): family = "render" icon = "eye" - def create(self, subset_name, instance_data, pre_create_data): + def create_instance( + self, instance_data, subset_name, pre_create_data, + selected_asset_path, master_seq, master_lvl, seq_data + ): + instance_data["members"] = [selected_asset_path] + instance_data["sequence"] = selected_asset_path + instance_data["master_sequence"] = master_seq + instance_data["master_level"] = master_lvl + instance_data["output"] = seq_data.get('output') + instance_data["frameStart"] = seq_data.get('frame_range')[0] + instance_data["frameEnd"] = seq_data.get('frame_range')[1] + + super(CreateRender, self).create( + subset_name, + instance_data, + pre_create_data) + + def create_with_new_sequence( + self, subset_name, instance_data, pre_create_data + ): + # If the option to create a new level sequence is selected, + # create a new level sequence and a master level. + + root = f"/Game/OpenPype/Sequences" + + # Create a new folder for the sequence in root + sequence_dir_name = create_folder(root, subset_name) + sequence_dir = f"{root}/{sequence_dir_name}" + + unreal.log_warning(f"sequence_dir: {sequence_dir}") + + # Create the level sequence + asset_tools = unreal.AssetToolsHelpers.get_asset_tools() + seq = asset_tools.create_asset( + asset_name=subset_name, + package_path=sequence_dir, + asset_class=unreal.LevelSequence, + factory=unreal.LevelSequenceFactoryNew()) + unreal.EditorAssetLibrary.save_asset(seq.get_path_name()) + + # Create the master level + prev_level = None + if UNREAL_VERSION.major >= 5: + curr_level = unreal.LevelEditorSubsystem().get_current_level() + else: + world = unreal.EditorLevelLibrary.get_editor_world() + levels = unreal.EditorLevelUtils.get_levels(world) + curr_level = levels[0] if len(levels) else None + if not curr_level: + raise RuntimeError("No level loaded.") + curr_level_path = curr_level.get_outer().get_path_name() + + # If the level path does not start with "/Game/", the current + # level is a temporary, unsaved level. + if curr_level_path.startswith("/Game/"): + prev_level = curr_level_path + if UNREAL_VERSION.major >= 5: + unreal.LevelEditorSubsystem().save_current_level() + else: + unreal.EditorLevelLibrary.save_current_level() + + ml_path = f"{sequence_dir}/{subset_name}_MasterLevel" + + if UNREAL_VERSION.major >= 5: + unreal.LevelEditorSubsystem().new_level(ml_path) + else: + unreal.EditorLevelLibrary.new_level(ml_path) + + seq_data = { + "sequence": seq, + "output": f"{seq.get_name()}", + "frame_range": ( + seq.get_playback_start(), + seq.get_playback_end())} + + self.create_instance( + instance_data, subset_name, pre_create_data, + seq.get_path_name(), seq.get_path_name(), ml_path, seq_data) + + def create_from_existing_sequence( + self, subset_name, instance_data, pre_create_data + ): ar = unreal.AssetRegistryHelpers.get_asset_registry() sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() @@ -118,20 +204,30 @@ class CreateRender(UnrealAssetCreator): "sub-sequence of the master sequence.") continue - instance_data["members"] = [selected_asset_path] - instance_data["sequence"] = selected_asset_path - instance_data["master_sequence"] = master_seq - instance_data["master_level"] = master_lvl - instance_data["output"] = seq_data.get('output') - instance_data["frameStart"] = seq_data.get('frame_range')[0] - instance_data["frameEnd"] = seq_data.get('frame_range')[1] + self.create_instance( + instance_data, subset_name, pre_create_data, + selected_asset_path, master_seq, master_lvl, seq_data) - super(CreateRender, self).create( - subset_name, - instance_data, - pre_create_data) + def create(self, subset_name, instance_data, pre_create_data): + if pre_create_data.get("create_seq"): + self.create_with_new_sequence( + subset_name, instance_data, pre_create_data) + else: + self.create_from_existing_sequence( + subset_name, instance_data, pre_create_data) def get_pre_create_attr_defs(self): return [ - UILabelDef("Select the sequence to render.") + UILabelDef( + "Select a Level Sequence to render or create a new one." + ), + BoolDef( + "create_seq", + label="Create a new Level Sequence", + default=False + ), + UILabelDef( + "WARNING: If you create a new Level Sequence, the current " + "level will be saved and a new Master Level will be created." + ) ] From f94cae429e0cb7b056153211adec7fa7813b28f8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 16 Feb 2023 11:46:10 +0000 Subject: [PATCH 086/428] Allow the user to set frame range of new sequence --- .../unreal/plugins/create/create_render.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index bc39b43802..b999f9ae20 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -11,7 +11,7 @@ from openpype.hosts.unreal.api.plugin import ( ) from openpype.lib import ( BoolDef, - UILabelDef + NumberDef ) @@ -61,6 +61,10 @@ class CreateRender(UnrealAssetCreator): package_path=sequence_dir, asset_class=unreal.LevelSequence, factory=unreal.LevelSequenceFactoryNew()) + + seq.set_playback_start(pre_create_data.get("start_frame")) + seq.set_playback_end(pre_create_data.get("end_frame")) + unreal.EditorAssetLibrary.save_asset(seq.get_path_name()) # Create the master level @@ -229,5 +233,19 @@ class CreateRender(UnrealAssetCreator): UILabelDef( "WARNING: If you create a new Level Sequence, the current " "level will be saved and a new Master Level will be created." - ) + ), + NumberDef( + "start_frame", + label="Start Frame", + default=0, + minimum=-999999, + maximum=999999 + ), + NumberDef( + "end_frame", + label="Start Frame", + default=150, + minimum=-999999, + maximum=999999 + ), ] From e2ea7fad1a7f4d0aaec178dd18e76ffa18e3f3af Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 16 Feb 2023 11:47:26 +0000 Subject: [PATCH 087/428] Added option to not include hierarchy when creating a render instance --- .../unreal/plugins/create/create_render.py | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index b999f9ae20..6f2049693f 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from pathlib import Path + import unreal from openpype.hosts.unreal.api.pipeline import ( @@ -10,6 +12,8 @@ from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator ) from openpype.lib import ( + UILabelDef, + UISeparatorDef, BoolDef, NumberDef ) @@ -68,7 +72,6 @@ class CreateRender(UnrealAssetCreator): unreal.EditorAssetLibrary.save_asset(seq.get_path_name()) # Create the master level - prev_level = None if UNREAL_VERSION.major >= 5: curr_level = unreal.LevelEditorSubsystem().get_current_level() else: @@ -82,7 +85,6 @@ class CreateRender(UnrealAssetCreator): # If the level path does not start with "/Game/", the current # level is a temporary, unsaved level. if curr_level_path.startswith("/Game/"): - prev_level = curr_level_path if UNREAL_VERSION.major >= 5: unreal.LevelEditorSubsystem().save_current_level() else: @@ -131,25 +133,31 @@ class CreateRender(UnrealAssetCreator): f"Skipping {selected_asset.get_name()}. It isn't a Level " "Sequence.") - # The asset name is the the third element of the path which - # contains the map. - # To take the asset name, we remove from the path the prefix - # "/Game/OpenPype/" and then we split the path by "/". - sel_path = selected_asset_path - asset_name = sel_path.replace("/Game/OpenPype/", "").split("/")[0] + if pre_create_data.get("use_hierarchy"): + # The asset name is the the third element of the path which + # contains the map. + # To take the asset name, we remove from the path the prefix + # "/Game/OpenPype/" and then we split the path by "/". + sel_path = selected_asset_path + asset_name = sel_path.replace( + "/Game/OpenPype/", "").split("/")[0] + + search_path = f"/Game/OpenPype/{asset_name}" + else: + search_path = Path(selected_asset_path).parent.as_posix() # Get the master sequence and the master level. # There should be only one sequence and one level in the directory. ar_filter = unreal.ARFilter( class_names=["LevelSequence"], - package_paths=[f"/Game/OpenPype/{asset_name}"], + package_paths=[search_path], recursive_paths=False) sequences = ar.get_assets(ar_filter) master_seq = sequences[0].get_asset().get_path_name() master_seq_obj = sequences[0].get_asset() ar_filter = unreal.ARFilter( class_names=["World"], - package_paths=[f"/Game/OpenPype/{asset_name}"], + package_paths=[search_path], recursive_paths=False) levels = ar.get_assets(ar_filter) master_lvl = levels[0].get_asset().get_path_name() @@ -168,7 +176,8 @@ class CreateRender(UnrealAssetCreator): master_seq_obj.get_playback_start(), master_seq_obj.get_playback_end())} - if selected_asset_path == master_seq: + if (selected_asset_path == master_seq or + pre_create_data.get("use_hierarchy")): seq_data = master_seq_data else: seq_data_list = [master_seq_data] @@ -231,7 +240,7 @@ class CreateRender(UnrealAssetCreator): default=False ), UILabelDef( - "WARNING: If you create a new Level Sequence, the current " + "WARNING: If you create a new Level Sequence, the current\n" "level will be saved and a new Master Level will be created." ), NumberDef( @@ -248,4 +257,14 @@ class CreateRender(UnrealAssetCreator): minimum=-999999, maximum=999999 ), + UISeparatorDef(), + UILabelDef( + "The following settings are valid only if you are not\n" + "creating a new sequence." + ), + BoolDef( + "use_hierarchy", + label="Use Hierarchy", + default=False + ), ] From d2403bcbdace79d8e645b6dbd68e439dbb144e03 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 16 Feb 2023 12:00:48 +0000 Subject: [PATCH 088/428] Hanldes IndexError when looking for hierarchy for selected sequence --- .../unreal/plugins/create/create_render.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index 6f2049693f..b2a246d3a8 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -148,19 +148,23 @@ class CreateRender(UnrealAssetCreator): # Get the master sequence and the master level. # There should be only one sequence and one level in the directory. - ar_filter = unreal.ARFilter( - class_names=["LevelSequence"], - package_paths=[search_path], - recursive_paths=False) - sequences = ar.get_assets(ar_filter) - master_seq = sequences[0].get_asset().get_path_name() - master_seq_obj = sequences[0].get_asset() - ar_filter = unreal.ARFilter( - class_names=["World"], - package_paths=[search_path], - recursive_paths=False) - levels = ar.get_assets(ar_filter) - master_lvl = levels[0].get_asset().get_path_name() + try: + ar_filter = unreal.ARFilter( + class_names=["LevelSequence"], + package_paths=[search_path], + recursive_paths=False) + sequences = ar.get_assets(ar_filter) + master_seq = sequences[0].get_asset().get_path_name() + master_seq_obj = sequences[0].get_asset() + ar_filter = unreal.ARFilter( + class_names=["World"], + package_paths=[search_path], + recursive_paths=False) + levels = ar.get_assets(ar_filter) + master_lvl = levels[0].get_asset().get_path_name() + except IndexError: + raise RuntimeError( + f"Could not find the hierarchy for the selected sequence.") # If the selected asset is the master sequence, we get its data # and then we create the instance for the master sequence. From a31b6035fe81ff0fe71b335fbd96e6c6f8e5ab9e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Mar 2023 17:18:05 +0800 Subject: [PATCH 089/428] add model creator, extractors and loaders --- .../hosts/max/plugins/create/create_model.py | 26 +++++ .../hosts/max/plugins/load/load_max_scene.py | 3 +- openpype/hosts/max/plugins/load/load_model.py | 98 +++++++++++++++++++ .../hosts/max/plugins/load/load_pointcache.py | 3 +- .../plugins/publish/extract_max_scene_raw.py | 3 +- .../max/plugins/publish/extract_model.py | 74 ++++++++++++++ 6 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_model.py create mode 100644 openpype/hosts/max/plugins/load/load_model.py create mode 100644 openpype/hosts/max/plugins/publish/extract_model.py diff --git a/openpype/hosts/max/plugins/create/create_model.py b/openpype/hosts/max/plugins/create/create_model.py new file mode 100644 index 0000000000..a78a30e0c7 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_model.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for model.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance + + +class CreateModel(plugin.MaxCreator): + identifier = "io.openpype.creators.max.model" + label = "Model" + family = "model" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + sel_obj = list(rt.selection) + instance = super(CreateModel, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + container = rt.getNodeByName(instance.data.get("instance_node")) + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + for obj in sel_obj: + obj.parent = container + # for additional work on the node: + # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/load/load_max_scene.py b/openpype/hosts/max/plugins/load/load_max_scene.py index b863b9363f..d37d3439fb 100644 --- a/openpype/hosts/max/plugins/load/load_max_scene.py +++ b/openpype/hosts/max/plugins/load/load_max_scene.py @@ -9,7 +9,8 @@ from openpype.hosts.max.api import lib class MaxSceneLoader(load.LoaderPlugin): """Max Scene Loader""" - families = ["camera"] + families = ["camera", + "model"] representations = ["max"] order = -8 icon = "code-fork" diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py new file mode 100644 index 0000000000..e6262b4f86 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -0,0 +1,98 @@ + +import os +from openpype.pipeline import ( + load, get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class ModelAbcLoader(load.LoaderPlugin): + """Loading model with the Alembic loader.""" + + families = ["model"] + label = "Load Model(Alembic)" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + file_path = os.path.normpath(self.fname) + + abc_before = { + c for c in rt.rootNode.Children + if rt.classOf(c) == rt.AlembicContainer + } + + abc_import_cmd = (f""" +AlembicImport.ImportToRoot = false +AlembicImport.CustomAttributes = true +AlembicImport.UVs = true +AlembicImport.VertexColors = true + +importFile @"{file_path}" #noPrompt + """) + + self.log.debug(f"Executing command: {abc_import_cmd}") + rt.execute(abc_import_cmd) + + abc_after = { + c for c in rt.rootNode.Children + if rt.classOf(c) == rt.AlembicContainer + } + + # This should yield new AlembicContainer node + abc_containers = abc_after.difference(abc_before) + + if len(abc_containers) != 1: + self.log.error("Something failed when loading.") + + abc_container = abc_containers.pop() + + return containerise( + name, [abc_container], context, loader=self.__class__.__name__) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + + alembic_objects = self.get_container_children(node, "AlembicObject") + for alembic_object in alembic_objects: + alembic_object.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) + + @staticmethod + def get_container_children(parent, type_name): + from pymxs import runtime as rt + + def list_children(node): + children = [] + for c in node.Children: + children.append(c) + children += list_children(c) + return children + + filtered = [] + for child in list_children(parent): + class_type = str(rt.classOf(child.baseObject)) + if class_type == type_name: + filtered.append(child) + + return filtered diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index f7a72ece25..b3e12adc7b 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -15,8 +15,7 @@ from openpype.hosts.max.api import lib class AbcLoader(load.LoaderPlugin): """Alembic loader.""" - families = ["model", - "camera", + families = ["camera", "animation", "pointcache"] label = "Load Alembic" diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index cacc84c591..aa01ad1a3a 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -20,7 +20,8 @@ class ExtractMaxSceneRaw(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.2 label = "Extract Max Scene (Raw)" hosts = ["max"] - families = ["camera"] + families = ["camera", + "model"] optional = True def process(self, instance): diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py new file mode 100644 index 0000000000..710ad5f97d --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -0,0 +1,74 @@ +import os +import pyblish.api +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api import ( + maintained_selection, + get_all_children +) + + +class ExtractModel(publish.Extractor, + OptionalPyblishPluginMixin): + """ + Extract Geometry in Alembic Format + """ + + order = pyblish.api.ExtractorOrder - 0.1 + label = "Extract Geometry (Alembic)" + hosts = ["max"] + families = ["model"] + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + + container = instance.data["instance_node"] + + self.log.info("Extracting Geometry ...") + + stagingdir = self.staging_dir(instance) + filename = "{name}.abc".format(**instance.data) + filepath = os.path.join(stagingdir, filename) + + # We run the render + self.log.info("Writing alembic '%s' to '%s'" % (filename, + stagingdir)) + + export_cmd = ( + f""" +AlembicExport.ArchiveType = #ogawa +AlembicExport.CoordinateSystem = #maya +AlembicExport.CustomAttributes = true +AlembicExport.UVs = true +AlembicExport.VertexColors = true +AlembicExport.PreserveInstances = true + +exportFile @"{filepath}" #noPrompt selectedOnly:on using:AlembicExport + + """) + + self.log.debug(f"Executing command: {export_cmd}") + + with maintained_selection(): + # select and export + rt.select(get_all_children(rt.getNodeByName(container))) + rt.execute(export_cmd) + + self.log.info("Performing Extraction ...") + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'abc', + 'ext': 'abc', + 'files': filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(representation) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + filepath)) From f18455717c95b67846558e59a785143961d5fc58 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Mar 2023 17:25:45 +0800 Subject: [PATCH 090/428] OP-4245 - Data Exchange: geometry --- .../max/plugins/publish/extract_model_usd.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/extract_model_usd.py diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py new file mode 100644 index 0000000000..1c8bf073da --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -0,0 +1,112 @@ +import os +import pyblish.api +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api import ( + maintained_selection, + get_all_children +) + + +class ExtractModelUSD(publish.Extractor, + OptionalPyblishPluginMixin): + """ + Extract Geometry in USDA Format + """ + + order = pyblish.api.ExtractorOrder - 0.05 + label = "Extract Geometry (USD)" + hosts = ["max"] + families = ["model"] + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + + container = instance.data["instance_node"] + + self.log.info("Extracting Geometry ...") + + stagingdir = self.staging_dir(instance) + asset_filename = "{name}.usda".format(**instance.data) + asset_filepath = os.path.join(stagingdir, + asset_filename) + self.log.info("Writing USD '%s' to '%s'" % (asset_filepath, + stagingdir)) + + log_filename ="{name}.txt".format(**instance.data) + log_filepath = os.path.join(stagingdir, + log_filename) + self.log.info("Writing log '%s' to '%s'" % (log_filepath, + stagingdir)) + + # get the nodes which need to be exported + export_options = self.get_export_options(log_filepath) + with maintained_selection(): + # select and export + node_list = self.get_node_list(container) + rt.USDExporter.ExportFile(asset_filepath, + exportOptions=export_options, + nodeList=node_list) + + self.log.info("Performing Extraction ...") + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'usda', + 'ext': 'usda', + 'files': asset_filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(representation) + + log_representation = { + 'name': 'txt', + 'ext': 'txt', + 'files': log_filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(log_representation) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + asset_filepath)) + + def get_node_list(self, container): + """ + Get the target nodes which are + the children of the container + """ + node_list = [] + + container_node = rt.getNodeByName(container) + target_node = container_node.Children + rt.select(target_node) + for sel in rt.selection: + node_list.append(sel) + + return node_list + + def get_export_options(self, log_path): + """Set Export Options for USD Exporter""" + + export_options = rt.USDExporter.createOptions() + + export_options.Meshes = True + export_options.Lights = False + export_options.Cameras = False + export_options.Materials = False + export_options.FileFormat = rt.name('ascii') + export_options.UpAxis = rt.name('y') + export_options.LogLevel = rt.name('info') + export_options.LogPath = log_path + export_options.PreserveEdgeOrientation = True + export_options.TimeMode = rt.name('current') + + rt.USDexporter.UIOptions = export_options + + return export_options From a7c11f0aece3b0484d94b64e92955103fc5b93e2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 2 Mar 2023 17:30:25 +0800 Subject: [PATCH 091/428] hound fix --- openpype/hosts/max/plugins/publish/extract_model_usd.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 1c8bf073da..0f8d283907 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -38,7 +37,7 @@ class ExtractModelUSD(publish.Extractor, self.log.info("Writing USD '%s' to '%s'" % (asset_filepath, stagingdir)) - log_filename ="{name}.txt".format(**instance.data) + log_filename = "{name}.txt".format(**instance.data) log_filepath = os.path.join(stagingdir, log_filename) self.log.info("Writing log '%s' to '%s'" % (log_filepath, From b5d748f466858557d09680923a30f1851cc8e6a2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 13:02:24 +0800 Subject: [PATCH 092/428] add export options to the usd extractors and add usd loader --- .../hosts/max/plugins/load/load_model_usd.py | 59 +++++++++++++++++++ .../max/plugins/publish/extract_model_usd.py | 2 + 2 files changed, 61 insertions(+) create mode 100644 openpype/hosts/max/plugins/load/load_model_usd.py diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py new file mode 100644 index 0000000000..c6c414b91c --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -0,0 +1,59 @@ +import os +from openpype.pipeline import ( + load, get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class ModelUSDLoader(load.LoaderPlugin): + """Loading model with the USD loader.""" + + families = ["model"] + label = "Load Model(USD)" + representations = ["usda"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + # asset_filepath + filepath = os.path.normpath(self.fname) + import_options = rt.USDImporter.CreateOptions() + base_filename = os.path.basename(filepath) + filename, ext = os.path.splitext(base_filename) + log_filepath = filepath.replace(ext, "txt") + + rt.LogPath = log_filepath + rt.LogLevel = rt.name('info') + rt.USDImporter.importFile(filepath, + importOptions=import_options) + + asset = rt.getNodeByName(f"{name}") + + return containerise( + name, [asset], context, loader=self.__class__.__name__) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + + usd_objects = self.get_container_children(node) + for usd_object in usd_objects: + usd_object.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 0f8d283907..2f89e4de16 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -96,9 +96,11 @@ class ExtractModelUSD(publish.Extractor, export_options = rt.USDExporter.createOptions() export_options.Meshes = True + export_options.Shapes = True export_options.Lights = False export_options.Cameras = False export_options.Materials = False + export_options.MeshFormat = rt.name('fromScene') export_options.FileFormat = rt.name('ascii') export_options.UpAxis = rt.name('y') export_options.LogLevel = rt.name('info') From 519cef018529e17fb94c7c8bb197885c762ede93 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 13:34:35 +0800 Subject: [PATCH 093/428] add validator for model family --- .../max/plugins/publish/extract_model_usd.py | 2 +- .../publish/validate_model_contents.py | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/max/plugins/publish/validate_model_contents.py diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 2f89e4de16..b20fd45eae 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -96,7 +96,7 @@ class ExtractModelUSD(publish.Extractor, export_options = rt.USDExporter.createOptions() export_options.Meshes = True - export_options.Shapes = True + export_options.Shapes = False export_options.Lights = False export_options.Cameras = False export_options.Materials = False diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py new file mode 100644 index 0000000000..01ae869c30 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from openpype.pipeline import PublishValidationError +from pymxs import runtime as rt + + +class ValidateModelContent(pyblish.api.InstancePlugin): + """Validates Model instance contents. + + A model instance may only hold either geometry + or editable meshes. + """ + + order = pyblish.api.ValidatorOrder + families = ["model"] + hosts = ["max"] + label = "Model Contents" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError("Model instance must only include" + "Geometry and Editable Mesh") + + def get_invalid(self, instance): + """ + Get invalid nodes if the instance is not camera + """ + invalid = list() + container = instance.data["instance_node"] + self.log.info("Validating look content for " + "{}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + for sel in selection_list: + if rt.classOf(sel) in rt.Camera.classes: + invalid.append(sel) + if rt.classOf(sel) in rt.Light.classes: + invalid.append(sel) + if rt.classOf(sel) in rt.Shape.classes: + invalid.append(sel) + + return invalid From 12211d70371354fafad96f980d05743542be6c5e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 13:35:52 +0800 Subject: [PATCH 094/428] add info in docstring for the validator --- openpype/hosts/max/plugins/publish/validate_model_contents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index 01ae869c30..dd9c8de2cf 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -7,8 +7,8 @@ from pymxs import runtime as rt class ValidateModelContent(pyblish.api.InstancePlugin): """Validates Model instance contents. - A model instance may only hold either geometry - or editable meshes. + A model instance may only hold either geometry-related + object(excluding Shapes) or editable meshes. """ order = pyblish.api.ValidatorOrder From c98160691b9e1273de8294ad1080792e8080c8a5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 16:01:22 +0800 Subject: [PATCH 095/428] add usdmodel as families --- .../max/plugins/create/create_model_usd.py | 22 +++++++++++++++++++ .../hosts/max/plugins/load/load_model_usd.py | 2 +- .../max/plugins/publish/extract_model_usd.py | 2 +- .../publish/validate_model_contents.py | 2 +- openpype/plugins/publish/integrate.py | 1 + openpype/plugins/publish/integrate_legacy.py | 1 + 6 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_model_usd.py diff --git a/openpype/hosts/max/plugins/create/create_model_usd.py b/openpype/hosts/max/plugins/create/create_model_usd.py new file mode 100644 index 0000000000..237ae8f4ae --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_model_usd.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for model exported in USD format.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance + + +class CreateUSDModel(plugin.MaxCreator): + identifier = "io.openpype.creators.max.usdmodel" + label = "USD Model" + family = "usdmodel" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + _ = super(CreateUSDModel, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + # for additional work on the node: + # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py index c6c414b91c..ac318fbb57 100644 --- a/openpype/hosts/max/plugins/load/load_model_usd.py +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -9,7 +9,7 @@ from openpype.hosts.max.api import lib class ModelUSDLoader(load.LoaderPlugin): """Loading model with the USD loader.""" - families = ["model"] + families = ["usdmodel"] label = "Load Model(USD)" representations = ["usda"] order = -10 diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index b20fd45eae..e0ad3bb23e 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -19,7 +19,7 @@ class ExtractModelUSD(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.05 label = "Extract Geometry (USD)" hosts = ["max"] - families = ["model"] + families = ["usdmodel"] optional = True def process(self, instance): diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index dd9c8de2cf..34578e6920 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -12,7 +12,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["model"] + families = ["model", "usdmodel"] hosts = ["max"] label = "Model Contents" diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index b117006871..fc098b416a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -124,6 +124,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "xgen", "hda", "usd", + "usdmodel", "staticMesh", "skeletalMesh", "mvLook", diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b93abab1d8..ba32c376d8 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -120,6 +120,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "xgen", "hda", "usd", + "usdmodel", "staticMesh", "skeletalMesh", "mvLook", From fd6aa8302eee6cfcb44cb4d80f30466cd994485d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 16:02:24 +0800 Subject: [PATCH 096/428] add usdmodel as families --- openpype/hosts/max/plugins/create/create_model_usd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_model_usd.py b/openpype/hosts/max/plugins/create/create_model_usd.py index 237ae8f4ae..21407ae1f3 100644 --- a/openpype/hosts/max/plugins/create/create_model_usd.py +++ b/openpype/hosts/max/plugins/create/create_model_usd.py @@ -11,7 +11,6 @@ class CreateUSDModel(plugin.MaxCreator): icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt _ = super(CreateUSDModel, self).create( subset_name, instance_data, From 5b4eff51acd3fdb3b6700fa154986fc34cb022a6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 3 Mar 2023 20:30:05 +0800 Subject: [PATCH 097/428] include only model family --- openpype/hosts/max/plugins/publish/validate_model_contents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index 34578e6920..dd9c8de2cf 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -12,7 +12,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["model", "usdmodel"] + families = ["model"] hosts = ["max"] label = "Model Contents" From 1511ddbccf7f89f5ce90d934536d8a5d1b0eeb71 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 Mar 2023 16:22:17 +0800 Subject: [PATCH 098/428] usdmodel extractor with selected node and remove usdmodel family --- .../max/plugins/create/create_model_usd.py | 21 ------------------- .../hosts/max/plugins/load/load_model_usd.py | 2 +- .../max/plugins/publish/extract_model_usd.py | 3 ++- openpype/plugins/publish/integrate.py | 1 - openpype/plugins/publish/integrate_legacy.py | 1 - 5 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 openpype/hosts/max/plugins/create/create_model_usd.py diff --git a/openpype/hosts/max/plugins/create/create_model_usd.py b/openpype/hosts/max/plugins/create/create_model_usd.py deleted file mode 100644 index 21407ae1f3..0000000000 --- a/openpype/hosts/max/plugins/create/create_model_usd.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -"""Creator plugin for model exported in USD format.""" -from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance - - -class CreateUSDModel(plugin.MaxCreator): - identifier = "io.openpype.creators.max.usdmodel" - label = "USD Model" - family = "usdmodel" - icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - _ = super(CreateUSDModel, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py index ac318fbb57..c6c414b91c 100644 --- a/openpype/hosts/max/plugins/load/load_model_usd.py +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -9,7 +9,7 @@ from openpype.hosts.max.api import lib class ModelUSDLoader(load.LoaderPlugin): """Loading model with the USD loader.""" - families = ["usdmodel"] + families = ["model"] label = "Load Model(USD)" representations = ["usda"] order = -10 diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index e0ad3bb23e..0bed2d855e 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -19,7 +19,7 @@ class ExtractModelUSD(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.05 label = "Extract Geometry (USD)" hosts = ["max"] - families = ["usdmodel"] + families = ["model"] optional = True def process(self, instance): @@ -50,6 +50,7 @@ class ExtractModelUSD(publish.Extractor, node_list = self.get_node_list(container) rt.USDExporter.ExportFile(asset_filepath, exportOptions=export_options, + contentSource=rt.name("selected"), nodeList=node_list) self.log.info("Performing Extraction ...") diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index fc098b416a..b117006871 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -124,7 +124,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "xgen", "hda", "usd", - "usdmodel", "staticMesh", "skeletalMesh", "mvLook", diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index ba32c376d8..b93abab1d8 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -120,7 +120,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "xgen", "hda", "usd", - "usdmodel", "staticMesh", "skeletalMesh", "mvLook", From 6064fa2d45ca59269cf101b6f19edcf557996f24 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 6 Mar 2023 11:09:49 +0000 Subject: [PATCH 099/428] Added settings for rendering --- .../settings/defaults/project_settings/unreal.json | 2 ++ .../schemas/projects_schema/schema_project_unreal.json | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 75cee11bd9..ff290ef254 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -11,6 +11,8 @@ }, "level_sequences_for_layouts": false, "delete_unmatched_assets": false, + "render_config_path": "", + "preroll_frames": 0, "project_setup": { "dev_mode": true } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 8988dd2ff0..40bbb40ccc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -32,6 +32,16 @@ "key": "delete_unmatched_assets", "label": "Delete assets that are not matched" }, + { + "type": "text", + "key": "render_config_path", + "label": "Render Config Path" + }, + { + "type": "number", + "key": "preroll_frames", + "label": "Pre-roll frames" + }, { "type": "dict", "collapsible": true, From 095c792ad229d23e0b0d2b5f4fa44eb0ae229862 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 6 Mar 2023 11:10:42 +0000 Subject: [PATCH 100/428] Uses settings for rendering --- openpype/hosts/unreal/api/rendering.py | 54 +++++++++++++++++--------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index 29e4747f6e..5ef4792000 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -2,6 +2,7 @@ import os import unreal +from openpype.settings import get_project_settings from openpype.pipeline import Anatomy from openpype.hosts.unreal.api import pipeline @@ -66,6 +67,13 @@ def start_rendering(): ar = unreal.AssetRegistryHelpers.get_asset_registry() + data = get_project_settings(project) + config = None + config_path = str(data.get("unreal").get("render_config_path")) + if config_path and unreal.EditorAssetLibrary.does_asset_exist(config_path): + unreal.log("Found saved render configuration") + config = ar.get_asset_by_object_path(config_path).get_asset() + for i in inst_data: sequence = ar.get_asset_by_object_path(i["sequence"]).get_asset() @@ -81,47 +89,50 @@ def start_rendering(): # Get all the sequences to render. If there are subsequences, # add them and their frame ranges to the render list. We also # use the names for the output paths. - for s in sequences: - subscenes = pipeline.get_subsequences(s.get('sequence')) + for seq in sequences: + subscenes = pipeline.get_subsequences(seq.get('sequence')) if subscenes: - for ss in subscenes: + for sub_seq in subscenes: sequences.append({ - "sequence": ss.get_sequence(), - "output": (f"{s.get('output')}/" - f"{ss.get_sequence().get_name()}"), + "sequence": sub_seq.get_sequence(), + "output": (f"{seq.get('output')}/" + f"{sub_seq.get_sequence().get_name()}"), "frame_range": ( - ss.get_start_frame(), ss.get_end_frame()) + sub_seq.get_start_frame(), sub_seq.get_end_frame()) }) else: # Avoid rendering camera sequences - if "_camera" not in s.get('sequence').get_name(): - render_list.append(s) + if "_camera" not in seq.get('sequence').get_name(): + render_list.append(seq) # Create the rendering jobs and add them to the queue. - for r in render_list: + for render_setting in render_list: job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob) job.sequence = unreal.SoftObjectPath(i["master_sequence"]) job.map = unreal.SoftObjectPath(i["master_level"]) job.author = "OpenPype" + # If we have a saved configuration, copy it to the job. + if config: + job.get_configuration().copy_from(config) + # User data could be used to pass data to the job, that can be # read in the job's OnJobFinished callback. We could, # for instance, pass the AvalonPublishInstance's path to the job. # job.user_data = "" + output_dir = render_setting.get('output') + shot_name = render_setting.get('sequence').get_name() + settings = job.get_configuration().find_or_add_setting_by_class( unreal.MoviePipelineOutputSetting) settings.output_resolution = unreal.IntPoint(1920, 1080) - settings.custom_start_frame = r.get("frame_range")[0] - settings.custom_end_frame = r.get("frame_range")[1] + settings.custom_start_frame = render_setting.get("frame_range")[0] + settings.custom_end_frame = render_setting.get("frame_range")[1] settings.use_custom_playback_range = True - settings.file_name_format = "{sequence_name}.{frame_number}" - settings.output_directory.path = f"{render_dir}/{r.get('output')}" - - renderPass = job.get_configuration().find_or_add_setting_by_class( - unreal.MoviePipelineDeferredPassBase) - renderPass.disable_multisample_effects = True + settings.file_name_format = f"{shot_name}" + ".{frame_number}" + settings.output_directory.path = f"{render_dir}/{output_dir}" job.get_configuration().find_or_add_setting_by_class( unreal.MoviePipelineImageSequenceOutput_PNG) @@ -130,6 +141,13 @@ def start_rendering(): if queue.get_jobs(): global executor executor = unreal.MoviePipelinePIEExecutor() + + preroll_frames = data.get("unreal").get("preroll_frames", 0) + + settings = unreal.MoviePipelinePIEExecutorSettings() + settings.set_editor_property( + "initial_delay_frame_count", preroll_frames) + executor.on_executor_finished_delegate.add_callable_unique( _queue_finish_callback) executor.on_individual_job_finished_delegate.add_callable_unique( From 839d5834ca611c20f042c3036bcf422ce5ee32ce Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 8 Mar 2023 11:12:24 +0100 Subject: [PATCH 101/428] Fix merge problem --- openpype/hosts/unreal/plugins/create/create_camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index 33a0662d7d..642924e2d6 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -7,8 +7,6 @@ from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator, ) -class CreateCamera(UnrealActorCreator): - """Create Camera.""" class CreateCamera(UnrealAssetCreator): """Create Camera.""" From d8efd09797467cf1464d06c36b654f8ec3e02b17 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Thu, 9 Mar 2023 07:03:53 +0100 Subject: [PATCH 102/428] update the mesh format to poly mesh --- openpype/hosts/max/plugins/publish/extract_model_usd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 0bed2d855e..f70a14ba0b 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -101,7 +101,7 @@ class ExtractModelUSD(publish.Extractor, export_options.Lights = False export_options.Cameras = False export_options.Materials = False - export_options.MeshFormat = rt.name('fromScene') + export_options.MeshFormat = rt.name('polyMesh') export_options.FileFormat = rt.name('ascii') export_options.UpAxis = rt.name('y') export_options.LogLevel = rt.name('info') From 861d60ca0cd3144e75e8ddb135ce071d0b1b65ae Mon Sep 17 00:00:00 2001 From: moonyuet Date: Mon, 13 Mar 2023 11:31:42 +0100 Subject: [PATCH 103/428] fbx obj extractors and oaders --- .../hosts/max/plugins/load/load_camera_fbx.py | 2 - .../hosts/max/plugins/load/load_model_fbx.py | 62 ++++++++++++++++ .../hosts/max/plugins/load/load_model_obj.py | 56 ++++++++++++++ .../max/plugins/publish/extract_model_fbx.py | 74 +++++++++++++++++++ .../max/plugins/publish/extract_model_obj.py | 59 +++++++++++++++ 5 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/max/plugins/load/load_model_fbx.py create mode 100644 openpype/hosts/max/plugins/load/load_model_obj.py create mode 100644 openpype/hosts/max/plugins/publish/extract_model_fbx.py create mode 100644 openpype/hosts/max/plugins/publish/extract_model_obj.py diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 3a6947798e..205e815dc8 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -36,8 +36,6 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) - container_name = f"{name}_CON" - asset = rt.getNodeByName(f"{name}") return containerise( diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py new file mode 100644 index 0000000000..38b8555d28 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -0,0 +1,62 @@ +import os +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class FbxModelLoader(load.LoaderPlugin): + """Fbx Model Loader""" + + families = ["model"] + representations = ["fbx"] + order = -9 + icon = "code-fork" + color = "white" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + filepath = os.path.normpath(self.fname) + + fbx_import_cmd = ( + f""" + +FBXImporterSetParam "Animation" false +FBXImporterSetParam "Cameras" false +FBXImporterSetParam "AxisConversionMethod" true +FbxExporterSetParam "UpAxis" "Y" +FbxExporterSetParam "Preserveinstances" true + +importFile @"{filepath}" #noPrompt using:FBXIMP + """) + + self.log.debug(f"Executing command: {fbx_import_cmd}") + rt.execute(fbx_import_cmd) + + asset = rt.getNodeByName(f"{name}") + + return containerise( + name, [asset], context, loader=self.__class__.__name__) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + + fbx_objects = self.get_container_children(node) + for fbx_object in fbx_objects: + fbx_object.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) diff --git a/openpype/hosts/max/plugins/load/load_model_obj.py b/openpype/hosts/max/plugins/load/load_model_obj.py new file mode 100644 index 0000000000..06b411cb5c --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_model_obj.py @@ -0,0 +1,56 @@ +import os +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class ObjLoader(load.LoaderPlugin): + """Obj Loader""" + + families = ["model"] + representations = ["obj"] + order = -9 + icon = "code-fork" + color = "white" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + filepath = os.path.normpath(self.fname) + self.log.debug(f"Executing command to import..") + + rt.execute(f'importFile @"{filepath}" #noPrompt using:ObjImp') + # get current selection + for selection in rt.getCurrentSelection(): + # create "missing" container for obj import + container = rt.container() + container.name = f"{name}" + selection.Parent = container + + asset = rt.getNodeByName(f"{name}") + + return containerise( + name, [asset], context, loader=self.__class__.__name__) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + + objects = self.get_container_children(node) + for obj in objects: + obj.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py new file mode 100644 index 0000000000..ce58e8cc17 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -0,0 +1,74 @@ +import os +import pyblish.api +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api import ( + maintained_selection, + get_all_children +) + + +class ExtractModelFbx(publish.Extractor, + OptionalPyblishPluginMixin): + """ + Extract Geometry in FBX Format + """ + + order = pyblish.api.ExtractorOrder - 0.05 + label = "Extract FBX" + hosts = ["max"] + families = ["model"] + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + + container = instance.data["instance_node"] + + self.log.info("Extracting Geometry ...") + + stagingdir = self.staging_dir(instance) + filename = "{name}.fbx".format(**instance.data) + filepath = os.path.join(stagingdir, + filename) + self.log.info("Writing FBX '%s' to '%s'" % (filepath, + stagingdir)) + + export_fbx_cmd = ( + f""" +FBXExporterSetParam "Animation" false +FBXExporterSetParam "Cameras" false +FBXExporterSetParam "Lights" false +FBXExporterSetParam "PointCache" false +FBXExporterSetParam "AxisConversionMethod" "Animation" +FbxExporterSetParam "UpAxis" "Y" +FbxExporterSetParam "Preserveinstances" true + +exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP + + """) + + self.log.debug(f"Executing command: {export_fbx_cmd}") + + with maintained_selection(): + # select and export + rt.select(get_all_children(rt.getNodeByName(container))) + rt.execute(export_fbx_cmd) + + self.log.info("Performing Extraction ...") + 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, + filepath)) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py new file mode 100644 index 0000000000..298e19151d --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -0,0 +1,59 @@ +import os +import pyblish.api +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api import ( + maintained_selection, + get_all_children +) + + +class ExtractModelObj(publish.Extractor, + OptionalPyblishPluginMixin): + """ + Extract Geometry in OBJ Format + """ + + order = pyblish.api.ExtractorOrder - 0.05 + label = "Extract OBJ" + hosts = ["max"] + families = ["model"] + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + + container = instance.data["instance_node"] + + self.log.info("Extracting Geometry ...") + + stagingdir = self.staging_dir(instance) + filename = "{name}.obj".format(**instance.data) + filepath = os.path.join(stagingdir, + filename) + self.log.info("Writing OBJ '%s' to '%s'" % (filepath, + stagingdir)) + + with maintained_selection(): + # select and export + rt.select(get_all_children(rt.getNodeByName(container))) + rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') + + self.log.info("Performing Extraction ...") + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'obj', + 'ext': 'obj', + 'files': filename, + "stagingDir": stagingdir, + } + + instance.data["representations"].append(representation) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + filepath)) From 64e8ff68b54d420c142c9276674e6cac74646ce0 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Mon, 13 Mar 2023 11:32:47 +0100 Subject: [PATCH 104/428] cosmetic issue fixed --- openpype/hosts/max/plugins/publish/extract_model_obj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index 298e19151d..7bda237880 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -41,7 +41,7 @@ class ExtractModelObj(publish.Extractor, with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') + rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') # noqa self.log.info("Performing Extraction ...") if "representations" not in instance.data: From 55a10a87932130828eeca112f7098e4a4cf5a24f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Mar 2023 22:55:00 +0100 Subject: [PATCH 105/428] Use new style `ColormanagedPyblishPluginMixin` --- .../hosts/substancepainter/plugins/publish/extract_textures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index e66ce6dbf6..469f8501f7 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -2,7 +2,8 @@ from openpype.pipeline import KnownPublishError, publish import substance_painter.export -class ExtractTextures(publish.ExtractorColormanaged): +class ExtractTextures(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): """Extract Textures using an output template config. Note: From d780974b1b1e0f2e49fdeffddc8ed8d44e673f0e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Mar 2023 17:23:44 +0800 Subject: [PATCH 106/428] usd mesh format to trimesh and adjustment on update function in loaders. --- openpype/hosts/max/plugins/load/load_model_fbx.py | 2 +- openpype/hosts/max/plugins/load/load_model_obj.py | 2 +- openpype/hosts/max/plugins/load/load_model_usd.py | 2 +- openpype/hosts/max/plugins/publish/extract_model_usd.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 38b8555d28..1729874a6b 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -47,7 +47,7 @@ importFile @"{filepath}" #noPrompt using:FBXIMP path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - fbx_objects = self.get_container_children(node) + fbx_objects = node.Children for fbx_object in fbx_objects: fbx_object.source = path diff --git a/openpype/hosts/max/plugins/load/load_model_obj.py b/openpype/hosts/max/plugins/load/load_model_obj.py index 06b411cb5c..281a986934 100644 --- a/openpype/hosts/max/plugins/load/load_model_obj.py +++ b/openpype/hosts/max/plugins/load/load_model_obj.py @@ -41,7 +41,7 @@ class ObjLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - objects = self.get_container_children(node) + objects = node.Children for obj in objects: obj.source = path diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py index c6c414b91c..b6a41f4e68 100644 --- a/openpype/hosts/max/plugins/load/load_model_usd.py +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -41,7 +41,7 @@ class ModelUSDLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - usd_objects = self.get_container_children(node) + usd_objects = node.Children for usd_object in usd_objects: usd_object.source = path diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index f70a14ba0b..60dddc8670 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -101,7 +101,7 @@ class ExtractModelUSD(publish.Extractor, export_options.Lights = False export_options.Cameras = False export_options.Materials = False - export_options.MeshFormat = rt.name('polyMesh') + export_options.MeshFormat = rt.name('triMesh') export_options.FileFormat = rt.name('ascii') export_options.UpAxis = rt.name('y') export_options.LogLevel = rt.name('info') From 5ebca028eede5a0545b5f41e3f685b922ccc0f58 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 19 Mar 2023 13:25:22 +0100 Subject: [PATCH 107/428] Remove `FramesPerTask` as Deadline job info submission - `FramesPerTask` does not exist and should be `ChunkSize` which is also set. --- openpype/modules/deadline/plugins/publish/submit_max_deadline.py | 1 - .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 417a03de74..392e36b08e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -74,7 +74,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.ChunkSize = instance.data.get("chunkSize", 1) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) - job_info.FramesPerTask = instance.data.get("framesPerTask", 1) if self.group: job_info.Group = self.group diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 062732c059..6884db4dc9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -145,7 +145,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.ChunkSize = instance.data.get("chunkSize", 10) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) - job_info.FramesPerTask = instance.data.get("framesPerTask", 1) if self.group != "none" and self.group: job_info.Group = self.group From 0cd7ddcef423003c1e40f29a88e6943c61ef7cdf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 19 Mar 2023 13:26:57 +0100 Subject: [PATCH 108/428] Remove setting of `ChunkSize` from instance data since CollectRender actually applies it through "renderGlobals" --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 6884db4dc9..19d4f170b6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -142,7 +142,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.ChunkSize = instance.data.get("chunkSize", 10) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) From fd2d210522fbeddd27f707b0683e2f7411affd8e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 11:26:48 +0100 Subject: [PATCH 109/428] Use create context environment Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../substancepainter/plugins/create/create_workfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 729cc8f718..29191a1714 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -29,9 +29,9 @@ class CreateWorkfile(AutoCreator): variant = self.default_variant project_name = self.project_name - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] + asset_name = self.create_context.get_current_asset_name() + task_name = self.create_context.get_current_task_name() + host_name = self.create_context.host_name # Workfile instance should always exist and must only exist once. # As such we'll first check if it already exists and is collected. From eeaa807588317b10e641e83566a07e278f3be6a7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 11:41:54 +0100 Subject: [PATCH 110/428] Remove unused import --- .../hosts/substancepainter/plugins/create/create_workfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 29191a1714..4e316f3b64 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -2,7 +2,6 @@ """Creator plugin for creating workfiles.""" from openpype.pipeline import CreatedInstance, AutoCreator -from openpype.pipeline import legacy_io from openpype.client import get_asset_by_name from openpype.hosts.substancepainter.api.pipeline import ( From 9020bf23d325b706485ed7374d22f6073aa71e79 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 11:44:56 +0100 Subject: [PATCH 111/428] Implement `get_context_data` and `update_context_data` --- .../hosts/substancepainter/api/pipeline.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index f4d4c5b00c..b377db1641 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -38,6 +38,7 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") OPENPYPE_METADATA_KEY = "OpenPype" OPENPYPE_METADATA_CONTAINERS_KEY = "containers" # child key +OPENPYPE_METADATA_CONTEXT_KEY = "context" # child key class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): @@ -140,15 +141,21 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): container["objectName"] = key yield container - @staticmethod - def create_context_node(): - pass - def update_context_data(self, data, changes): - pass + + if not substance_painter.project.is_open(): + return + + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + metadata.set(OPENPYPE_METADATA_CONTEXT_KEY, data) def get_context_data(self): - pass + + if not substance_painter.project.is_open(): + return + + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + return metadata.get(OPENPYPE_METADATA_CONTEXT_KEY) or {} def _install_menu(self): from PySide2 import QtWidgets From eeb2388475d664aa95dff4b09fdef9fc6ed17549 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 14:13:21 +0100 Subject: [PATCH 112/428] Use `openpype.pipeline.create.get_subset_name` to define the subset name --- .../publish/collect_textureset_images.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 04187d4079..b368c86749 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -9,6 +9,8 @@ from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, strip_template ) +from openpype.pipeline.create import get_subset_name +from openpype.client import get_asset_by_name class CollectTextureSet(pyblish.api.InstancePlugin): @@ -24,6 +26,10 @@ class CollectTextureSet(pyblish.api.InstancePlugin): def process(self, instance): config = self.get_export_config(instance) + asset_doc = get_asset_by_name( + project_name=instance.context.data["projectName"], + asset_name=instance.data["asset"] + ) instance.data["exportConfig"] = config maps = get_parsed_export_maps(config) @@ -34,9 +40,11 @@ class CollectTextureSet(pyblish.api.InstancePlugin): self.log.info(f"Processing {texture_set_name}/{stack_name}") for template, outputs in template_maps.items(): self.log.info(f"Processing {template}") - self.create_image_instance(instance, template, outputs) + self.create_image_instance(instance, template, outputs, + asset_doc=asset_doc) - def create_image_instance(self, instance, template, outputs): + def create_image_instance(self, instance, template, outputs, + asset_doc): """Create a new instance per image or UDIM sequence. The new instances will be of family `image`. @@ -53,8 +61,17 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Define the suffix we want to give this particular texture # set and set up a remapped subset naming for it. suffix = f".{map_identifier}" - image_subset = instance.data["subset"][len("textureSet"):] - image_subset = "texture" + image_subset + suffix + image_subset = get_subset_name( + # TODO: The family actually isn't 'texture' currently but for now + # this is only done so the subset name starts with 'texture' + family="texture", + variant=instance.data["variant"] + suffix, + task_name=instance.data.get("task"), + asset_doc=asset_doc, + project_name=context.data["projectName"], + host_name=context.data["hostName"], + project_settings=context.data["project_settings"] + ) # Prepare representation representation = { From f8a3e24c606048883fa0942c01df1f7aff893436 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 20:04:36 +0100 Subject: [PATCH 113/428] Explain how Texture Sets are split into separate publishes per output map in documentation --- website/docs/artist_hosts_substancepainter.md | 33 ++++++++++++++++-- ...ter_pbrmetallicroughness_export_preset.png | Bin 0 -> 45842 bytes ...painter_pbrmetallicroughness_published.png | Bin 0 -> 7497 bytes 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 website/docs/assets/substancepainter_pbrmetallicroughness_export_preset.png create mode 100644 website/docs/assets/substancepainter_pbrmetallicroughness_published.png diff --git a/website/docs/artist_hosts_substancepainter.md b/website/docs/artist_hosts_substancepainter.md index 9ed83421af..86bcbba82e 100644 --- a/website/docs/artist_hosts_substancepainter.md +++ b/website/docs/artist_hosts_substancepainter.md @@ -51,8 +51,9 @@ publish instance. To create a **TextureSet instance** we will use OpenPype's publisher tool. Go to **OpenPype → Publish... → TextureSet** -The texture set instance will define what Substance Painter export template `.spexp` to -use and thus defines what texture maps will be exported from your workfile. +The texture set instance will define what Substance Painter export template (`.spexp`) to +use and thus defines what texture maps will be exported from your workfile. This +can be set with the **Output Template** attribute on the instance. :::info The TextureSet instance gets saved with your Substance Painter project. As such, @@ -61,8 +62,34 @@ just click **OpenPype → Publish...** and start publishing directly with the same settings. ::: +#### Publish per output map of the Substance Painter preset -### Known issues +The Texture Set instance generates a publish per output map that is defined in +the Substance Painter's export preset. For example a publish from a default +PBR Metallic Roughness texture set results in six separate published subsets +(if all the channels exist in your file). + +![Substance Painter PBR Metallic Roughness Export Preset](assets/substancepainter_pbrmetallicroughness_export_preset.png) + +When publishing for example a texture set with variant **Main** six instances will +be published with the variants: +- Main.**BaseColor** +- Main.**Emissive** +- Main.**Height** +- Main.**Metallic** +- Main.**Normal** +- Main.**Roughness** + +The bold output map name for the publish is based on the string that is pulled +from the what is considered to be the static part of the filename templates in +the export preset. The tokens like `$mesh` and `(_$colorSpace)` are ignored. +So `$mesh_$textureSet_BaseColor(_$colorSpace)(.$udim)` becomes `BaseColor`. + +An example output for PBR Metallic Roughness would be: + +![Substance Painter PBR Metallic Roughness Publish Example in Loader](assets/substancepainter_pbrmetallicroughness_published.png) + +## Known issues #### Can't see the OpenPype menu? diff --git a/website/docs/assets/substancepainter_pbrmetallicroughness_export_preset.png b/website/docs/assets/substancepainter_pbrmetallicroughness_export_preset.png new file mode 100644 index 0000000000000000000000000000000000000000..35a4545f83563332e983ada1698433955498a969 GIT binary patch literal 45842 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/substancepainter_pbrmetallicroughness_published.png b/website/docs/assets/substancepainter_pbrmetallicroughness_published.png new file mode 100644 index 0000000000000000000000000000000000000000..15b0e5b87687a8675c481a8265e5f0c5639de8fe GIT binary patch literal 7497 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 Date: Mon, 20 Mar 2023 20:07:23 +0100 Subject: [PATCH 114/428] Simplify setting review tag and stagingDir for thumbnail on representation --- .../plugins/publish/collect_textureset_images.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index b368c86749..f7187b638f 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -87,6 +87,12 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # strings. See CollectTextures plug-in and Integrators. representation["udim"] = [output["udim"] for output in outputs] + # Set up the representation for thumbnail generation + # TODO: Simplify this once thumbnail extraction is refactored + staging_dir = os.path.dirname(first_filepath) + representation["tags"] = ["review"] + representation["stagingDir"] = staging_dir + # Clone the instance image_instance = context.create_instance(instance.name) image_instance[:] = instance[:] @@ -108,12 +114,6 @@ class CollectTextureSet(pyblish.api.InstancePlugin): self.log.debug(f"{image_subset} colorspace: {colorspace}") image_instance.data["colorspace"] = colorspace - # Set up the representation for thumbnail generation - # TODO: Simplify this once thumbnail extraction is refactored - staging_dir = os.path.dirname(first_filepath) - image_instance.data["representations"][0]["tags"] = ["review"] - image_instance.data["representations"][0]["stagingDir"] = staging_dir - # Store the instance in the original instance as a member instance.append(image_instance) From 0b3cb6942dc03e231743fd1713f3e919fdc785f7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 20:27:34 +0100 Subject: [PATCH 115/428] Add todo about a potentially critical issue to still be solved. --- .../plugins/publish/collect_textureset_images.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index f7187b638f..14168138b6 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -60,6 +60,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Define the suffix we want to give this particular texture # set and set up a remapped subset naming for it. + # TODO (Critical) Support needs to be added to have multiple materials + # with each their own maps. So we might need to include the + # material or alike in the variant suffix too? suffix = f".{map_identifier}" image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now From 700927c1645fc9183a739abfd4529f4a94e027d2 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 21 Mar 2023 15:21:35 +0000 Subject: [PATCH 116/428] Restored lost changes --- .../unreal/plugins/create/create_render.py | 212 ++++++++++++++---- 1 file changed, 174 insertions(+), 38 deletions(-) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index 5834d2e7a7..b2a246d3a8 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -1,14 +1,22 @@ # -*- coding: utf-8 -*- +from pathlib import Path + import unreal -from openpype.pipeline import CreatorError from openpype.hosts.unreal.api.pipeline import ( - get_subsequences + UNREAL_VERSION, + create_folder, + get_subsequences, ) from openpype.hosts.unreal.api.plugin import ( UnrealAssetCreator ) -from openpype.lib import UILabelDef +from openpype.lib import ( + UILabelDef, + UISeparatorDef, + BoolDef, + NumberDef +) class CreateRender(UnrealAssetCreator): @@ -19,7 +27,90 @@ class CreateRender(UnrealAssetCreator): family = "render" icon = "eye" - def create(self, subset_name, instance_data, pre_create_data): + def create_instance( + self, instance_data, subset_name, pre_create_data, + selected_asset_path, master_seq, master_lvl, seq_data + ): + instance_data["members"] = [selected_asset_path] + instance_data["sequence"] = selected_asset_path + instance_data["master_sequence"] = master_seq + instance_data["master_level"] = master_lvl + instance_data["output"] = seq_data.get('output') + instance_data["frameStart"] = seq_data.get('frame_range')[0] + instance_data["frameEnd"] = seq_data.get('frame_range')[1] + + super(CreateRender, self).create( + subset_name, + instance_data, + pre_create_data) + + def create_with_new_sequence( + self, subset_name, instance_data, pre_create_data + ): + # If the option to create a new level sequence is selected, + # create a new level sequence and a master level. + + root = f"/Game/OpenPype/Sequences" + + # Create a new folder for the sequence in root + sequence_dir_name = create_folder(root, subset_name) + sequence_dir = f"{root}/{sequence_dir_name}" + + unreal.log_warning(f"sequence_dir: {sequence_dir}") + + # Create the level sequence + asset_tools = unreal.AssetToolsHelpers.get_asset_tools() + seq = asset_tools.create_asset( + asset_name=subset_name, + package_path=sequence_dir, + asset_class=unreal.LevelSequence, + factory=unreal.LevelSequenceFactoryNew()) + + seq.set_playback_start(pre_create_data.get("start_frame")) + seq.set_playback_end(pre_create_data.get("end_frame")) + + unreal.EditorAssetLibrary.save_asset(seq.get_path_name()) + + # Create the master level + if UNREAL_VERSION.major >= 5: + curr_level = unreal.LevelEditorSubsystem().get_current_level() + else: + world = unreal.EditorLevelLibrary.get_editor_world() + levels = unreal.EditorLevelUtils.get_levels(world) + curr_level = levels[0] if len(levels) else None + if not curr_level: + raise RuntimeError("No level loaded.") + curr_level_path = curr_level.get_outer().get_path_name() + + # If the level path does not start with "/Game/", the current + # level is a temporary, unsaved level. + if curr_level_path.startswith("/Game/"): + if UNREAL_VERSION.major >= 5: + unreal.LevelEditorSubsystem().save_current_level() + else: + unreal.EditorLevelLibrary.save_current_level() + + ml_path = f"{sequence_dir}/{subset_name}_MasterLevel" + + if UNREAL_VERSION.major >= 5: + unreal.LevelEditorSubsystem().new_level(ml_path) + else: + unreal.EditorLevelLibrary.new_level(ml_path) + + seq_data = { + "sequence": seq, + "output": f"{seq.get_name()}", + "frame_range": ( + seq.get_playback_start(), + seq.get_playback_end())} + + self.create_instance( + instance_data, subset_name, pre_create_data, + seq.get_path_name(), seq.get_path_name(), ml_path, seq_data) + + def create_from_existing_sequence( + self, subset_name, instance_data, pre_create_data + ): ar = unreal.AssetRegistryHelpers.get_asset_registry() sel_objects = unreal.EditorUtilityLibrary.get_selected_assets() @@ -27,8 +118,8 @@ class CreateRender(UnrealAssetCreator): a.get_path_name() for a in sel_objects if a.get_class().get_name() == "LevelSequence"] - if not selection: - raise CreatorError("Please select at least one Level Sequence.") + if len(selection) == 0: + raise RuntimeError("Please select at least one Level Sequence.") seq_data = None @@ -42,28 +133,38 @@ class CreateRender(UnrealAssetCreator): f"Skipping {selected_asset.get_name()}. It isn't a Level " "Sequence.") - # The asset name is the third element of the path which - # contains the map. - # To take the asset name, we remove from the path the prefix - # "/Game/OpenPype/" and then we split the path by "/". - sel_path = selected_asset_path - asset_name = sel_path.replace("/Game/OpenPype/", "").split("/")[0] + if pre_create_data.get("use_hierarchy"): + # The asset name is the the third element of the path which + # contains the map. + # To take the asset name, we remove from the path the prefix + # "/Game/OpenPype/" and then we split the path by "/". + sel_path = selected_asset_path + asset_name = sel_path.replace( + "/Game/OpenPype/", "").split("/")[0] + + search_path = f"/Game/OpenPype/{asset_name}" + else: + search_path = Path(selected_asset_path).parent.as_posix() # Get the master sequence and the master level. # There should be only one sequence and one level in the directory. - ar_filter = unreal.ARFilter( - class_names=["LevelSequence"], - package_paths=[f"/Game/OpenPype/{asset_name}"], - recursive_paths=False) - sequences = ar.get_assets(ar_filter) - master_seq = sequences[0].get_asset().get_path_name() - master_seq_obj = sequences[0].get_asset() - ar_filter = unreal.ARFilter( - class_names=["World"], - package_paths=[f"/Game/OpenPype/{asset_name}"], - recursive_paths=False) - levels = ar.get_assets(ar_filter) - master_lvl = levels[0].get_asset().get_path_name() + try: + ar_filter = unreal.ARFilter( + class_names=["LevelSequence"], + package_paths=[search_path], + recursive_paths=False) + sequences = ar.get_assets(ar_filter) + master_seq = sequences[0].get_asset().get_path_name() + master_seq_obj = sequences[0].get_asset() + ar_filter = unreal.ARFilter( + class_names=["World"], + package_paths=[search_path], + recursive_paths=False) + levels = ar.get_assets(ar_filter) + master_lvl = levels[0].get_asset().get_path_name() + except IndexError: + raise RuntimeError( + f"Could not find the hierarchy for the selected sequence.") # If the selected asset is the master sequence, we get its data # and then we create the instance for the master sequence. @@ -79,7 +180,8 @@ class CreateRender(UnrealAssetCreator): master_seq_obj.get_playback_start(), master_seq_obj.get_playback_end())} - if selected_asset_path == master_seq: + if (selected_asset_path == master_seq or + pre_create_data.get("use_hierarchy")): seq_data = master_seq_data else: seq_data_list = [master_seq_data] @@ -119,20 +221,54 @@ class CreateRender(UnrealAssetCreator): "sub-sequence of the master sequence.") continue - instance_data["members"] = [selected_asset_path] - instance_data["sequence"] = selected_asset_path - instance_data["master_sequence"] = master_seq - instance_data["master_level"] = master_lvl - instance_data["output"] = seq_data.get('output') - instance_data["frameStart"] = seq_data.get('frame_range')[0] - instance_data["frameEnd"] = seq_data.get('frame_range')[1] + self.create_instance( + instance_data, subset_name, pre_create_data, + selected_asset_path, master_seq, master_lvl, seq_data) - super(CreateRender, self).create( - subset_name, - instance_data, - pre_create_data) + def create(self, subset_name, instance_data, pre_create_data): + if pre_create_data.get("create_seq"): + self.create_with_new_sequence( + subset_name, instance_data, pre_create_data) + else: + self.create_from_existing_sequence( + subset_name, instance_data, pre_create_data) def get_pre_create_attr_defs(self): return [ - UILabelDef("Select the sequence to render.") + UILabelDef( + "Select a Level Sequence to render or create a new one." + ), + BoolDef( + "create_seq", + label="Create a new Level Sequence", + default=False + ), + UILabelDef( + "WARNING: If you create a new Level Sequence, the current\n" + "level will be saved and a new Master Level will be created." + ), + NumberDef( + "start_frame", + label="Start Frame", + default=0, + minimum=-999999, + maximum=999999 + ), + NumberDef( + "end_frame", + label="Start Frame", + default=150, + minimum=-999999, + maximum=999999 + ), + UISeparatorDef(), + UILabelDef( + "The following settings are valid only if you are not\n" + "creating a new sequence." + ), + BoolDef( + "use_hierarchy", + label="Use Hierarchy", + default=False + ), ] From afd6faffc3e6309d325e960e3461fe2a64435292 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 21 Mar 2023 17:53:04 +0100 Subject: [PATCH 117/428] Update openpype/modules/deadline/plugins/publish/submit_max_deadline.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 392e36b08e..226ca2eb98 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -71,7 +71,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.ChunkSize = instance.data.get("chunkSize", 1) + job_info.ChunkSize = instance.data.get("chunkSize", self.chunk_size) job_info.Comment = context.data.get("comment") job_info.Priority = instance.data.get("priority", self.priority) From 423cbf9e5465ee146523460376efde0595e44374 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 21 Mar 2023 17:06:36 +0000 Subject: [PATCH 118/428] Fix level sequence not being added to instance --- openpype/hosts/unreal/plugins/create/create_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index b2a246d3a8..b9c443c456 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -69,6 +69,8 @@ class CreateRender(UnrealAssetCreator): seq.set_playback_start(pre_create_data.get("start_frame")) seq.set_playback_end(pre_create_data.get("end_frame")) + pre_create_data["members"] = [seq.get_path_name()] + unreal.EditorAssetLibrary.save_asset(seq.get_path_name()) # Create the master level From 7d1e376761f8c4532af04f649355f9aead58e61f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 22 Mar 2023 11:35:20 +0000 Subject: [PATCH 119/428] Added warning if no assets selected when starting rendering --- openpype/hosts/unreal/api/rendering.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index 29e4747f6e..25faa2ac2c 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -4,6 +4,7 @@ import unreal from openpype.pipeline import Anatomy from openpype.hosts.unreal.api import pipeline +from openpype.widgets.message_window import Window queue = None @@ -37,6 +38,15 @@ def start_rendering(): # Get selected sequences assets = unreal.EditorUtilityLibrary.get_selected_assets() + if not assets: + Window( + parent=None, + title="No assets selected", + message="No assets selected. Select a render instance.", + level="warning") + raise RuntimeError( + "No assets selected. You need to select a render instance.") + # instances = pipeline.ls_inst() instances = [ a for a in assets From 217b9dd70822ecccfaf6e2d45b4caac0d479835b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 10:54:18 +0100 Subject: [PATCH 120/428] Move and refactor PySide2 imports to `qtpy` and top of file --- openpype/hosts/substancepainter/api/lib.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index e552caee6d..e299ab03de 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -7,6 +7,8 @@ import substance_painter.project import substance_painter.resource import substance_painter.js +from qtpy import QtGui, QtWidgets, QtCore + def get_export_presets(): """Return Export Preset resource URLs for all available Export Presets. @@ -391,8 +393,6 @@ def get_parsed_export_maps(config): dict: [texture_set, stack]: {template: [file1_data, file2_data]} """ - import substance_painter.export - from .colorspace import get_project_channel_data outputs = substance_painter.export.list_project_textures(config) templates = get_export_templates(config, strip_folder=False) @@ -524,7 +524,6 @@ def load_shelf(path, name=None): def _get_new_project_action(): """Return QAction which triggers Substance Painter's new project dialog""" - from PySide2 import QtGui main_window = substance_painter.ui.get_main_window() @@ -564,7 +563,6 @@ def prompt_new_file_with_mesh(mesh_filepath): for example when the user might have cancelled the operation. """ - from PySide2 import QtWidgets, QtCore app = QtWidgets.QApplication.instance() assert os.path.isfile(mesh_filepath), \ From 1cc2db14bbd0be5a380fadc7108f0ed646f95abc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 10:56:14 +0100 Subject: [PATCH 121/428] Add back in imports that accidentally got removed --- openpype/hosts/substancepainter/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index e299ab03de..2cd08f862e 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -6,6 +6,7 @@ from collections import defaultdict import substance_painter.project import substance_painter.resource import substance_painter.js +import substance_painter.export from qtpy import QtGui, QtWidgets, QtCore @@ -393,6 +394,8 @@ def get_parsed_export_maps(config): dict: [texture_set, stack]: {template: [file1_data, file2_data]} """ + # Import is here to avoid recursive lib <-> colorspace imports + from .colorspace import get_project_channel_data outputs = substance_painter.export.list_project_textures(config) templates = get_export_templates(config, strip_folder=False) From 8b3ce3044a9368663d91ba45279c7a63fcb3876e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 10:56:58 +0100 Subject: [PATCH 122/428] Raise KnownPublishError instead of assert Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/substancepainter/plugins/publish/save_workfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py index 5e86785e0d..2bd342cda1 100644 --- a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py +++ b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py @@ -13,7 +13,8 @@ class SaveCurrentWorkfile(pyblish.api.ContextPlugin): def process(self, context): host = registered_host() - assert context.data['currentFile'] == host.get_current_workfile() + if context.data['currentFile'] != host.get_current_workfile(): + raise KnownPublishError("Workfile has changed during publishing!") if host.has_unsaved_changes(): self.log.info("Saving current file..") From 17fc4ed9251551c37f5405101f12af8e1bc8e890 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 10:58:04 +0100 Subject: [PATCH 123/428] Fix import --- .../hosts/substancepainter/plugins/publish/save_workfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py index 2bd342cda1..f19deccb0e 100644 --- a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py +++ b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py @@ -1,6 +1,9 @@ import pyblish.api -from openpype.pipeline import registered_host +from openpype.pipeline import ( + registered_host, + KnownPublishError +) class SaveCurrentWorkfile(pyblish.api.ContextPlugin): From 4fdb31611dc9810346a45a10c50ea9a209d7a99f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 11:03:54 +0100 Subject: [PATCH 124/428] Allow to mark an instance to skip integration explicitly Use `instance.data["integrate"] = False` --- .../plugins/publish/extract_textures.py | 15 ++++----------- openpype/plugins/publish/integrate.py | 5 +++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index 469f8501f7..bd933610f4 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -58,14 +58,7 @@ class ExtractTextures(publish.Extractor, context=context, colorspace=colorspace) - # Add a fake representation which won't be integrated so the - # Integrator leaves us alone - otherwise it would error - # TODO: Add `instance.data["integrate"] = False` support in Integrator? - instance.data["representations"] = [ - { - "name": "_fake", - "ext": "_fake", - "delete": True, - "files": [] - } - ] + # The TextureSet instance should not be integrated. It generates no + # output data. Instead the separated texture instances are generated + # from it which themselves integrate into the database. + instance.data["integrate"] = False diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6a0327ec84..c24758ba0f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -160,6 +160,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "Instance is marked to be processed on farm. Skipping") return + # Instance is marked to not get integrated + if instance.data.get("integrate", True): + self.log.info("Instance is marked to skip integrating. Skipping") + return + filtered_repres = self.filter_representations(instance) # Skip instance if there are not representations to integrate # all representations should not be integrated From 5b3af11f0f6bbd53dcc590de49f51660dbdeb556 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 11:04:25 +0100 Subject: [PATCH 125/428] Fix the if statement --- openpype/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index c24758ba0f..fa29d2a58b 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -161,7 +161,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): return # Instance is marked to not get integrated - if instance.data.get("integrate", True): + if not instance.data.get("integrate", True): self.log.info("Instance is marked to skip integrating. Skipping") return From ddc0117aeda6fd1542d96ee54fb374a1339d8aae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 11:14:39 +0100 Subject: [PATCH 126/428] Update openpype/settings/defaults/project_settings/substancepainter.json Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../settings/defaults/project_settings/substancepainter.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/substancepainter.json b/openpype/settings/defaults/project_settings/substancepainter.json index 0f9f1af71e..60929e85fd 100644 --- a/openpype/settings/defaults/project_settings/substancepainter.json +++ b/openpype/settings/defaults/project_settings/substancepainter.json @@ -10,4 +10,4 @@ } }, "shelves": {} -} \ No newline at end of file +} From 57b84f18bc343b4892382d642927847496f3e43e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 11:18:37 +0100 Subject: [PATCH 127/428] Fix docstring --- openpype/hosts/substancepainter/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index b377db1641..652ec9ec7d 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Pipeline tools for OpenPype Gaffer integration.""" +"""Pipeline tools for OpenPype Substance Painter integration.""" import os import logging from functools import partial From f4d423dc4f7b1a42310540c74230ba3a1dcd20ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 14:39:48 +0100 Subject: [PATCH 128/428] Add Create... menu entry to match other hosts --- openpype/hosts/substancepainter/api/pipeline.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 652ec9ec7d..df41d9bb70 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -165,6 +165,12 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): menu = QtWidgets.QMenu("OpenPype") + action = menu.addAction("Create...") + action.triggered.connect( + lambda: host_tools.show_publisher(parent=parent, + tab="create") + ) + action = menu.addAction("Load...") action.triggered.connect( lambda: host_tools.show_loader(parent=parent, use_context=True) @@ -172,7 +178,8 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): action = menu.addAction("Publish...") action.triggered.connect( - lambda: host_tools.show_publisher(parent=parent) + lambda: host_tools.show_publisher(parent=parent, + tab="publish") ) action = menu.addAction("Manage...") From d4a0c6634cd0d9c31ea8f1cf12b92fee5e7ba797 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 15:45:13 +0100 Subject: [PATCH 129/428] Optimize logic --- openpype/hosts/substancepainter/api/colorspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/api/colorspace.py b/openpype/hosts/substancepainter/api/colorspace.py index a9df3eb066..375b61b39b 100644 --- a/openpype/hosts/substancepainter/api/colorspace.py +++ b/openpype/hosts/substancepainter/api/colorspace.py @@ -25,11 +25,11 @@ def _iter_document_stack_channels(): material_name = material["name"] for stack in material["stacks"]: stack_name = stack["name"] + if stack_name: + stack_path = [material_name, stack_name] + else: + stack_path = material_name for channel in stack["channels"]: - if stack_name: - stack_path = [material_name, stack_name] - else: - stack_path = material_name yield stack_path, channel From 3860fe36926aad704cf6d0ef66c89542e2acf922 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Mar 2023 18:09:11 +0100 Subject: [PATCH 130/428] OP-3951 - updated validator to use special pattern Handles numbers (version, instance number) in file names. --- .../publish/validate_sequence_frames.py | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index f03229da22..c932c0e779 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -1,3 +1,8 @@ +import os.path + +import clique +import re + import pyblish.api @@ -7,6 +12,11 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): The files found in the folder are checked against the startFrame and endFrame of the instance. If the first or last file is not corresponding with the first or last frame it is flagged as invalid. + + Used regular expression pattern handles numbers in the file names + (eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr", + "Main_beauty.1001.1001.exr") but not numbers behind frames (eg. + "Main_beauty.1001.v001.exr") """ order = pyblish.api.ValidatorOrder @@ -15,20 +25,31 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): hosts = ["shell"] def process(self, instance): + representations = instance.data.get("representations") + for repr in representations: + if isinstance(repr["files"], str): + repr["files"] = [repr["files"]] - collection = instance[0] - self.log.info(collection) + _, ext = os.path.splitext(repr["files"][0]) + pattern = r"\D?(?P(?P0*)\d+){}$".format( + re.escape(ext)) + patterns = [pattern] - frames = list(collection.indexes) + collections, remainder = clique.assemble( + repr["files"], minimum_items=1, patterns=patterns) - current_range = (frames[0], frames[-1]) - required_range = (instance.data["frameStart"], - instance.data["frameEnd"]) + assert not remainder, "Must not have remainder" + assert len(collections) == 1, "Must detect single collection" + collection = collections[0] + frames = list(collection.indexes) - if current_range != required_range: - raise ValueError("Invalid frame range: {0} - " - "expected: {1}".format(current_range, - required_range)) + current_range = (frames[0], frames[-1]) + required_range = (instance.data["frameStart"], + instance.data["frameEnd"]) - missing = collection.holes().indexes - assert not missing, "Missing frames: %s" % (missing,) + if current_range != required_range: + raise ValueError(f"Invalid frame range: {current_range} - " + f"expected: {required_range}") + + missing = collection.holes().indexes + assert not missing, "Missing frames: %s" % (missing,) From 22d628d054809a9e8f1d816994a7426197d864f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 18:09:13 +0100 Subject: [PATCH 131/428] Store instances in single project metadata key by id + fix adding/removing instances --- .../hosts/substancepainter/api/pipeline.py | 67 ++++++++++++++----- .../plugins/create/create_textures.py | 39 ++++++----- .../plugins/create/create_workfile.py | 27 +++++--- 3 files changed, 93 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index df41d9bb70..b995c9030d 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -39,6 +39,7 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") OPENPYPE_METADATA_KEY = "OpenPype" OPENPYPE_METADATA_CONTAINERS_KEY = "containers" # child key OPENPYPE_METADATA_CONTEXT_KEY = "context" # child key +OPENPYPE_METADATA_INSTANCES_KEY = "instances" # child key class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): @@ -312,21 +313,6 @@ def imprint_container(container, container[key] = value -def set_project_metadata(key, data): - """Set a key in project's OpenPype metadata.""" - metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) - metadata.set(key, data) - - -def get_project_metadata(key): - """Get a key from project's OpenPype metadata.""" - if not substance_painter.project.is_open(): - return - - metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) - return metadata.get(key) - - def set_container_metadata(object_name, container_data, update=False): """Helper method to directly set the data for a specific container @@ -359,3 +345,54 @@ def remove_container_metadata(object_name): if containers: containers.pop(object_name, None) metadata.set("containers", containers) + + +def set_instance(instance_id, instance_data, update=False): + """Helper method to directly set the data for a specific container + + Args: + instance_id (str): Unique identifier for the instance + instance_data (dict): The instance data to store in the metaadata. + """ + set_instances({instance_id: instance_data}, update=update) + + +def set_instances(instance_data_by_id, update=False): + """Store data for multiple instances at the same time. + + This is more optimal than querying and setting them in the metadata one + by one. + """ + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + instances = metadata.get(OPENPYPE_METADATA_INSTANCES_KEY) or {} + + for instance_id, instance_data in instance_data_by_id.items(): + if update: + existing_data = instances.get(instance_id, {}) + existing_data.update(instance_data) + else: + instances[instance_id] = instance_data + + metadata.set("instances", instances) + + +def remove_instance(instance_id): + """Helper method to remove the data for a specific container""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + instances = metadata.get(OPENPYPE_METADATA_INSTANCES_KEY) or {} + instances.pop(instance_id, None) + metadata.set("instances", instances) + + +def get_instances_by_id(): + """Return all instances stored in the project instances metadata""" + if not substance_painter.project.is_open(): + return {} + + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + return metadata.get(OPENPYPE_METADATA_INSTANCES_KEY) or {} + + +def get_instances(): + """Return all instances stored in the project instances as a list""" + return list(get_instances_by_id().values()) diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index 9d641215dc..19133768a5 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating textures.""" -from openpype.pipeline import CreatedInstance, Creator +from openpype.pipeline import CreatedInstance, Creator, CreatorError from openpype.lib import ( EnumDef, UILabelDef, @@ -9,8 +9,10 @@ from openpype.lib import ( ) from openpype.hosts.substancepainter.api.pipeline import ( - set_project_metadata, - get_project_metadata + get_instances, + set_instance, + set_instances, + remove_instance ) from openpype.hosts.substancepainter.api.lib import get_export_presets @@ -29,27 +31,34 @@ class CreateTextures(Creator): def create(self, subset_name, instance_data, pre_create_data): if not substance_painter.project.is_open(): - return + raise CreatorError("Can't create a Texture Set instance without " + "an open project.") - instance = self.create_instance_in_context(subset_name, instance_data) - set_project_metadata("textureSet", instance.data_to_store()) + instance = self.create_instance_in_context(subset_name, + instance_data) + set_instance( + instance_id=instance["instance_id"], + instance_data=instance.data_to_store() + ) def collect_instances(self): - workfile = get_project_metadata("textureSet") - if workfile: - self.create_instance_in_context_from_existing(workfile) + for instance in get_instances(): + if (instance.get("creator_identifier") == self.identifier or + instance.get("family") == self.family): + self.create_instance_in_context_from_existing(instance) def update_instances(self, update_list): + instance_data_by_id = {} for instance, _changes in update_list: - # Update project's metadata - data = get_project_metadata("textureSet") or {} - data.update(instance.data_to_store()) - set_project_metadata("textureSet", data) + # Persist the data + instance_id = instance.get("instance_id") + instance_data = instance.data_to_store() + instance_data_by_id[instance_id] = instance_data + set_instances(instance_data_by_id, update=True) def remove_instances(self, instances): for instance in instances: - # TODO: Implement removal - # api.remove_instance(instance) + remove_instance(instance["instance_id"]) self._remove_instance_from_context(instance) # Helper methods (this might get moved into Creator class) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index 4e316f3b64..d7f31f9dcf 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -5,8 +5,9 @@ from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name from openpype.hosts.substancepainter.api.pipeline import ( - set_project_metadata, - get_project_metadata + set_instances, + set_instance, + get_instances ) import substance_painter.project @@ -66,19 +67,25 @@ class CreateWorkfile(AutoCreator): current_instance["task"] = task_name current_instance["subset"] = subset_name - set_project_metadata("workfile", current_instance.data_to_store()) + set_instance( + instance_id=current_instance.get("instance_id"), + instance_data=current_instance.data_to_store() + ) def collect_instances(self): - workfile = get_project_metadata("workfile") - if workfile: - self.create_instance_in_context_from_existing(workfile) + for instance in get_instances(): + if (instance.get("creator_identifier") == self.identifier or + instance.get("family") == self.family): + self.create_instance_in_context_from_existing(instance) def update_instances(self, update_list): + instance_data_by_id = {} for instance, _changes in update_list: - # Update project's workfile metadata - data = get_project_metadata("workfile") or {} - data.update(instance.data_to_store()) - set_project_metadata("workfile", data) + # Persist the data + instance_id = instance.get("instance_id") + instance_data = instance.data_to_store() + instance_data_by_id[instance_id] = instance_data + set_instances(instance_data_by_id, update=True) # Helper methods (this might get moved into Creator class) def create_instance_in_context(self, subset_name, data): From e8f9c130bbb7b805f6ea68a3bbc37d81168d5893 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Mar 2023 18:11:28 +0100 Subject: [PATCH 132/428] OP-3951 - merged global validator and unreal one Both should do same thing. Combination of host and family should be safe. --- .../publish/validate_sequence_frames.py | 41 ------------------- .../publish/validate_sequence_frames.py | 4 +- 2 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py diff --git a/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py b/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py deleted file mode 100644 index 87f1338ee8..0000000000 --- a/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py +++ /dev/null @@ -1,41 +0,0 @@ -import clique - -import pyblish.api - - -class ValidateSequenceFrames(pyblish.api.InstancePlugin): - """Ensure the sequence of frames is complete - - The files found in the folder are checked against the frameStart and - frameEnd of the instance. If the first or last file is not - corresponding with the first or last frame it is flagged as invalid. - """ - - order = pyblish.api.ValidatorOrder - label = "Validate Sequence Frames" - families = ["render"] - hosts = ["unreal"] - optional = True - - def process(self, instance): - representations = instance.data.get("representations") - for repr in representations: - patterns = [clique.PATTERNS["frames"]] - collections, remainder = clique.assemble( - repr["files"], minimum_items=1, patterns=patterns) - - assert not remainder, "Must not have remainder" - assert len(collections) == 1, "Must detect single collection" - collection = collections[0] - frames = list(collection.indexes) - - current_range = (frames[0], frames[-1]) - required_range = (instance.data["frameStart"], - instance.data["frameEnd"]) - - if current_range != required_range: - raise ValueError(f"Invalid frame range: {current_range} - " - f"expected: {required_range}") - - missing = collection.holes().indexes - assert not missing, "Missing frames: %s" % (missing,) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index c932c0e779..56641fbf57 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -21,8 +21,8 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Sequence Frames" - families = ["imagesequence"] - hosts = ["shell"] + families = ["imagesequence", "render"] + hosts = ["shell", "unreal"] def process(self, instance): representations = instance.data.get("representations") From 0648271ec784824fef1c7ffe373d0ae1034b9ec6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Mar 2023 18:15:11 +0100 Subject: [PATCH 133/428] OP-3951 - added tests for validate_sequence_frames Introduced conftest with dummy environment for basic tests --- tests/unit/openpype/conftest.py | 38 +++++ .../publish/test_validate_sequence_frames.py | 143 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 tests/unit/openpype/conftest.py create mode 100644 tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py diff --git a/tests/unit/openpype/conftest.py b/tests/unit/openpype/conftest.py new file mode 100644 index 0000000000..e5355a3120 --- /dev/null +++ b/tests/unit/openpype/conftest.py @@ -0,0 +1,38 @@ +"""Dummy environment that allows importing Openpype modules and run +tests in parent folder and all subfolders manually from IDE. + +This should not get triggered if the tests are running from `runtests` as it +is expected there that environment is handled by OP itself. + +This environment should be enough to run simple `BaseTest` where no +external preparation is necessary (eg. no prepared DB, no source files). +These tests might be enough to import and run simple pyblish plugins to +validate logic. + +Please be aware that these tests might use values in real databases, so use +`BaseTest` only for logic without side effects or special configuration. For +these there is `tests.lib.testing_classes.ModuleUnitTest` which would setup +proper test DB (but it requires `mongorestore` on the sys.path) + +If pyblish plugins require any host dependent communication, it would need + to be mocked. + +This setting of env vars is necessary to run before any imports of OP code! +(This is why it is in `conftest.py` file.) +If your test requires any additional env var, copy this file to folder of your +test, it should only that folder. +""" + +import os + + +if not os.environ.get("IS_TEST"): # running tests from cmd or CI + os.environ["AVALON_MONGO"] = "mongodb://localhost:27017" + os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017" + os.environ["AVALON_DB"] = "avalon" + os.environ["OPENPYPE_DATABASE_NAME"] = "avalon" + os.environ["AVALON_TIMEOUT"] = '3000' + os.environ["OPENPYPE_DEBUG"] = "3" + os.environ["AVALON_CONFIG"] = "pype" + os.environ["AVALON_ASSET"] = "test_asset" + os.environ["AVALON_PROJECT"] = "test_project" diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py new file mode 100644 index 0000000000..5580621cfb --- /dev/null +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -0,0 +1,143 @@ + + +"""Test Publish_plugins pipeline publish modul, tests API methods + + File: + creates temporary directory and downloads .zip file from GDrive + unzips .zip file + uses content of .zip file (MongoDB's dumps) to import to new databases + with use of 'monkeypatch_session' modifies required env vars + temporarily + runs battery of tests checking that site operation for Sync Server + module are working + removes temporary folder + removes temporary databases (?) +""" +import pytest +import logging + +from pyblish.api import Instance as PyblishInstance + +from tests.lib.testing_classes import BaseTest +from openpype.plugins.publish.validate_sequence_frames import ( + ValidateSequenceFrames +) + +log = logging.getLogger(__name__) + + +class TestValidateSequenceFrames(BaseTest): + """ Testing ValidateSequenceFrames plugin + + """ + + @pytest.fixture + def instance(self): + + class Instance(PyblishInstance): + data = { + "frameStart": 1001, + "frameEnd": 1001, + "representations": [] + } + yield Instance + + @pytest.fixture(scope="module") + def plugin(self): + plugin = ValidateSequenceFrames() + plugin.log = log + + yield plugin + + def test_validate_sequence_frames_single_frame(self, instance, plugin): + representations = [ + { + "ext": "exr", + "files": "Main_beauty.1001.exr", + } + ] + instance.data["representations"] = representations + + plugin.process(instance) + + @pytest.mark.parametrize("files", + ["Main_beauty.v001.1001.exr", + "Main_beauty_v001.1001.exr", + "Main_beauty.1001.1001.exr"]) + def test_validate_sequence_frames_single_frame_name(self, instance, + plugin, + files): + # tests for names with number inside, caused clique failure before + representations = [ + { + "ext": "exr", + "files": files, + } + ] + instance.data["representations"] = representations + + plugin.process(instance) + + @pytest.mark.parametrize("files", + ["Main_beauty.1001.v001.exr"]) + def test_validate_sequence_frames_single_frame_wrong_name(self, instance, + plugin, + files): + # tests for names with number inside, caused clique failure before + representations = [ + { + "ext": "exr", + "files": files, + } + ] + instance.data["representations"] = representations + + with pytest.raises(ValueError) as excinfo: + plugin.process(instance) + assert ("Invalid frame range: (1, 1) - expected: (1001, 1001)" in + str(excinfo.value)) + + def test_validate_sequence_frames_multi_frame(self, instance, plugin): + representations = [ + { + "ext": "exr", + "files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr", + "Main_beauty.1003.exr"] + } + ] + instance.data["representations"] = representations + instance.data["frameEnd"] = 1003 + + plugin.process(instance) + + def test_validate_sequence_frames_multi_frame_missing(self, instance, + plugin): + representations = [ + { + "ext": "exr", + "files": ["Main_beauty.1001.exr", "Main_beauty.1002.exr"] + } + ] + instance.data["representations"] = representations + instance.data["frameEnd"] = 1003 + + with pytest.raises(ValueError) as excinfo: + plugin.process(instance) + assert ("Invalid frame range: (1001, 1002) - expected: (1001, 1003)" in + str(excinfo.value)) + + def test_validate_sequence_frames_multi_frame_hole(self, instance, plugin): + representations = [ + { + "ext": "exr", + "files": ["Main_beauty.1001.exr", "Main_beauty.1003.exr"] + } + ] + instance.data["representations"] = representations + instance.data["frameEnd"] = 1003 + + with pytest.raises(AssertionError) as excinfo: + plugin.process(instance) + assert ("Missing frames: [1002]" in str(excinfo.value)) + +test_case = TestValidateSequenceFrames() From 87fe237aa718d5e387b288bb7ad430d586712ec6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 24 Mar 2023 11:14:51 +0100 Subject: [PATCH 134/428] OP-3951 - fix - do not change files in place Integrator depends that single files is not in list. --- openpype/plugins/publish/validate_sequence_frames.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index 56641fbf57..98df259cc8 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -27,16 +27,17 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): representations = instance.data.get("representations") for repr in representations: - if isinstance(repr["files"], str): - repr["files"] = [repr["files"]] + repr_files = repr["files"] + if isinstance(repr_files, str): + repr_files = [repr_files] - _, ext = os.path.splitext(repr["files"][0]) + _, ext = os.path.splitext(repr_files[0]) pattern = r"\D?(?P(?P0*)\d+){}$".format( re.escape(ext)) patterns = [pattern] collections, remainder = clique.assemble( - repr["files"], minimum_items=1, patterns=patterns) + repr_files, minimum_items=1, patterns=patterns) assert not remainder, "Must not have remainder" assert len(collections) == 1, "Must detect single collection" From f95007b660c9d573d9c41b4ec6169e022434daed Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 24 Mar 2023 11:15:27 +0100 Subject: [PATCH 135/428] OP-3951 - Hound --- .../openpype/plugins/publish/test_validate_sequence_frames.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py index 5580621cfb..8e690f4f65 100644 --- a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -124,7 +124,7 @@ class TestValidateSequenceFrames(BaseTest): with pytest.raises(ValueError) as excinfo: plugin.process(instance) assert ("Invalid frame range: (1001, 1002) - expected: (1001, 1003)" in - str(excinfo.value)) + str(excinfo.value)) def test_validate_sequence_frames_multi_frame_hole(self, instance, plugin): representations = [ @@ -140,4 +140,5 @@ class TestValidateSequenceFrames(BaseTest): plugin.process(instance) assert ("Missing frames: [1002]" in str(excinfo.value)) + test_case = TestValidateSequenceFrames() From 30916962c8bc0f6fbfb557c286014efe62065bd2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 24 Mar 2023 12:34:23 +0100 Subject: [PATCH 136/428] OP-3951 - refactor Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/validate_sequence_frames.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index 98df259cc8..409d9ac17e 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -26,6 +26,8 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): representations = instance.data.get("representations") + if not representations: + return for repr in representations: repr_files = repr["files"] if isinstance(repr_files, str): From bc50139ec8ef7d93636f7ccfc861bd98dbdc2ba0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Mar 2023 15:20:01 +0000 Subject: [PATCH 137/428] Only parent to world on extraction if nested. --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 0cc842b4ec..fb097ca84a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -65,9 +65,10 @@ class ExtractXgen(publish.Extractor): ) cmds.delete(set(children) - set(shapes)) - duplicate_transform = cmds.parent( - duplicate_transform, world=True - )[0] + if cmds.listRelatives(duplicate_transform, parent=True): + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] duplicate_nodes.append(duplicate_transform) From 399541602898ce342f3f8639a1969a144c9824c7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Mar 2023 15:27:33 +0000 Subject: [PATCH 138/428] Validation for required namespace. --- .../hosts/maya/plugins/publish/validate_xgen.py | 13 +++++++++++++ website/docs/artist_hosts_maya_xgen.md | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 2870909974..47b24e218c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -57,3 +57,16 @@ class ValidateXgen(pyblish.api.InstancePlugin): json.dumps(inactive_modifiers, indent=4, sort_keys=True) ) ) + + # We need a namespace else there will be a naming conflict when + # extracting because of stripping namespaces and parenting to world. + node_names = [instance.data["xgmPalette"]] + for _, connections in instance.data["xgenConnections"].items(): + node_names.append(connections["transform"].split(".")[0]) + + non_namespaced_nodes = [n for n in node_names if ":" not in n] + if non_namespaced_nodes: + raise PublishValidationError( + "Could not find namespace on {}. Namespace is required for" + " xgen publishing.".format(non_namespaced_nodes) + ) diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index ec5f2ed921..db7bbd0557 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -43,6 +43,10 @@ Create an Xgen instance to publish. This needs to contain only **one Xgen collec You can create multiple Xgen instances if you have multiple collections to publish. +:::note +The Xgen publishing requires a namespace on the Xgen collection (palette) and the geometry used. +::: + ### Publish The publishing process will grab geometry used for Xgen along with any external files used in the collection's descriptions. This creates an isolated Maya file with just the Xgen collection's dependencies, so you can use any nested geometry when creating the Xgen description. An Xgen version will consist of: From 94ceba790edf1cb4c8d04f9ed67ab518f6722455 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 24 Mar 2023 16:49:43 +0100 Subject: [PATCH 139/428] OP-3951 - updated tests --- .../publish/test_validate_sequence_frames.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py index 8e690f4f65..232557b76d 100644 --- a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -63,7 +63,8 @@ class TestValidateSequenceFrames(BaseTest): @pytest.mark.parametrize("files", ["Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr", - "Main_beauty.1001.1001.exr"]) + "Main_beauty.1001.1001.exr", + "Main_beauty_v001_1001.exr"]) def test_validate_sequence_frames_single_frame_name(self, instance, plugin, files): @@ -97,6 +98,24 @@ class TestValidateSequenceFrames(BaseTest): assert ("Invalid frame range: (1, 1) - expected: (1001, 1001)" in str(excinfo.value)) + @pytest.mark.parametrize("files", + ["Main_beauty.1001.v001.ass.gz"]) + def test_validate_sequence_frames_single_frame_possible_wrong_name(self, + instance, plugin, files): + # currently pattern fails on extensions with dots + representations = [ + { + "ext": "exr", + "files": files, + } + ] + instance.data["representations"] = representations + + with pytest.raises(AssertionError) as excinfo: + plugin.process(instance) + assert ("Must not have remainder" in + str(excinfo.value)) + def test_validate_sequence_frames_multi_frame(self, instance, plugin): representations = [ { From 7dc59ece7b245e3bf47daf5bb7cbbd76cf49cf33 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 09:46:35 +0100 Subject: [PATCH 140/428] Define settings --- .../projects_schema/schema_project_maya.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 47dfb37024..80e2d43411 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -10,6 +10,41 @@ "key": "open_workfile_post_initialization", "label": "Open Workfile Post Initialization" }, + { + "type": "dict", + "key": "explicit_plugins_loading", + "label": "Explicit Plugins Loading", + "collapsible": true, + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "plugins_to_load", + "label": "Plugins To Load", + "object_type": { + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "name", + "label": "Name" + } + ] + } + } + ] + }, { "key": "imageio", "type": "dict", From 5349579f748bc1522d0ade1ef24da3c07337ad7c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 09:46:46 +0100 Subject: [PATCH 141/428] Define setting defaults --- .../defaults/project_settings/maya.json | 409 ++++++++++++++++++ 1 file changed, 409 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 2aa95fd1be..cc3a76c599 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,5 +1,414 @@ { "open_workfile_post_initialization": false, + "explicit_plugins_loading": { + "enabled": false, + "plugins_to_load": [ + { + "enabled": false, + "name": "AbcBullet" + }, + { + "enabled": true, + "name": "AbcExport" + }, + { + "enabled": true, + "name": "AbcImport" + }, + { + "enabled": false, + "name": "animImportExport" + }, + { + "enabled": false, + "name": "ArubaTessellator" + }, + { + "enabled": false, + "name": "ATFPlugin" + }, + { + "enabled": false, + "name": "atomImportExport" + }, + { + "enabled": false, + "name": "AutodeskPacketFile" + }, + { + "enabled": false, + "name": "autoLoader" + }, + { + "enabled": false, + "name": "bifmeshio" + }, + { + "enabled": false, + "name": "bifrostGraph" + }, + { + "enabled": false, + "name": "bifrostshellnode" + }, + { + "enabled": false, + "name": "bifrostvisplugin" + }, + { + "enabled": false, + "name": "blast2Cmd" + }, + { + "enabled": false, + "name": "bluePencil" + }, + { + "enabled": false, + "name": "Boss" + }, + { + "enabled": false, + "name": "bullet" + }, + { + "enabled": true, + "name": "cacheEvaluator" + }, + { + "enabled": false, + "name": "cgfxShader" + }, + { + "enabled": false, + "name": "cleanPerFaceAssignment" + }, + { + "enabled": false, + "name": "clearcoat" + }, + { + "enabled": false, + "name": "convertToComponentTags" + }, + { + "enabled": false, + "name": "curveWarp" + }, + { + "enabled": false, + "name": "ddsFloatReader" + }, + { + "enabled": true, + "name": "deformerEvaluator" + }, + { + "enabled": false, + "name": "dgProfiler" + }, + { + "enabled": false, + "name": "drawUfe" + }, + { + "enabled": false, + "name": "dx11Shader" + }, + { + "enabled": false, + "name": "fbxmaya" + }, + { + "enabled": false, + "name": "fltTranslator" + }, + { + "enabled": false, + "name": "freeze" + }, + { + "enabled": false, + "name": "Fur" + }, + { + "enabled": false, + "name": "gameFbxExporter" + }, + { + "enabled": false, + "name": "gameInputDevice" + }, + { + "enabled": false, + "name": "GamePipeline" + }, + { + "enabled": false, + "name": "gameVertexCount" + }, + { + "enabled": false, + "name": "geometryReport" + }, + { + "enabled": false, + "name": "geometryTools" + }, + { + "enabled": false, + "name": "glslShader" + }, + { + "enabled": true, + "name": "GPUBuiltInDeformer" + }, + { + "enabled": false, + "name": "gpuCache" + }, + { + "enabled": false, + "name": "hairPhysicalShader" + }, + { + "enabled": false, + "name": "ik2Bsolver" + }, + { + "enabled": false, + "name": "ikSpringSolver" + }, + { + "enabled": false, + "name": "invertShape" + }, + { + "enabled": false, + "name": "lges" + }, + { + "enabled": false, + "name": "lookdevKit" + }, + { + "enabled": false, + "name": "MASH" + }, + { + "enabled": false, + "name": "matrixNodes" + }, + { + "enabled": false, + "name": "mayaCharacterization" + }, + { + "enabled": false, + "name": "mayaHIK" + }, + { + "enabled": false, + "name": "MayaMuscle" + }, + { + "enabled": false, + "name": "mayaUsdPlugin" + }, + { + "enabled": false, + "name": "mayaVnnPlugin" + }, + { + "enabled": false, + "name": "melProfiler" + }, + { + "enabled": false, + "name": "meshReorder" + }, + { + "enabled": false, + "name": "modelingToolkit" + }, + { + "enabled": false, + "name": "mtoa" + }, + { + "enabled": false, + "name": "mtoh" + }, + { + "enabled": false, + "name": "nearestPointOnMesh" + }, + { + "enabled": true, + "name": "objExport" + }, + { + "enabled": false, + "name": "OneClick" + }, + { + "enabled": false, + "name": "OpenEXRLoader" + }, + { + "enabled": false, + "name": "pgYetiMaya" + }, + { + "enabled": false, + "name": "pgyetiVrayMaya" + }, + { + "enabled": false, + "name": "polyBoolean" + }, + { + "enabled": false, + "name": "poseInterpolator" + }, + { + "enabled": false, + "name": "quatNodes" + }, + { + "enabled": false, + "name": "randomizerDevice" + }, + { + "enabled": false, + "name": "redshift4maya" + }, + { + "enabled": true, + "name": "renderSetup" + }, + { + "enabled": false, + "name": "retargeterNodes" + }, + { + "enabled": false, + "name": "RokokoMotionLibrary" + }, + { + "enabled": false, + "name": "rotateHelper" + }, + { + "enabled": false, + "name": "sceneAssembly" + }, + { + "enabled": false, + "name": "shaderFXPlugin" + }, + { + "enabled": false, + "name": "shotCamera" + }, + { + "enabled": false, + "name": "snapTransform" + }, + { + "enabled": false, + "name": "stage" + }, + { + "enabled": true, + "name": "stereoCamera" + }, + { + "enabled": false, + "name": "stlTranslator" + }, + { + "enabled": false, + "name": "studioImport" + }, + { + "enabled": false, + "name": "Substance" + }, + { + "enabled": false, + "name": "substancelink" + }, + { + "enabled": false, + "name": "substancemaya" + }, + { + "enabled": false, + "name": "substanceworkflow" + }, + { + "enabled": false, + "name": "svgFileTranslator" + }, + { + "enabled": false, + "name": "sweep" + }, + { + "enabled": false, + "name": "testify" + }, + { + "enabled": false, + "name": "tiffFloatReader" + }, + { + "enabled": false, + "name": "timeSliderBookmark" + }, + { + "enabled": false, + "name": "Turtle" + }, + { + "enabled": false, + "name": "Type" + }, + { + "enabled": false, + "name": "udpDevice" + }, + { + "enabled": false, + "name": "ufeSupport" + }, + { + "enabled": false, + "name": "Unfold3D" + }, + { + "enabled": false, + "name": "VectorRender" + }, + { + "enabled": false, + "name": "vrayformaya" + }, + { + "enabled": false, + "name": "vrayvolumegrid" + }, + { + "enabled": false, + "name": "xgenToolkit" + }, + { + "enabled": false, + "name": "xgenVray" + } + ] + }, "imageio": { "ocio_config": { "enabled": false, From f99c968df3dca4949e2e12a24ee908d5bf1ca997 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 09:48:13 +0100 Subject: [PATCH 142/428] Add launch arguments and env --- openpype/hooks/pre_add_last_workfile_arg.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 2558daef30..3d5f59cc67 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -44,10 +44,20 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): # Determine whether to open workfile post initialization. if self.host_name == "maya": - key = "open_workfile_post_initialization" - if self.data["project_settings"]["maya"][key]: + maya_settings = self.data["project_settings"]["maya"] + + if maya_settings["explicit_plugins_loading"]["enabled"]: + self.log.debug("Explicit plugins loading.") + self.launch_context.launch_args.append("-noAutoloadPlugins") + + keys = [ + "open_workfile_post_initialization", "explicit_plugins_loading" + ] + values = [maya_settings[k] for k in keys] + if any(values): self.log.debug("Opening workfile post initialization.") - self.data["env"]["OPENPYPE_" + key.upper()] = "1" + key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" + self.data["env"][key] = "1" return # Add path to workfile to arguments From ae4468bd209144fa406ba17b15a5c4d54c147516 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 09:48:25 +0100 Subject: [PATCH 143/428] Load plugins explicitly --- openpype/hosts/maya/startup/userSetup.py | 34 +++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index c77ecb829e..4932bf14c0 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,5 +1,4 @@ import os -from functools import partial from openpype.settings import get_project_settings from openpype.pipeline import install_host @@ -13,23 +12,40 @@ install_host(host) print("Starting OpenPype usersetup...") +settings = get_project_settings(os.environ['AVALON_PROJECT']) + +# Loading plugins explicitly. +if settings["maya"]["explicit_plugins_loading"]["enabled"]: + def _explicit_load_plugins(): + project_settings = get_project_settings(os.environ["AVALON_PROJECT"]) + maya_settings = project_settings["maya"] + explicit_plugins_loading = maya_settings["explicit_plugins_loading"] + if explicit_plugins_loading["enabled"]: + for plugin in explicit_plugins_loading["plugins_to_load"]: + if plugin["enabled"]: + print("Loading " + plugin["name"]) + try: + cmds.loadPlugin(plugin["name"], quiet=True) + except RuntimeError as e: + print(e) + + cmds.evalDeferred( + _explicit_load_plugins, + lowestPriority=True + ) # Open Workfile Post Initialization. key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" if bool(int(os.environ.get(key, "0"))): + def _log_and_open(): + print("Opening \"{}\"".format(os.environ["AVALON_LAST_WORKFILE"])) + cmds.file(os.environ["AVALON_LAST_WORKFILE"], open=True, force=True) cmds.evalDeferred( - partial( - cmds.file, - os.environ["AVALON_LAST_WORKFILE"], - open=True, - force=True - ), + _log_and_open, lowestPriority=True ) - # Build a shelf. -settings = get_project_settings(os.environ['AVALON_PROJECT']) shelf_preset = settings['maya'].get('project_shelf') if shelf_preset: From 53361fe9dfae15bd4a1f318561dd4ea8393ce353 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 09:53:41 +0100 Subject: [PATCH 144/428] Modeling Toolkit is default loaded. --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index cc3a76c599..9b71b97d75 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -228,7 +228,7 @@ "name": "meshReorder" }, { - "enabled": false, + "enabled": true, "name": "modelingToolkit" }, { From 4bfb4aa75779cdd75d380cb0a976b5cb1757cbbd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 10:03:53 +0100 Subject: [PATCH 145/428] Docs --- website/docs/admin_hosts_maya.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md index 23cacb4193..edbfa8da36 100644 --- a/website/docs/admin_hosts_maya.md +++ b/website/docs/admin_hosts_maya.md @@ -172,3 +172,12 @@ Fill in the necessary fields (the optional fields are regex filters) - Build your workfile ![maya build template](assets/maya-build_workfile_from_template.png) + +## Explicit Plugins Loading +You can define which plugins to load on launch of Maya here; `project_settings/maya/explicit_plugins_loading`. This can help improve Maya's launch speed, if you know which plugins are needed. + +By default only the required plugins are enabled. You can also add any plugin to the list to enable on launch. + +:::note technical +When enabling this feature, the workfile will be launched post initialization no matter the setting on `project_settings/maya/open_workfile_post_initialization`. This is to avoid any issues with references needing plugins. +::: From 4dd58e15d89383a870890562e9f084ee3fb189bf Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 27 Mar 2023 11:13:04 +0100 Subject: [PATCH 146/428] Fixed error on rendering --- openpype/hosts/unreal/api/rendering.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index 5ef4792000..e197f9075d 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -134,6 +134,9 @@ def start_rendering(): settings.file_name_format = f"{shot_name}" + ".{frame_number}" settings.output_directory.path = f"{render_dir}/{output_dir}" + job.get_configuration().find_or_add_setting_by_class( + unreal.MoviePipelineDeferredPassBase) + job.get_configuration().find_or_add_setting_by_class( unreal.MoviePipelineImageSequenceOutput_PNG) From 45ea981efb5af84deaae232a8737e0aae6abab21 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 27 Mar 2023 11:15:20 +0100 Subject: [PATCH 147/428] Added setting for rendering format --- openpype/hosts/unreal/api/rendering.py | 18 +++++++++++++++--- .../defaults/project_settings/unreal.json | 1 + .../projects_schema/schema_project_unreal.json | 12 ++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index e197f9075d..a2be041c18 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -33,7 +33,7 @@ def start_rendering(): """ Start the rendering process. """ - print("Starting rendering...") + unreal.log("Starting rendering...") # Get selected sequences assets = unreal.EditorUtilityLibrary.get_selected_assets() @@ -137,8 +137,20 @@ def start_rendering(): job.get_configuration().find_or_add_setting_by_class( unreal.MoviePipelineDeferredPassBase) - job.get_configuration().find_or_add_setting_by_class( - unreal.MoviePipelineImageSequenceOutput_PNG) + render_format = data.get("unreal").get("render_format", "png") + + if render_format == "png": + job.get_configuration().find_or_add_setting_by_class( + unreal.MoviePipelineImageSequenceOutput_PNG) + elif render_format == "exr": + job.get_configuration().find_or_add_setting_by_class( + unreal.MoviePipelineImageSequenceOutput_EXR) + elif render_format == "jpg": + job.get_configuration().find_or_add_setting_by_class( + unreal.MoviePipelineImageSequenceOutput_JPG) + elif render_format == "bmp": + job.get_configuration().find_or_add_setting_by_class( + unreal.MoviePipelineImageSequenceOutput_BMP) # If there are jobs in the queue, start the rendering process. if queue.get_jobs(): diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index ff290ef254..737a17d289 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -13,6 +13,7 @@ "delete_unmatched_assets": false, "render_config_path": "", "preroll_frames": 0, + "render_format": "png", "project_setup": { "dev_mode": true } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 40bbb40ccc..35eb0b24f1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -42,6 +42,18 @@ "key": "preroll_frames", "label": "Pre-roll frames" }, + { + "key": "render_format", + "label": "Render format", + "type": "enum", + "multiselection": false, + "enum_items": [ + {"png": "PNG"}, + {"exr": "EXR"}, + {"jpg": "JPG"}, + {"bmp": "BMP"} + ] + }, { "type": "dict", "collapsible": true, From a579dfc860b7e22d344c617afefef37899dae994 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 27 Mar 2023 12:31:02 +0100 Subject: [PATCH 148/428] Get the correct frame range data --- .../hosts/unreal/plugins/publish/validate_sequence_frames.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py b/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py index 87f1338ee8..e6584e130f 100644 --- a/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py +++ b/openpype/hosts/unreal/plugins/publish/validate_sequence_frames.py @@ -20,6 +20,7 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): representations = instance.data.get("representations") for repr in representations: + data = instance.data.get("assetEntity", {}).get("data", {}) patterns = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble( repr["files"], minimum_items=1, patterns=patterns) @@ -30,8 +31,8 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): frames = list(collection.indexes) current_range = (frames[0], frames[-1]) - required_range = (instance.data["frameStart"], - instance.data["frameEnd"]) + required_range = (data["frameStart"], + data["frameEnd"]) if current_range != required_range: raise ValueError(f"Invalid frame range: {current_range} - " From 93d98c3f9c48da9477cd6ac7ffda186b57a202b8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 27 Mar 2023 16:55:19 +0200 Subject: [PATCH 149/428] OP-3951 - use ext on representation Without it ass.gz files won't work --- .../publish/validate_sequence_frames.py | 6 ++++- .../publish/test_validate_sequence_frames.py | 22 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index 409d9ac17e..a804a6d9dd 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -33,7 +33,11 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): if isinstance(repr_files, str): repr_files = [repr_files] - _, ext = os.path.splitext(repr_files[0]) + ext = repr.get("ext") + if not ext: + _, ext = os.path.splitext(repr_files[0]) + elif not ext.startswith("."): + ext = ".{}".format(ext) pattern = r"\D?(?P(?P0*)\d+){}$".format( re.escape(ext)) patterns = [pattern] diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py index 232557b76d..e1facdc37b 100644 --- a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -99,13 +99,12 @@ class TestValidateSequenceFrames(BaseTest): str(excinfo.value)) @pytest.mark.parametrize("files", - ["Main_beauty.1001.v001.ass.gz"]) - def test_validate_sequence_frames_single_frame_possible_wrong_name(self, - instance, plugin, files): + ["Main_beauty.v001.1001.ass.gz"]) + def test_validate_sequence_frames_single_frame_possible_wrong_name( + self, instance, plugin, files): # currently pattern fails on extensions with dots representations = [ { - "ext": "exr", "files": files, } ] @@ -116,6 +115,21 @@ class TestValidateSequenceFrames(BaseTest): assert ("Must not have remainder" in str(excinfo.value)) + @pytest.mark.parametrize("files", + ["Main_beauty.v001.1001.ass.gz"]) + def test_validate_sequence_frames_single_frame_correct_ext( + self, instance, plugin, files): + # currently pattern fails on extensions with dots + representations = [ + { + "ext": "ass.gz", + "files": files, + } + ] + instance.data["representations"] = representations + + plugin.process(instance) + def test_validate_sequence_frames_multi_frame(self, instance, plugin): representations = [ { From 6d2a45e9516ef55b84a181d0b30a8abe5405afbd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 16:42:35 +0100 Subject: [PATCH 150/428] Move -noAutoLoadPlugins flag to separate hook. --- openpype/hooks/pre_add_last_workfile_arg.py | 7 +------ .../hosts/maya/hooks/pre_auto_load_plugins.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 openpype/hosts/maya/hooks/pre_auto_load_plugins.py diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 3d5f59cc67..df4aa5cc5d 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -44,15 +44,10 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): # Determine whether to open workfile post initialization. if self.host_name == "maya": - maya_settings = self.data["project_settings"]["maya"] - - if maya_settings["explicit_plugins_loading"]["enabled"]: - self.log.debug("Explicit plugins loading.") - self.launch_context.launch_args.append("-noAutoloadPlugins") - keys = [ "open_workfile_post_initialization", "explicit_plugins_loading" ] + maya_settings = self.data["project_settings"]["maya"] values = [maya_settings[k] for k in keys] if any(values): self.log.debug("Opening workfile post initialization.") diff --git a/openpype/hosts/maya/hooks/pre_auto_load_plugins.py b/openpype/hosts/maya/hooks/pre_auto_load_plugins.py new file mode 100644 index 0000000000..3c3ddbe4dc --- /dev/null +++ b/openpype/hosts/maya/hooks/pre_auto_load_plugins.py @@ -0,0 +1,15 @@ +from openpype.lib import PreLaunchHook + + +class PreAutoLoadPlugins(PreLaunchHook): + """Define -noAutoloadPlugins command flag.""" + + # Execute before workfile argument. + order = 0 + app_groups = ["maya"] + + def execute(self): + maya_settings = self.data["project_settings"]["maya"] + if maya_settings["explicit_plugins_loading"]["enabled"]: + self.log.debug("Explicit plugins loading.") + self.launch_context.launch_args.append("-noAutoloadPlugins") From 72af67fc657fd7e52eca302d86f887c9af041212 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 27 Mar 2023 16:42:45 +0100 Subject: [PATCH 151/428] Warn about render farm support. --- website/docs/admin_hosts_maya.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md index edbfa8da36..5211760632 100644 --- a/website/docs/admin_hosts_maya.md +++ b/website/docs/admin_hosts_maya.md @@ -180,4 +180,6 @@ By default only the required plugins are enabled. You can also add any plugin to :::note technical When enabling this feature, the workfile will be launched post initialization no matter the setting on `project_settings/maya/open_workfile_post_initialization`. This is to avoid any issues with references needing plugins. + +Renderfarm integration is not supported for this feature. ::: From c20f45e88136371dd2a8a35eca66cf28f7ac3ee8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 27 Mar 2023 23:48:27 +0800 Subject: [PATCH 152/428] skip unrelated script --- openpype/hosts/max/plugins/load/load_camera_fbx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 205e815dc8..3a6947798e 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -36,6 +36,8 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) + container_name = f"{name}_CON" + asset = rt.getNodeByName(f"{name}") return containerise( From 32bb42e37922dd2de79f01c6e133b17ee8e7c6fa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 17:26:04 +0800 Subject: [PATCH 153/428] update the obj loader and add maintained_selection for loaders --- openpype/hosts/max/plugins/load/load_model.py | 12 ++++++++---- openpype/hosts/max/plugins/load/load_model_fbx.py | 4 ++++ openpype/hosts/max/plugins/load/load_model_obj.py | 12 ++++++++---- openpype/hosts/max/plugins/load/load_model_usd.py | 4 ++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index c248d75718..95ee014e07 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -5,6 +5,7 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib +from openpype.hosts.max.api.lib import maintained_selection class ModelAbcLoader(load.LoaderPlugin): @@ -57,12 +58,8 @@ importFile @"{file_path}" #noPrompt def update(self, container, representation): from pymxs import runtime as rt - path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) - }) rt.select(node.Children) for alembic in rt.selection: @@ -76,6 +73,13 @@ importFile @"{file_path}" #noPrompt alembic_obj = rt.getNodeByName(abc_obj.name) alembic_obj.source = path + with maintained_selection(): + rt.select(node) + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index d8f4011277..88b8f1ed89 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -5,6 +5,7 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib +from openpype.hosts.max.api.lib import maintained_selection class FbxModelLoader(load.LoaderPlugin): @@ -59,6 +60,9 @@ importFile @"{path}" #noPrompt using:FBXIMP """) rt.execute(fbx_reimport_cmd) + with maintained_selection(): + rt.select(node) + lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) diff --git a/openpype/hosts/max/plugins/load/load_model_obj.py b/openpype/hosts/max/plugins/load/load_model_obj.py index 63ae058ae0..c55e462111 100644 --- a/openpype/hosts/max/plugins/load/load_model_obj.py +++ b/openpype/hosts/max/plugins/load/load_model_obj.py @@ -5,6 +5,7 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib +from openpype.hosts.max.api.lib import maintained_selection class ObjLoader(load.LoaderPlugin): @@ -42,16 +43,19 @@ class ObjLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] node = rt.getNodeByName(node_name) + instance_name, _ = node_name.split("_") + container = rt.getNodeByName(instance_name) + for n in container.Children: + rt.delete(n) rt.execute(f'importFile @"{path}" #noPrompt using:ObjImp') - # create "missing" container for obj import - container = rt.container() - container.name = f"{instance_name}" # get current selection for selection in rt.getCurrentSelection(): selection.Parent = container - container.Parent = node + + with maintained_selection(): + rt.select(node) lib.imprint(node_name, { "representation": str(representation["_id"]) diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py index 2237426187..143f91f40b 100644 --- a/openpype/hosts/max/plugins/load/load_model_usd.py +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -4,6 +4,7 @@ from openpype.pipeline import ( ) from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib +from openpype.hosts.max.api.lib import maintained_selection class ModelUSDLoader(load.LoaderPlugin): @@ -60,6 +61,9 @@ class ModelUSDLoader(load.LoaderPlugin): asset = rt.getNodeByName(f"{instance_name}") asset.Parent = node + with maintained_selection(): + rt.select(node) + lib.imprint(node_name, { "representation": str(representation["_id"]) }) From c5172b74101dda2be4a797c0bc61056dfa134569 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 29 Mar 2023 13:17:24 +0200 Subject: [PATCH 154/428] OP-3951 - update imports Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/validate_sequence_frames.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index a804a6d9dd..db71f2e23c 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -1,8 +1,7 @@ -import os.path - -import clique +import os import re +import clique import pyblish.api From 36328f8b1abbb03051795646bec8ceed5d78a874 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 29 Mar 2023 13:18:07 +0200 Subject: [PATCH 155/428] OP-3951 - removed unnecessary key Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- tests/unit/openpype/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/openpype/conftest.py b/tests/unit/openpype/conftest.py index e5355a3120..220ec3ba86 100644 --- a/tests/unit/openpype/conftest.py +++ b/tests/unit/openpype/conftest.py @@ -33,6 +33,5 @@ if not os.environ.get("IS_TEST"): # running tests from cmd or CI os.environ["OPENPYPE_DATABASE_NAME"] = "avalon" os.environ["AVALON_TIMEOUT"] = '3000' os.environ["OPENPYPE_DEBUG"] = "3" - os.environ["AVALON_CONFIG"] = "pype" os.environ["AVALON_ASSET"] = "test_asset" os.environ["AVALON_PROJECT"] = "test_project" From e2546e5547b10d085ff6b57375355361e8bda5f7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 29 Mar 2023 13:18:37 +0200 Subject: [PATCH 156/428] OP-3951 - removed unnecessary key Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- tests/unit/openpype/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/openpype/conftest.py b/tests/unit/openpype/conftest.py index 220ec3ba86..0bc3f0dcfe 100644 --- a/tests/unit/openpype/conftest.py +++ b/tests/unit/openpype/conftest.py @@ -27,7 +27,6 @@ import os if not os.environ.get("IS_TEST"): # running tests from cmd or CI - os.environ["AVALON_MONGO"] = "mongodb://localhost:27017" os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017" os.environ["AVALON_DB"] = "avalon" os.environ["OPENPYPE_DATABASE_NAME"] = "avalon" From 0a642d475cda961e033f2b4aa58e5dd09e7ec781 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 29 Mar 2023 13:19:02 +0200 Subject: [PATCH 157/428] OP-3951 - changed debug value Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- tests/unit/openpype/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/openpype/conftest.py b/tests/unit/openpype/conftest.py index 0bc3f0dcfe..9b5be54a0d 100644 --- a/tests/unit/openpype/conftest.py +++ b/tests/unit/openpype/conftest.py @@ -31,6 +31,6 @@ if not os.environ.get("IS_TEST"): # running tests from cmd or CI os.environ["AVALON_DB"] = "avalon" os.environ["OPENPYPE_DATABASE_NAME"] = "avalon" os.environ["AVALON_TIMEOUT"] = '3000' - os.environ["OPENPYPE_DEBUG"] = "3" + os.environ["OPENPYPE_DEBUG"] = "1" os.environ["AVALON_ASSET"] = "test_asset" os.environ["AVALON_PROJECT"] = "test_project" From 53d395e7087c2b4bd65c33706be25b3fb2dafb4d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 29 Mar 2023 13:19:21 +0200 Subject: [PATCH 158/428] OP-3951 - updated value Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- tests/unit/openpype/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/openpype/conftest.py b/tests/unit/openpype/conftest.py index 9b5be54a0d..0aec25becb 100644 --- a/tests/unit/openpype/conftest.py +++ b/tests/unit/openpype/conftest.py @@ -29,7 +29,7 @@ import os if not os.environ.get("IS_TEST"): # running tests from cmd or CI os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017" os.environ["AVALON_DB"] = "avalon" - os.environ["OPENPYPE_DATABASE_NAME"] = "avalon" + os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" os.environ["AVALON_TIMEOUT"] = '3000' os.environ["OPENPYPE_DEBUG"] = "1" os.environ["AVALON_ASSET"] = "test_asset" From 669a2256ef4093a399b6e9615c28da0094de7419 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 14:46:41 +0200 Subject: [PATCH 159/428] Merge develop --- .github/workflows/project_actions.yml | 22 + ARCHITECTURE.md | 77 + CHANGELOG.md | 1774 +++ openpype/cli.py | 8 +- openpype/hooks/pre_add_last_workfile_arg.py | 1 + .../hooks/pre_create_extra_workdir_folders.py | 9 +- openpype/hooks/pre_foundry_apps.py | 4 +- openpype/host/dirmap.py | 63 +- openpype/hosts/blender/api/__init__.py | 5 + openpype/hosts/blender/api/capture.py | 278 + openpype/hosts/blender/api/lib.py | 10 + openpype/hosts/blender/api/ops.py | 8 +- openpype/hosts/blender/api/plugin.py | 7 +- .../blender/plugins/create/create_review.py | 47 + .../blender/plugins/publish/collect_review.py | 64 + .../plugins/publish/extract_playblast.py | 123 + .../plugins/publish/extract_thumbnail.py | 99 + .../celaction/hooks/pre_celaction_setup.py | 36 +- .../publish/collect_celaction_cli_kwargs.py | 2 +- openpype/hosts/fusion/__init__.py | 4 + openpype/hosts/fusion/addon.py | 52 +- openpype/hosts/fusion/api/action.py | 13 +- openpype/hosts/fusion/api/lib.py | 14 +- openpype/hosts/fusion/api/menu.py | 13 - .../hosts/fusion/deploy/fusion_shared.prefs | 30 +- .../fusion/hooks/pre_fusion_profile_hook.py | 161 + .../hosts/fusion/hooks/pre_fusion_setup.py | 56 +- .../fusion/plugins/create/create_saver.py | 83 +- .../publish/collect_expected_frames.py | 50 + .../plugins/publish/collect_render_target.py | 44 - .../fusion/plugins/publish/collect_renders.py | 25 + .../plugins/publish/extract_render_local.py | 109 + .../fusion/plugins/publish/render_local.py | 100 - .../publish/validate_create_folder_checked.py | 14 +- .../validate_expected_frames_existence.py | 78 + .../hosts/fusion/scripts/set_rendermode.py | 112 - openpype/hosts/hiero/api/lib.py | 6 +- openpype/hosts/hiero/api/plugin.py | 20 +- .../hosts/houdini/startup/MainMenuCommon.xml | 8 +- openpype/hosts/max/api/lib.py | 111 + openpype/hosts/max/api/lib_renderproducts.py | 48 +- openpype/hosts/max/api/menu.py | 20 + openpype/hosts/max/api/pipeline.py | 5 + openpype/hosts/max/api/plugin.py | 4 +- .../max/plugins/create/create_maxScene.py | 26 + .../max/plugins/create/create_pointcloud.py | 26 + .../hosts/max/plugins/load/load_max_scene.py | 6 +- .../hosts/max/plugins/load/load_pointcloud.py | 51 + .../max/plugins/publish/collect_render.py | 2 +- .../plugins/publish/extract_max_scene_raw.py | 3 +- .../max/plugins/publish/extract_pointcloud.py | 207 + .../publish/increment_workfile_version.py | 19 + .../publish/validate_no_max_content.py | 23 + .../plugins/publish/validate_pointcloud.py | 191 + openpype/hosts/maya/api/customize.py | 3 +- openpype/hosts/maya/api/lib.py | 100 +- openpype/hosts/maya/api/lib_rendersettings.py | 5 +- openpype/hosts/maya/api/menu.py | 7 +- .../maya/plugins/create/create_animation.py | 3 +- .../maya/plugins/create/create_review.py | 2 + .../plugins/inventory/connect_geometry.py | 2 +- .../maya/plugins/inventory/connect_xgen.py | 2 +- .../plugins/inventory/connect_yeti_rig.py | 178 + .../maya/plugins/load/load_arnold_standin.py | 6 +- .../hosts/maya/plugins/load/load_image.py | 332 + openpype/hosts/maya/plugins/load/load_xgen.py | 2 +- .../hosts/maya/plugins/load/load_yeti_rig.py | 94 +- .../maya/plugins/publish/collect_review.py | 26 + .../maya/plugins/publish/extract_look.py | 101 +- .../maya/plugins/publish/extract_playblast.py | 126 +- .../maya/plugins/publish/extract_thumbnail.py | 52 +- .../publish/validate_look_color_space.py | 26 + .../publish/validate_rendersettings.py | 111 +- .../validate_yeti_renderscript_callbacks.py | 12 + openpype/hosts/maya/tools/__init__.py | 27 + .../maya}/tools/mayalookassigner/LICENSE | 0 .../maya}/tools/mayalookassigner/__init__.py | 0 .../maya}/tools/mayalookassigner/app.py | 0 .../maya}/tools/mayalookassigner/commands.py | 16 +- .../maya}/tools/mayalookassigner/models.py | 0 .../maya}/tools/mayalookassigner/views.py | 0 .../tools/mayalookassigner/vray_proxies.py | 0 .../maya}/tools/mayalookassigner/widgets.py | 0 openpype/hosts/nuke/api/lib.py | 54 +- .../hosts/nuke/plugins/load/load_backdrop.py | 22 +- .../nuke/plugins/publish/collect_backdrop.py | 32 +- .../plugins/publish/extract_review_data.py | 2 +- .../publish/extract_review_data_lut.py | 7 +- .../publish/extract_review_data_mov.py | 5 +- .../nuke/plugins/publish/extract_thumbnail.py | 2 +- openpype/hosts/photoshop/api/launch_logic.py | 4 +- openpype/hosts/resolve/api/menu.py | 8 +- .../hosts/tvpaint/api/communication_server.py | 4 +- .../tvpaint/plugins/create/convert_legacy.py | 4 +- .../tvpaint/plugins/create/create_render.py | 15 +- .../publish/collect_instance_frames.py | 2 + .../plugins/publish/validate_asset_name.py | 12 +- .../publish/validate_layers_visibility.py | 2 +- .../tvpaint/plugins/publish/validate_marks.py | 13 +- .../publish/validate_scene_settings.py | 13 +- .../plugins/publish/validate_start_frame.py | 13 +- openpype/hosts/unreal/ue_workers.py | 142 +- openpype/lib/applications.py | 18 +- openpype/lib/execute.py | 11 + openpype/lib/file_transaction.py | 22 +- openpype/lib/vendor_bin_utils.py | 14 +- openpype/modules/clockify/clockify_api.py | 363 +- openpype/modules/clockify/clockify_module.py | 173 +- openpype/modules/clockify/constants.py | 2 +- .../server/action_clockify_sync_server.py | 25 +- .../ftrack/user/action_clockify_sync_local.py | 26 +- .../launcher_actions/ClockifyStart.py | 48 +- .../clockify/launcher_actions/ClockifySync.py | 55 +- openpype/modules/clockify/widgets.py | 16 +- .../deadline/plugins/publish/collect_pools.py | 47 +- .../publish/submit_celaction_deadline.py | 2 +- .../plugins/publish/submit_max_deadline.py | 72 +- .../plugins/publish/submit_maya_deadline.py | 63 +- .../plugins/publish/submit_nuke_deadline.py | 10 +- .../plugins/publish/submit_publish_job.py | 4 + .../publish/validate_deadline_pools.py | 10 +- .../validate_expected_and_rendered_files.py | 11 +- .../custom/plugins/GlobalJobPreLoad.py | 8 +- .../OpenPypeTileAssembler.py | 97 +- .../example_addons/example_addon/addon.py | 2 +- .../action_push_frame_values_to_task.py | 316 +- .../event_push_frame_values_to_task.py | 1096 +- .../action_applications.py | 9 + .../plugins/publish/collect_ftrack_family.py | 91 +- .../plugins/publish/collect_kitsu_entities.py | 2 +- .../plugins/publish/integrate_kitsu_note.py | 109 +- .../plugins/publish/integrate_kitsu_review.py | 8 +- .../modules/kitsu/utils/update_op_with_zou.py | 34 +- .../modules/sync_server/sync_server_module.py | 20 +- .../modules/timers_manager/timers_manager.py | 4 +- openpype/pipeline/create/context.py | 91 +- openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 80 +- openpype/plugins/publish/cleanup.py | 4 + openpype/plugins/publish/cleanup_farm.py | 2 +- openpype/plugins/publish/collect_comment.py | 2 +- .../publish/collect_custom_staging_dir.py | 67 + openpype/plugins/publish/extract_burnin.py | 42 +- openpype/plugins/publish/integrate.py | 18 +- openpype/plugins/publish/integrate_legacy.py | 2 + .../preintegrate_thumbnail_representation.py | 2 + openpype/pype_commands.py | 5 +- openpype/scripts/otio_burnin.py | 125 +- .../defaults/project_anatomy/templates.json | 6 +- .../project_settings/applications.json | 3 + .../defaults/project_settings/blender.json | 88 + .../defaults/project_settings/celaction.json | 7 + .../defaults/project_settings/deadline.json | 7 +- .../defaults/project_settings/fusion.json | 5 + .../defaults/project_settings/global.json | 26 +- .../defaults/project_settings/kitsu.json | 10 +- .../defaults/project_settings/max.json | 15 + .../defaults/project_settings/maya.json | 11 +- .../defaults/project_settings/tvpaint.json | 1 + .../system_settings/applications.json | 2 +- openpype/settings/entities/schemas/README.md | 2 +- .../schemas/projects_schema/schema_main.json | 4 + .../schema_project_applications.json | 14 + .../schema_project_celaction.json | 25 + .../schema_project_deadline.json | 19 +- .../schema_project_fusion.json | 23 + .../projects_schema/schema_project_kitsu.json | 84 +- .../projects_schema/schema_project_max.json | 24 +- .../schema_project_tvpaint.json | 6 + .../schemas/schema_blender_publish.json | 62 +- .../schemas/schema_global_tools.json | 65 + .../schemas/schema_maya_capture.json | 11 +- .../schemas/schema_maya_create.json | 5 + .../schemas/schema_maya_publish.json | 16 +- openpype/tools/launcher/models.py | 6 + openpype/tools/loader/widgets.py | 6 +- .../project_manager/project_manager/view.py | 4 +- openpype/tools/publisher/constants.py | 5 +- openpype/tools/publisher/control.py | 185 +- openpype/tools/publisher/widgets/__init__.py | 6 +- .../publisher/widgets/card_view_widgets.py | 19 + .../tools/publisher/widgets/images/save.png | Bin 0 -> 3961 bytes .../publisher/widgets/list_view_widgets.py | 27 + .../publisher/widgets/overview_widget.py | 38 +- openpype/tools/publisher/widgets/widgets.py | 34 +- openpype/tools/publisher/window.py | 129 +- openpype/tools/sceneinventory/model.py | 22 +- openpype/tools/sceneinventory/view.py | 7 + openpype/tools/sceneinventory/window.py | 6 +- .../local_settings/projects_widget.py | 2 +- .../tools/settings/settings/search_dialog.py | 12 +- .../model_filter_proxy_recursive_sort.py | 6 +- openpype/tools/traypublisher/window.py | 2 +- openpype/tools/utils/delegates.py | 8 +- openpype/tools/utils/host_tools.py | 32 - openpype/tools/utils/lib.py | 4 +- openpype/tools/utils/models.py | 20 +- openpype/version.py | 2 +- openpype/widgets/splash_screen.py | 11 +- pyproject.toml | 2 +- tests/conftest.py | 10 + .../nuke/test_deadline_publish_in_nuke.py | 18 + .../hosts/nuke/test_publish_in_nuke.py | 20 +- tests/lib/testing_classes.py | 22 +- .../pipeline/publish/test_publish_plugins.py | 6 +- website/docs/admin_environment.md | 2 +- website/docs/admin_hosts_maya.md | 56 +- website/docs/admin_settings.md | 11 +- website/docs/admin_use.md | 2 +- website/docs/artist_hosts_3dsmax.md | 4 +- website/docs/artist_hosts_aftereffects.md | 6 +- website/docs/artist_hosts_harmony.md | 2 +- website/docs/artist_hosts_hiero.md | 6 +- website/docs/artist_hosts_maya.md | 24 +- website/docs/artist_hosts_maya_arnold.md | 2 +- website/docs/artist_hosts_maya_yeti.md | 79 +- website/docs/artist_hosts_photoshop.md | 2 +- website/docs/artist_tools_sync_queu.md | 2 +- ...armony_creator.PNG => harmony_creator.png} | Bin .../assets/integrate_kitsu_note_settings.png | Bin 0 -> 48874 bytes .../maya-admin_extract_playblast_settings.png | Bin 0 -> 26814 bytes ...ract_playblast_settings_camera_options.png | Bin 0 -> 16732 bytes ...ct_playblast_settings_viewport_options.png | Bin 0 -> 1064191 bytes .../maya-admin_render_settings_validator.png | Bin 11220 -> 11855 bytes website/docs/assets/maya-yeti_hair_setup.png | Bin 0 -> 138812 bytes .../assets/maya-yeti_load_connections.png | Bin 0 -> 125692 bytes .../docs/assets/maya-yeti_publish_setup.png | Bin 0 -> 133890 bytes website/docs/assets/maya-yeti_rig.jpg | Bin 59405 -> 0 bytes website/docs/assets/maya-yeti_rig_setup.png | Bin 0 -> 111360 bytes website/docs/assets/maya-yeti_simple_rig.png | Bin 0 -> 96188 bytes website/docs/dev_colorspace.md | 6 +- website/docs/dev_host_implementation.md | 4 +- website/docs/dev_publishing.md | 2 +- website/docs/dev_settings.md | 6 +- website/docs/dev_testing.md | 2 +- website/docs/manager_ftrack.md | 2 +- website/docs/module_deadline.md | 4 + website/docs/module_ftrack.md | 2 +- website/docs/module_kitsu.md | 17 +- website/docs/module_site_sync.md | 2 +- .../global_tools_custom_staging_dir.png | Bin 0 -> 9940 bytes .../settings_project_global.md | 45 +- .../project_settings/settings_project_nuke.md | 2 +- .../settings_project_standalone.md | 2 +- website/docs/pype2/admin_ftrack.md | 2 +- website/docs/pype2/admin_presets_plugins.md | 4 +- website/docs/system_introduction.md | 4 +- website/yarn.lock | 10884 ++++++++-------- 248 files changed, 13531 insertions(+), 8058 deletions(-) create mode 100644 .github/workflows/project_actions.yml create mode 100644 ARCHITECTURE.md create mode 100644 openpype/hosts/blender/api/capture.py create mode 100644 openpype/hosts/blender/plugins/create/create_review.py create mode 100644 openpype/hosts/blender/plugins/publish/collect_review.py create mode 100644 openpype/hosts/blender/plugins/publish/extract_playblast.py create mode 100644 openpype/hosts/blender/plugins/publish/extract_thumbnail.py create mode 100644 openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py create mode 100644 openpype/hosts/fusion/plugins/publish/collect_expected_frames.py delete mode 100644 openpype/hosts/fusion/plugins/publish/collect_render_target.py create mode 100644 openpype/hosts/fusion/plugins/publish/collect_renders.py create mode 100644 openpype/hosts/fusion/plugins/publish/extract_render_local.py delete mode 100644 openpype/hosts/fusion/plugins/publish/render_local.py create mode 100644 openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py delete mode 100644 openpype/hosts/fusion/scripts/set_rendermode.py create mode 100644 openpype/hosts/max/plugins/create/create_maxScene.py create mode 100644 openpype/hosts/max/plugins/create/create_pointcloud.py create mode 100644 openpype/hosts/max/plugins/load/load_pointcloud.py create mode 100644 openpype/hosts/max/plugins/publish/extract_pointcloud.py create mode 100644 openpype/hosts/max/plugins/publish/increment_workfile_version.py create mode 100644 openpype/hosts/max/plugins/publish/validate_no_max_content.py create mode 100644 openpype/hosts/max/plugins/publish/validate_pointcloud.py create mode 100644 openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py create mode 100644 openpype/hosts/maya/plugins/load/load_image.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_look_color_space.py create mode 100644 openpype/hosts/maya/tools/__init__.py rename openpype/{ => hosts/maya}/tools/mayalookassigner/LICENSE (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/__init__.py (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/app.py (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/commands.py (93%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/models.py (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/views.py (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/vray_proxies.py (100%) rename openpype/{ => hosts/maya}/tools/mayalookassigner/widgets.py (100%) create mode 100644 openpype/plugins/publish/collect_custom_staging_dir.py create mode 100644 openpype/settings/defaults/project_settings/applications.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_applications.json create mode 100644 openpype/tools/publisher/widgets/images/save.png rename website/docs/assets/{harmony_creator.PNG => harmony_creator.png} (100%) create mode 100644 website/docs/assets/integrate_kitsu_note_settings.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings_camera_options.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings_viewport_options.png create mode 100644 website/docs/assets/maya-yeti_hair_setup.png create mode 100644 website/docs/assets/maya-yeti_load_connections.png create mode 100644 website/docs/assets/maya-yeti_publish_setup.png delete mode 100644 website/docs/assets/maya-yeti_rig.jpg create mode 100644 website/docs/assets/maya-yeti_rig_setup.png create mode 100644 website/docs/assets/maya-yeti_simple_rig.png create mode 100644 website/docs/project_settings/assets/global_tools_custom_staging_dir.png diff --git a/.github/workflows/project_actions.yml b/.github/workflows/project_actions.yml new file mode 100644 index 0000000000..26bc2b8a1f --- /dev/null +++ b/.github/workflows/project_actions.yml @@ -0,0 +1,22 @@ +name: project-actions + +on: + pull_request: + types: [review_requested] + pull_request_review: + types: [submitted] + +jobs: + pr_review_requested: + name: pr_review_requested + runs-on: ubuntu-latest + if: github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' + steps: + - name: Move PR to 'Change Requested' + uses: leonsteinhaeuser/project-beta-automations@v2.1.0 + with: + gh_token: ${{ secrets.YNPUT_BOT_TOKEN }} + organization: ynput + project_id: 11 + resource_node_id: ${{ github.event.pull_request.node_id }} + status_value: Change Requested diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..912780d803 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# Architecture + +OpenPype is a monolithic Python project that bundles several parts, this document will try to give a birds eye overview of the project and, to a certain degree, each of the sub-projects. +The current file structure looks like this: + +``` +. +├── common - Code in this folder is backend portion of Addon distribution logic for v4 server. +├── docs - Documentation of the source code. +├── igniter - The OpenPype bootstrapper, deals with running version resolution and setting up the connection to the mongodb. +├── openpype - The actual OpenPype core package. +├── schema - Collection of JSON files describing schematics of objects. This follows Avalon's convention. +├── tests - Integration and unit tests. +├── tools - Conveninece scripts to perform common actions (in both bash and ps1). +├── vendor - When using the igniter, it deploys third party tools in here, such as ffmpeg. +└── website - Source files for https://openpype.io/ which is Docusaursus (https://docusaurus.io/). +``` + +The core functionality of the pipeline can be found in `igniter` and `openpype`, which in turn rely on the `schema` files, whenever you build (or download a pre-built) version of OpenPype, these two are bundled in there, and `Igniter` is the entry point. + + +## Igniter + +It's the setup and update tool for OpenPype, unless you want to package `openpype` separately and deal with all the config manually, this will most likely be your entry point. + +``` +igniter/ +├── bootstrap_repos.py - Module that will find or install OpenPype versions in the system. +├── __init__.py - Igniter entry point. +├── install_dialog.py- Show dialog for choosing central pype repository. +├── install_thread.py - Threading helpers for the install process. +├── __main__.py - Like `__init__.py` ? +├── message_dialog.py - Qt Dialog with a message and "Ok" button. +├── nice_progress_bar.py - Fancy Qt progress bar. +├── splash.txt - ASCII art for the terminal installer. +├── stylesheet.css - Installer Qt styles. +├── terminal_splash.py - Terminal installer animation, relies in `splash.txt`. +├── tools.py - Collection of methods that don't fit in other modules. +├── update_thread.py - Threading helper to update existing OpenPype installs. +├── update_window.py - Qt UI to update OpenPype installs. +├── user_settings.py - Interface for the OpenPype user settings. +└── version.py - Igniter's version number. +``` + +## OpenPype + +This is the main package of the OpenPype logic, it could be loosely described as a combination of [Avalon](https://getavalon.github.io), [Pyblish](https://pyblish.com/) and glue around those with custom OpenPype only elements, things are in progress of being moved around to better prepare for V4, which will be released under a new name AYON. + +``` +openpype/ +├── client - Interface for the MongoDB. +├── hooks - Hooks to be executed on certain OpenPype Applications defined in `openpype.lib.applications`. +├── host - Base class for the different hosts. +├── hosts - Integration with the different DCCs (hosts) using the `host` base class. +├── lib - Libraries that stitch together the package, some have been moved into other parts. +├── modules - OpenPype modules should contain separated logic of specific kind of implementation, such as Ftrack connection and its python API. +├── pipeline - Core of the OpenPype pipeline, handles creation of data, publishing, etc. +├── plugins - Global/core plugins for loader and publisher tool. +├── resources - Icons, fonts, etc. +├── scripts - Loose scipts that get run by tools/publishers. +├── settings - OpenPype settings interface. +├── style - Qt styling. +├── tests - Unit tests. +├── tools - Core tools, check out https://openpype.io/docs/artist_tools. +├── vendor - Vendoring of needed required Python packes. +├── widgets - Common re-usable Qt Widgets. +├── action.py - LEGACY: Lives now in `openpype.pipeline.publish.action` Pyblish actions. +├── cli.py - Command line interface, leverages `click`. +├── __init__.py - Sets two constants. +├── __main__.py - Entry point, calls the `cli.py` +├── plugin.py - Pyblish plugins. +├── pype_commands.py - Implementation of OpenPype commands. +└── version.py - Current version number. +``` + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ecbc83bf..4e22b783c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,1779 @@ # Changelog +## [3.15.3](https://github.com/ynput/OpenPype/tree/3.15.3) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.2...3.15.3) + +### **🆕 New features** + + +
+Blender: Extract Review #3616 + +Added Review to Blender. + +This implementation is based on #3508 but made compatible for the current implementation of OpenPype for Blender. + + +___ + +
+ + +
+Data Exchanges: Point Cloud for 3dsMax #4532 + +Publish PRT format with tyFlow in 3dsmax + +Publish PRT format with tyFlow in 3dsmax and possibly set up loader to load the format too. +- [x] creator +- [x] extractor +- [x] validator +- [x] loader + + +___ + +
+ + +
+Global: persistent staging directory for renders #4583 + +Allows configure if staging directory (`stagingDir`) should be persistent with use of profiles. + +With this feature, users can specify a transient data folder path based on presets, which can be used during the creation and publishing stages. In some cases, these DCCs automatically add a rendering path during the creation stage, which is then used in publishing.One of the key advantages of this feature is that it allows users to take advantage of faster storages for rendering, which can help improve workflow efficiency. Additionally, this feature allows users to keep their rendered data persistent, and use their own infrastructure for regular cleaning.However, it should be noted that some productions may want to use this feature without persistency. Furthermore, there may be a need for retargeting the rendering folder to faster storages, which is also not supported at the moment.It is studio responsibility to clean up obsolete folders with data.Location of the folder is configured in `project_anatomy/templates/others`. ('transient' key is expected, with 'folder' key, could be more templates)Which family/task type/subset is applicable is configured in:`project_settings/global/tools/publish/transient_dir_profiles` + + +___ + +
+ + +
+Kitsu custom comment template #4599 + +Kitsu allows to write markdown in its comment field. This can be something very powerful to deliver dynamic comments with the help the data from the instance.This feature is defaults to off so the admin have to manually set up the comment field the way they want.I have added a basic example on how the comment can look like as the comment-fields default value.To this I want to add some documentation also but that's on its way when the code itself looks good for the reviewers. + + +___ + +
+ + +
+MaxScene Family #4615 + +Introduction of the Max Scene Family + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Multiple values on single render attribute - OP-4131 #4631 + +When validating render attributes, this adds support for multiple values. When repairing first value in list is used. + + +___ + +
+ + +
+Maya: enable 2D Pan/Zoom for playblasts - OP-5213 #4687 + +Setting for enabling 2D Pan/Zoom on reviews. + + +___ + +
+ + +
+Copy existing or generate new Fusion profile on prelaunch #4572 + +Fusion preferences will be copied to the predefined `~/.openpype/hosts/fusion/prefs` folder (or any other folder set in system settings) on launch. + +The idea is to create a copy of existing Fusion profile, adding an OpenPype menu to the Fusion instance.By default the copy setting is turned off, so no file copying is performed. Instead the clean Fusion profile is created by Fusion in the predefined folder. The default locaion is set to `~/.openpype/hosts/fusion/prefs`, to better comply with the other os platforms. After creating the default profile, some modifications are applied: +- forced Python3 +- forced English interface +- setup Openpype specific path maps.If the `copy_prefs` checkbox is toggled, a copy of existing Fusion profile folder will be placed in the mentioned location. Then they are altered the same way as described above. The operation is run only once, on the first launch, unless the `force_sync [Resync profile on each launch]` is toggled.English interface is forced because the `FUSION16_PROFILE_DIR` environment variable is not read otherwise (seems to be a Fusion bug). + + +___ + +
+ + +
+Houdini: Create button open new publisher's "create" tab #4601 + +During a talk with @maxpareschi he mentioned that the new publisher in Houdini felt super confusing due to "Create" going to the older creator but now being completely empty and the publish button directly went to the publish tab.This resolves that by fixing the Create button to now open the new publisher but on the Create tab.Also made publish button enforce going to the "publish" tab for consistency in usage.@antirotor I think changing the Create button's callback was just missed in this commit or was there a specific reason to not change that around yet? + + +___ + +
+ + +
+Clockify: refresh and fix the integration #4607 + +Due to recent API changes, Clockify requires `user_id` to operate with the timers. I updated this part and currently it is a WIP for making it fully functional. Most functions, such as start and stop timer, and projects sync are currently working. For the rate limiting task new dependency is added: https://pypi.org/project/ratelimiter/ + + +___ + +
+ + +
+Fusion publish existing frames #4611 + +This PR adds the function to publish existing frames instead of having to re-render all of them for each new publish.I have split the render_locally plugin so the review-part is its own plugin now.I also change the saver-creator-plugin's label from Saver to Render (saver) as I intend to add a Prerender creator like in Nuke. + + +___ + +
+ + +
+Resolution settings referenced from DB record for 3dsMax #4652 + +- Add Callback for setting the resolution according to DB after the new scene is created. +- Add a new Action into openpype menu which allows the user to reset the resolution in 3dsMax + + +___ + +
+ + +
+3dsmax: render instance settings in Publish tab #4658 + +Allows user preset the pools, group and use_published settings in Render Creator in the Max Hosts.User can set the settings before or after creating instance in the new publisher + + +___ + +
+ + +
+scene length setting referenced from DB record for 3dsMax #4665 + +Setting the timeline length based on DB record in 3dsMax Hosts + + +___ + +
+ + +
+Publisher: Windows reduce command window pop-ups during Publishing #4672 + +Reduce the command line pop-ups that show on Windows during publishing. + + +___ + +
+ + +
+Publisher: Explicit save #4676 + +Publisher have explicit button to save changes, so reset can happen without saving any changes. Save still happens automatically when publishing is started or on publisher window close. But a popup is shown if context of host has changed. Important context was enhanced by workfile path (if host integration supports it) so workfile changes are captured too. In that case a dialog with confirmation is shown to user. All callbacks that may require save of context were moved to main window to be able handle dialog show at one place. Save changes now returns success so the rest of logic is skipped -> publishing won't start, when save of instances fails.Save and reset buttons have shortcuts (Ctrl + s and Ctrls + r). + + +___ + +
+ + +
+CelAction: conditional workfile parameters from settings #4677 + +Since some productions were requesting excluding some workfile parameters from publishing submission, we needed to move them to settings so those could be altered per project. + + +___ + +
+ + +
+Improve logging of used app + tool envs on application launch #4682 + +Improve logging of what apps + tool environments got loaded for an application launch. + + +___ + +
+ + +
+Fix name and docstring for Create Workdir Extra Folders prelaunch hook #4683 + +Fix class name and docstring for Create Workdir Extra Folders prelaunch hookThe class name and docstring were originally copied from another plug-in and didn't match the plug-in logic.This also fixes potentially seeing this twice in your logs. Before:After:Where it was actually running both this prelaunch hook and the actual `AddLastWorkfileToLaunchArgs` plugin. + + +___ + +
+ + +
+Application launch context: Include app group name in logger #4684 + +Clarify in logs better what app group the ApplicationLaunchContext belongs to and what application is being launched.Before:After: + + +___ + +
+ + +
+increment workfile version 3dsmax #4685 + +increment workfile version in 3dsmax as if in blender and maya hosts. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Fix getting non-active model panel. #2968 + +When capturing multiple cameras with image planes that have file sequences playing, only the active (first) camera will play through the file sequence. + + +___ + +
+ + +
+Maya: Fix broken review publishing. #4549 + +Resolves #4547 + + +___ + +
+ + +
+Maya: Avoid error on right click in Loader if `mtoa` is not loaded #4616 + +Fix an error on right clicking in the Loader when `mtoa` is not a loaded plug-in.Additionally if `mtoa` isn't loaded the loader will now load the plug-in before trying to create the arnold standin. + + +___ + +
+ + +
+Maya: Fix extract look colorspace detection #4618 + +Fix the logic which guesses the colorspace using `arnold` python library. +- Previously it'd error if `mtoa` was not available on path so it still required `mtoa` to be available. +- The guessing colorspace logic doesn't actually require `mtoa` to be loaded, but just the `arnold` python library to be available. This changes the logic so it doesn't require the `mtoa` plugin to get loaded to guess the colorspace. +- The if/else branch was likely not doing what was intended `cmds.loadPlugin("mtoa", quiet=True)` returns None if the plug-in was already loaded. So this would only ever be true if it ends up loading the `mtoa` plugin the first time. +```python +# Tested in Maya 2022.1 +print(cmds.loadPlugin("mtoa", quiet=True)) +# ['mtoa'] +print(cmds.loadPlugin("mtoa", quiet=True)) +# None +``` + + +___ + +
+ + +
+Maya: Maya Playblast Options overrides - OP-3847 #4634 + +When publishing a review in Maya, the extractor would fail due to wrong (long) panel name. + + +___ + +
+ + +
+Bugfix/op 2834 fix extract playblast #4701 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Bugfix/op 2834 fix extract playblast #4704 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Maya: bug fix for passing zoom settings if review is attached to subset #4716 + +Fix for attaching review to subset with pan/zoom option. + + +___ + +
+ + +
+Maya: tile assembly fail in draft - OP-4820 #4416 + +Tile assembly in Deadline was broken. + +Initial bug report revealed other areas of the tile assembly that needed fixing. + + +___ + +
+ + +
+Maya: Yeti Validate Rig Input - OP-3454 #4554 + +Fix Yeti Validate Rig Input + +Existing workflow was broken due to this #3297. + + +___ + +
+ + +
+Scene inventory: Fix code errors when "not found" entries are found #4594 + +Whenever a "NOT FOUND" entry is present a lot of errors happened in the Scene Inventory: +- It started spamming a lot of errors for the VersionDelegate since it had no numeric version (no version at all).Error reported on Discord: +```python +Traceback (most recent call last): + File "C:\Users\videopro\Documents\github\OpenPype\openpype\tools\utils\delegates.py", line 65, in paint + text = self.displayText( + File "C:\Users\videopro\Documents\github\OpenPype\openpype\tools\utils\delegates.py", line 33, in displayText + assert isinstance(value, numbers.Integral), ( +AssertionError: Version is not integer. "None" +``` +- Right click menu would error on NOT FOUND entries, and thus not show. With this PR it will now _disregard_ not found items for "Set version" and "Remove" but still allow actions.This PR resolves those. + + +___ + +
+ + +
+Kitsu: Sync OP with zou, make sure value-data is int or float #4596 + +Currently the data zou pulls is a string and not a value causing some bugs in the pipe where a value is expected (like `Set frame range` in Fusion). + + + +This PR makes sure each value is set with int() or float() so these bugs can't happen later on. + + + +_(A request to cgwire has also bin sent to allow force values only for some metadata columns, but currently the user can enter what ever they want in there)_ + + +___ + +
+ + +
+Max: fix the bug of removing an instance #4617 + +fix the bug of removing an instance in 3dsMax + + +___ + +
+ + +
+Global | Nuke: fixing farm publishing workflow #4623 + +After Nuke had adopted new publisher with new creators new issues were introduced. Those issues were addressed with this PR. Those are for example broken reviewable video files publishing if published via farm. Also fixed local publishing. + + +___ + +
+ + +
+Ftrack: Ftrack additional families filtering #4633 + +Ftrack family collector makes sure the subset family is also in instance families for additional families filtering. + + +___ + +
+ + +
+Ftrack: Hierarchical <> Non-Hierarchical attributes sync fix #4635 + +Sync between hierarchical and non-hierarchical attributes should be fixed and work as expected. Action should sync the values as expected and event handler should do it too and only on newly created entities. + + +___ + +
+ + +
+bugfix for 3dsmax publishing error #4637 + +fix the bug of failing publishing job in 3dsMax + + +___ + +
+ + +
+General: Use right validation for ffmpeg executable #4640 + +Use ffmpeg exec validation for ffmpeg executables instead of oiio exec validation. The validation is used as last possible source of ffmpeg from `PATH` environment variables, which is an edge case but can cause issues. + + +___ + +
+ + +
+3dsmax: opening last workfile #4644 + +Supports opening last saved workfile in 3dsmax host. + + +___ + +
+ + +
+Fixed a bug where a QThread in the splash screen could be destroyed before finishing execution #4647 + +This should fix the occasional behavior of the QThread being destroyed before even its worker returns from the `run()` function.After quiting, it should wait for the QThread object to properly close itself. + + +___ + +
+ + +
+General: Use right plugin class for Collect Comment #4653 + +Collect Comment plugin is instance plugin so should inherit from `InstancePlugin` instead of `ContextPlugin`. + + +___ + +
+ + +
+Global: add tags field to thumbnail representation #4660 + +Thumbnail representation might be missing tags field. + + +___ + +
+ + +
+Integrator: Enforce unique destination transfers, disallow overwrites in queued transfers #4662 + +Fix #4656 by enforcing unique destination transfers in the Integrator. It's now disallowed to a destination in the file transaction queue with a new source path during the publish. + + +___ + +
+ + +
+Hiero: Creator with correct workfile numeric padding input #4666 + +Creator was showing 99 in workfile input for long time, even if users set default value to 1001 in studio settings. This has been fixed now. + + +___ + +
+ + +
+Nuke: Nukenodes family instance without frame range #4669 + +No need to add frame range data into `nukenodes` (backdrop) family publishes - since those are timeless. + + +___ + +
+ + +
+TVPaint: Optional Validation plugins can be de/activated by user #4674 + +Added `OptionalPyblishPluginMixin` to TVpaint plugins that can be optional. + + +___ + +
+ + +
+Kitsu: Slightly less strict with instance data #4678 + +- Allow to take task name from context if asset doesn't have any. Fixes an issue with Photoshop's review instance not having `task` in data. +- Allow to match "review" against both `instance.data["family"]` and `instance.data["families"]` because some instances don't have the primary family in families, e.g. in Photoshop and TVPaint. +- Do not error on Integrate Kitsu Review whenever for whatever reason Integrate Kitsu Note did not created a comment but just log the message that it was unable to connect a review. + + +___ + +
+ + +
+Publisher: Fix reset shortcut sequence #4694 + +Fix bug created in https://github.com/ynput/OpenPype/pull/4676 where key sequence is checked using unsupported method. The check was changed to convert event into `QKeySequence` object which can be compared to prepared sequence. + + +___ + +
+ + +
+Refactor _capture #4702 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Hiero: correct container colors if UpToDate #4708 + +Colors on loaded containers are now correctly identifying real state of version. `Red` for out of date and `green` for up to date. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Look Assigner: Move Look Assigner tool since it's Maya only #4604 + +Fix #4357: Move Look Assigner tool to maya since it's Maya only + + +___ + +
+ + +
+Maya: Remove unused functions from Extract Look #4671 + +Remove unused functions from Maya Extract Look plug-in + + +___ + +
+ + +
+Extract Review code refactor #3930 + +Trying to reduce complexity of Extract Review plug-in +- Re-use profile filtering from lib +- Remove "combination families" additional filtering which supposedly was from OP v2 +- Simplify 'formatting' for filling gaps +- Use `legacy_io.Session` over `os.environ` + + +___ + +
+ + +
+Maya: Replace last usages of Qt module #4610 + +Replace last usage of `Qt` module with `qtpy`. This change is needed for `PySide6` support. All changes happened in Maya loader plugins. + + +___ + +
+ + +
+Update tests and documentation for `ColormanagedPyblishPluginMixin` #4612 + +Refactor `ExtractorColormanaged` to `ColormanagedPyblishPluginMixin` in tests and documentation. + + +___ + +
+ + +
+Improve logging of used app + tool envs on application launch (minor tweak) #4686 + +Use `app.full_name` for change done in #4682 + + +___ + +
+ +### **📃 Documentation** + + +
+Docs/add architecture document #4344 + +Add `ARCHITECTURE.md` document. + +his document attemps to give a quick overview of the project to help onboarding, it's not an extensive documentation but more of a elevator pitch one-line descriptions of files/directories and what the attempt to do. + + +___ + +
+ + +
+Documentation: Tweak grammar and fix some typos #4613 + +This resolves some grammar and typos in the documentation.Also fixes the extension of some images in after effects docs which used uppercase extension even though files were lowercase extension. + + +___ + +
+ + +
+Docs: Fix some minor grammar/typos #4680 + +Typo/grammar fixes in documentation. + + +___ + +
+ +### **Merged pull requests** + + +
+Maya: Implement image file node loader #4313 + +Implements a loader for loading texture image into a `file` node in Maya. + +Similar to Maya's hypershade creation of textures on load you have the option to choose for three modes of creating: +- Texture +- Projection +- StencilThese should match what Maya generates if you create those in Maya. +- [x] Load and manage file nodes +- [x] Apply color spaces after #4195 +- [x] Support for _either_ UDIM or image sequence - currently it seems to always load sequences as UDIM automatically. +- [ ] Add support for animation sequences of UDIM textures using the `..exr` path format? + + +___ + +
+ + +
+Maya Look Assigner: Don't rely on containers for get all assets #4600 + +This resolves #4044 by not actually relying on containers in the scene but instead just rely on finding nodes with `cbId` attributes. As such, imported nodes would also be found and a shader can be assigned (similar to when using get from selection).**Please take into consideration the potential downsides below**Potential downsides would be: +- IF an already loaded look has any dagNodes, say a 3D Projection node - then that will also show up as a loaded asset where previously nodes from loaded looks were ignored. +- If any dag nodes were created locally - they would have gotten `cbId` attributes on scene save and thus the current asset would almost always show? + + +___ + +
+ + +
+Maya: Unify menu labels for "Set Frame Range" and "Set Resolution" #4605 + +Fix #4109: Unify menu labels for "Set Frame Range" and "Set Resolution"This also tweaks it in Houdini from Reset Frame Range to Set Frame Range. + + +___ + +
+ + +
+Resolve missing OPENPYPE_MONGO in deadline global job preload #4484 + +In the GlobalJobPreLoad plugin, we propose to replace the SpawnProcess by a sub-process and to pass the environment variables in the parameters, since the SpawnProcess under Centos Linux does not pass the environment variables. + +In the GlobalJobPreLoad plugin, the Deadline SpawnProcess is used to start the OpenPype process. The problem is that the SpawnProcess does not pass environment variables, including OPENPYPE_MONGO, to the process when it is under Centos7 linux, and the process gets stuck. We propose to replace it by a subprocess and to pass the variable in the parameters. + + +___ + +
+ + +
+Tests: Added setup_only to tests #4591 + +Allows to download test zip, unzip and restore DB in preparation for new test. + + +___ + +
+ + +
+Maya: Arnold don't reset maya timeline frame range on render creation (or setting render settings) #4603 + +Fix #4429: Do not reset fps or playback timeline on applying or creating render settings + + +___ + +
+ + +
+Bump @sideway/formula from 3.0.0 to 3.0.1 in /website #4609 + +Bumps [@sideway/formula](https://github.com/sideway/formula) from 3.0.0 to 3.0.1. +
+Commits + +
+
+Maintainer changes +

This version was pushed to npm by marsup, a new releaser for @​sideway/formula since your current version.

+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sideway/formula&package-manager=npm_and_yarn&previous-version=3.0.0&new-version=3.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) +- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language +- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language +- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language +- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language + +You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ynput/OpenPype/network/alerts). + +
+___ + +
+ + +
+Update artist_hosts_maya_arnold.md #4626 + +Correct Arnold docs. +___ + +
+ + +
+Maya: Add "Include Parent Hierarchy" option in animation creator plugin #4645 + +Add an option in Project Settings > Maya > Creator Plugins > Create Animation to include (or not) parent hierarchy. This is to avoid artists to check manually the option for all create animation. + + +___ + +
+ + +
+General: Filter available applications #4667 + +Added option to filter applications that don't have valid executable available in settings in launcher and ftrack actions. This option can be disabled in new settings category `Applications`. The filtering is by default disabled. + + +___ + +
+ + +
+3dsmax: make sure that startup script executes #4695 + +Fixing reliability of OpenPype startup in 3dsmax. + + +___ + +
+ + +
+Project Manager: Change minimum frame start/end to '0' #4719 + +Project manager can have frame start/end set to `0`. + + +___ + +
+ + + +## [3.15.2](https://github.com/ynput/OpenPype/tree/3.15.2) + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.1...3.15.2) + +### **🆕 New features** + + +
+maya gltf texture convertor and validator #4261 + +Continuity of the gltf extractor implementation + +Continuity of the gltf extractor https://github.com/pypeclub/OpenPype/pull/4192UPDATE:**Validator for GLSL Shader**: Validate whether the mesh uses GLSL Shader. If not it will error out. The user can choose to perform the repair action and it will help to assign glsl shader. If the mesh with Stringray PBS, the repair action will also check to see if there is any linked texture such as Color, Occulsion, and Normal Map. If yes, it will help to relink the related textures to the glsl shader.*****If the mesh uses the PBS Shader, + + +___ + +
+ + +
+Unreal: New Publisher #4370 + +Implementation of the new publisher for Unreal. + +The implementation of the new publisher for Unreal. This PR includes the changes for all the existing creators to be compatible with the new publisher.The basic creator has been split in two distinct creators: +- `UnrealAssetCreator`, works with assets in the Content Browser. +- `UnrealActorCreator` that works with actors in the scene. + + +___ + +
+ + +
+Implementation of a new splash screen #4592 + +Implemented a new splash screen widget to reflect a process running in the background. This widget can be used for other tasks than UE. **Also fixed the compilation error of the AssetContainer.cpp when trying to build the plugin in UE 5.0** + + +___ + +
+ + +
+Deadline for 3dsMax #4439 + +Setting up deadline for 3dsmax + +Setting up deadline for 3dsmax by setting render outputs and viewport camera + + +___ + +
+ + +
+Nuke: adding nukeassist #4494 + +Adding support for NukeAssist + +For support of NukeAssist we had to limit some Nuke features since NukeAssist itself Nuke with limitations. We do not support Creator and Publisher. User can only Load versions with version control. User can also set Framerange and Colorspace. + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: OP-2630 acescg maya #4340 + +Resolves #2712 + + +___ + +
+ + +
+Default Ftrack Family on RenderLayer #4458 + +With default settings, renderlayers in Maya were not being tagged with the Ftrack family leading to confusion when doing reviews. + + +___ + +
+ + +
+Maya: Maya Playblast Options - OP-3783 #4487 + +Replacement PR for #3912. Adds more options for playblasts to preferences/settings. + +Adds the following as options in generating playblasts, matching viewport settings. +- Use default material +- Wireframe on shaded +- X-ray +- X-ray Joints +- X-ray active component + + +___ + +
+ + +
+Maya: Passing custom attributes to alembic - OP-4111 #4516 + +Passing custom attributes to alembic + +This PR makes it possible to pass all user defined attributes along to the alembic representation. + + +___ + +
+ + +
+Maya: Options for VrayProxy output - OP-2010 #4525 + +Options for output of VrayProxy. + +Client requested more granular control of output from VrayProxy instance. Exposed options on the instance and settings for vrmesh and alembic. + + +___ + +
+ + +
+Maya: Validate missing instance attributes #4559 + +Validate missing instance attributes. + +New attributes can be introduced as new features come in. Old instances will need to be updated with these attributes for the documentation to make sense, and users do not have to recreate the instances. + + +___ + +
+ + +
+Refactored Generation of UE Projects, installation of plugins moved to the engine #4369 + +Improved the way how OpenPype works with generation of UE projects. Also the installation of the plugin has been altered to install into the engine + +OpenPype now uses the appropriate tools to generate UE projects. Unreal Build Tool (UBT) and a "Commandlet Project" is needed to properly generate a BP project, or C++ code in case that `dev_mode = True`, folders, the .uproject file and many other resources.On the plugin's side, it is built seperately with the UnrealAutomationTool (UAT) and then it's contents are moved under the `Engine/Plugins/Marketplace/OpenPype` directory. + + +___ + +
+ + +
+Unreal: Use client functions in Layout loader #4578 + +Use 'get_representations' instead of 'legacy_io' query in layout loader. + +This is removing usage of `find_one` called on `legacy_io` and use rather client functions as preparation for AYON connection. Also all representations are queried at once instead of one by one. + + +___ + +
+ + +
+General: Support for extensions filtering in loaders #4492 + +Added extensions filtering support to loader plugins. + +To avoid possible backwards compatibility break is filtering exactly the same and filtering by extensions is enabled only if class attribute 'extensions' is set. + + +___ + +
+ + +
+Nuke: multiple reformat in baking review profiles #4514 + +Added support for multiple reformat nodes in baking profiles. + +Old settings for single reformat node is supported and prioritised just in case studios are using it and backward compatibility is needed. Warnings in Nuke terminal are notifying users to switch settings to new workflow. Settings are also explaining the migration way. + + +___ + +
+ + +
+Nuke: Add option to use new creating system in workfile template builder #4545 + +Nuke workfile template builder can use new creators instead of legacy creators. + +Modified workfile template builder to have option to say if legacy creators should be used or new creators. Legacy creators are disabled by default, so Maya has changed the value. + + +___ + +
+ + +
+Global, Nuke: Workfile first version with template processing #4579 + +Supporting new template workfile builder with toggle for creation of first version of workfile in case there is none yet. + + +___ + +
+ + +
+Fusion: New Publisher #4523 + +This is an updated PR for @BigRoy 's old PR (https://github.com/ynput/OpenPype/pull/3892).I have merged it with code from OP 3.15.1-nightly.6 and made sure it works as expected.This converts the old publishing system to the new one. It implements Fusion as a new host addon. + + +- Create button removed in OpenPype menu in favor of the new Publisher +- Draft refactor validations to raise PublishValidationError +- Implement Creator for New Publisher +- Implement Fusion as Host addon + + +___ + +
+ + +
+TVPaint: Use Publisher tool #4471 + +Use Publisher tool and new creation system in TVPaint integration. + +Using new creation system makes TVPaint integration a little bit easier to maintain for artists. Removed unneeded tools Creator and Subset Manager tools. Goal is to keep the integration work as close as possible to previous integration. Some changes were made but primarilly because they were not right using previous system.All creators create instance with final family instead of changing the family during extraction. Render passes are not related to group id but to render layer instance. Render layer is still related to group. Workfile, review and scene render instances are created using autocreators instead of auto-collection during publishing. Subset names are fully filled during publishing but instance labels are filled on refresh with the last known right value. Implemented basic of legacy convertor which should convert render layers and render passes. + + +___ + +
+ + +
+TVPaint: Auto-detect render creation #4496 + +Create plugin which will create Render Layer and Render Pass instances based on information in the scene. + +Added new creator that must be triggered by artist. The create plugin will first create Render Layer instances if were not created yet. For variant is used color group name. The creator has option to rename color groups by template defined in settings -> Template may use index of group by it's usage in scene (from bottom to top). After Render Layers will create Render Passes. Render Pass is created for each individual TVPaint layer in any group that had created Render Layer. It's name is used as variant (pass). + + +___ + +
+ + +
+TVPaint: Small enhancements #4501 + +Small enhancements in TVPaint integration which did not get to https://github.com/ynput/OpenPype/pull/4471. + +It was found out that `opacity` returned from `tv_layerinfo` is always empty and is dangerous to add it to layer information. Added information about "current" layer to layers information. Disable review of Render Layer and Render Pass instances by default. In most of productions is used only "scene review". Skip usage of `"enabled"` key from settings in automated layer/pass creation. + + +___ + +
+ + +
+Global: color v3 global oiio transcoder plugin #4291 + +Implements possibility to use `oiiotool` to transcode image sequences from one color space to another(s). + +Uses collected `colorspaceData` information about source color spaces, these information needs to be collected previously in each DCC interested in color management.Uses profiles configured in Settings to create single or multiple new representations (and file extensions) with different color spaces.New representations might replace existing one, each new representation might contain different tags and custom tags to control its integration step. + + +___ + +
+ + +
+Deadline: Added support for multiple install dirs in Deadline #4451 + +SearchDirectoryList returns FIRST existing so if you would have multiple OP install dirs, it won't search for appropriate version in later ones. + + +___ + +
+ + +
+Ftrack: Upload reviewables with original name #4483 + +Ftrack can integrate reviewables with original filenames. + +As ftrack have restrictions about names of components the only way how to achieve the result was to upload the same file twice, one with required name and one with origin name. + + +___ + +
+ + +
+TVPaint: Ignore transparency in Render Pass #4499 + +It is possible to ignore layers transparency during Render Pass extraction. + +Render pass extraction does not respect opacity of TVPaint layers set in scene during extraction. It can be enabled/disabled in settings. + + +___ + +
+ + +
+Anatomy: Preparation for different root overrides #4521 + +Prepare Anatomy to handle only 'studio' site override on it's own. + +Change how Anatomy fill root overrides based on requested site name. The logic which decide what is active site was moved to sync server addon and the same for receiving root overrides of local site. The Anatomy resolve only studio site overrides anything else is handled by sync server. BaseAnatomy only expect root overrides value and does not need site name. Validation of site name happens in sync server same as resolving if site name is local or not. + + +___ + +
+ + +
+Nuke | Global: colormanaged plugin in collection #4556 + +Colormanaged extractor had changed to Mixin class so it can be added to any stage of publishing rather then just to Exctracting.Nuke is no collecting colorspaceData to representation collected on already rendered images. + +Mixin class can no be used as secondary parent in publishing plugins. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+look publishing and srgb colorspace in maya #4276 + +Check the OCIO color management is enabled before doing linearize colorspace for converting the texture maps into tx files. + +Check whether the OCIO color management is enabled before the condition of converting the texture to tx extension. + + +___ + +
+ + +
+Maya: extract Thumbnail "No active model panel found" - OP-3849 #4421 + +Error when extracting playblast with no model panel. + +If `project_settings/maya/publish/ExtractPlayblast/capture_preset/Viewport Options/override_viewport_options` were off and publishing without showing any model panel, the extraction would fail. + + +___ + +
+ + +
+Maya: Fix setting scene fps with float input #4488 + +Returned value of float fps on integer values would return float. + +This PR fixes the case when switching between integer fps values for example 24 > 25. Issue was when setting the scene fps, the original float value was used which makes it unpredictable whether the value is float or integer when mapping the fps values. + + +___ + +
+ + +
+Maya: Multipart fix #4497 + +Fix multipart logic in render products. + +Each renderer has a different way of defining whether output images is multipart, so we need to define it for each renderer. Also before the `multipart` class variable was defined multiple times in several places, which made it tricky to debug where `multipart` was defined. Now its created on initialization and referenced as `self.multipart` + + +___ + +
+ + +
+Maya: Set pool on tile assembly - OP-2012 #4520 + +Set pool on tile assembly + +Pool for publishing and tiling jobs, need to use the settings (`project_settings/deadline/publish/ProcessSubmittedJobOnFarm/deadline_pool`) else fallback on primary pool (`project_settings/deadline/publish/CollectDeadlinePools/primary_pool`) + + +___ + +
+ + +
+Maya: Extract review with handles #4527 + +Review was not extracting properly with/without handles. + +Review instance was not created properly resulting in the frame range on the instance including handles. + + +___ + +
+ + +
+Maya: Fix broken lib. #4529 + +Fix broken lib. + +This commit from this PR broke the Maya lib module. + + +___ + +
+ + +
+Maya: Validate model name - OP-4983 #4539 + +Validate model name issues. + +Couple of issues with validate model name; +- missing platform extraction from settings +- map function should be list comprehension +- code cosmetics + + +___ + +
+ + +
+Maya: SkeletalMesh family loadable as reference #4573 + +In Maya, fix the SkeletalMesh family not loadable as reference. + + +___ + +
+ + +
+Unreal: fix loaders because of missing AssetContainer #4536 + +Fixing Unreal loaders, where changes in OpenPype Unreal integration plugin deleted AssetContainer. + +`AssetContainer` and `AssetContainerFactory` are still used to mark loaded instances. Because of optimizations in Integration plugin we've accidentally removed them but that broke loader. + + +___ + +
+ + +
+3dsmax unable to delete loaded asset in the scene inventory #4507 + +Fix the bug of being unable to delete loaded asset in the Scene Inventory + +Fix the bug of being unable to delete loaded asset in the Scene Inventory + + +___ + +
+ + +
+Hiero/Nuke: originalBasename editorial publishing and loading #4453 + +Publishing and loading `originalBasename` is working as expected + +Frame-ranges on version document is now correctly defined to fit original media frame range which is published. It means loading is now correctly identifying frame start and end on clip loader in Nuke. + + +___ + +
+ + +
+Nuke: Fix workfile template placeholder creation #4512 + +Template placeholder creation was erroring out in Nuke due to the Workfile template builder not being able to find any of the plugins for the Nuke host. + +Move `get_workfile_build_placeholder_plugins` function to NukeHost class as workfile template builder expects. + + +___ + +
+ + +
+Nuke: creator farm attributes from deadline submit plugin settings #4519 + +Defaults in farm attributes are sourced from settings. + +Settings for deadline nuke submitter are now used during nuke render and prerender creator plugins. + + +___ + +
+ + +
+Nuke: fix clip sequence loading #4574 + +Nuke is loading correctly clip from image sequence created without "{originalBasename}" token in anatomy template. + + +___ + +
+ + +
+Fusion: Fix files collection and small bug-fixes #4423 + +Fixed Fusion review-representation and small bug-fixes + +This fixes the problem with review-file generation that stopped the publishing on second publish before the fix.The problem was that Fusion simply looked at all the files in the render-folder instead of only gathering the needed frames for the review.Also includes a fix to get the handle start/end that before throw an error if the data didn't exist (like from a kitsu sync). + + +___ + +
+ + +
+Fusion: Updated render_local.py to not only process the first instance #4522 + +Moved the `__hasRun` to `render_once()` so the check only happens with the rendering. Currently only the first render node gets the representations added.Critical PR + + +___ + +
+ + +
+Fusion: Load sequence fix filepath resolving from representation #4580 + +Resolves issue mentioned on discord by @movalex:The loader was incorrectly trying to find the file in the publish folder which resulted in just picking 'any first file'. + +This gets the filepath from representation instead of taking the first file from listing files from publish folder. + + +___ + +
+ + +
+Fusion: Fix review burnin start and end frame #4590 + +Fix the burnin start and end frame for reviews. Without this the asset document's start and end handle would've been added to the _burnin_ frame range even though that would've been incorrect since the handles are based on the comp saver's render range instead. + + +___ + +
+ + +
+Harmony: missing set of frame range when opening scene #4485 + +Frame range gets set from DB everytime scene is opened. + +Added also check for not up-to-date loaded containers. + + +___ + +
+ + +
+Photoshop: context is not changed in publisher #4570 + +When PS is already open and artists launch new task, it should keep only opened PS open, but change context. + +Problem were occurring in Workfile app where under new task files from old task were shown. This fixes this and adds opening of last workfile for new context if workfile exists. + + +___ + +
+ + +
+hiero: fix effect item node class #4543 + +Collected effect name after renaming is saving correct class name. + + +___ + +
+ + +
+Bugfix/OP-4616 vray multipart #4297 + +This fixes a bug where multipart vray renders would not make a review in Ftrack. + + +___ + +
+ + +
+Maya: Fix changed location of reset_frame_range #4491 + +Location in commands caused cyclic import + + +___ + +
+ + +
+global: source template fixed frame duplication #4503 + +Duplication is not happening. + +Template is using `originalBasename` which already assume all necessary elements are part of the file name so there was no need for additional optional name elements. + + +___ + +
+ + +
+Deadline: Hint to use Python 3 #4518 + +Added shebank to give deadline hint which python should be used. + +Deadline has issues with Python 2 (especially with `os.scandir`). When a shebank is added to file header deadline will use python 3 mode instead of python 2 which fix the issue. + + +___ + +
+ + +
+Publisher: Prevent access to create tab after publish start #4528 + +Prevent access to create tab after publish start. + +Disable create button in instance view on publish start and enable it again on reset. Even with that make sure that it is not possible to go to create tab if the tab is disabled. + + +___ + +
+ + +
+Color Transcoding: store target_colorspace as new colorspace #4544 + +When transcoding into new colorspace, representation must carry this information instead original color space. + + +___ + +
+ + +
+Deadline: fix submit_publish_job #4552 + +Fix submit_publish_job + +Resolves #4541 + + +___ + +
+ + +
+Kitsu: Fix task itteration in update-op-with-zou #4577 + +From the last PR (https://github.com/ynput/OpenPype/pull/4425) a comment-commit last second messed up the code and resulted in two lines being the same, crashing the script. This PR fixes that. +___ + +
+ + +
+AttrDefs: Fix type for PySide6 #4584 + +Use right type in signal emit for value change of attribute definitions. + +Changed `UUID` type to `str`. This is not an issue with PySide2 but it is with PySide6. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Scene Inventory: Avoid using ObjectId #4524 + +Avoid using conversion to ObjectId type in scene inventory tool. + +Preparation for AYON compatibility where ObjectId won't be used for ids. Representation ids from loaded containers are not converted to ObjectId but kept as strings which also required some changes when working with representation documents. + + +___ + +
+ +### **Merged pull requests** + + +
+SiteSync: host dirmap is not working properly #4563 + +If artists uses SiteSync with real remote (gdrive, dropbox, sftp) drive, Local Settings were throwing error `string indices must be integers`. + +Logic was reworked to provide only `local_drive` values to be overrriden by Local Settings. If remote site is `gdrive` etc. mapping to `studio` is provided as it is expected that workfiles will have imported from `studio` location and not from `gdrive` folder.Also Nuke dirmap was reworked to be less verbose and much faster. + + +___ + +
+ + +
+General: Input representation ids are not ObjectIds #4576 + +Don't use `ObjectId` as representation ids during publishing. + +Representation ids are kept as strings during publishing instead of converting them to `ObjectId`. This change is pre-requirement for AYON connection.Inputs are used for integration of links and for farm publishing (or at least it looks like). + + +___ + +
+ + +
+Shotgrid: Fixes on Deadline submissions #4498 + +A few other bug fixes for getting Nuke submission to Deadline work smoothly using Shotgrid integration. + +Continuing on the work done on this other PR this fixes a few other bugs I came across with further tests. + + +___ + +
+ + +
+Fusion: New Publisher #3892 + +This converts the old publishing system to the new one. It implements Fusion as a new host addon. + + +- Create button removed in OpenPype menu in favor of the new Publisher +- Draft refactor validations to raise `PublishValidationError` +- Implement Creator for New Publisher +- Implement Fusion as Host addon + + +___ + +
+ + +
+Make Kitsu work with Tray Publisher, added kitsureview tag, fixed sync-problems. #4425 + +Make Kitsu work with Tray Publisher, added kitsureview tag, fixed sync-problems. + +This PR updates the way the module gather info for the current publish so it now works with Tray Publisher.It fixes the data that gets synced from Kitsu to OP so all needed data gets registered even if it doesn't exist on Kitsus side.It also adds the tag "Add review to Kitsu" and adds it to Burn In so previews gets generated by default to Kitsu. + + +___ + +
+ + +
+Maya: V-Ray Set Image Format from settings #4566 + +Resolves #4565 + +Set V-Ray Image Format using settings. + + +___ + +
+ + + ## [3.15.1](https://github.com/ynput/OpenPype/tree/3.15.1) diff --git a/openpype/cli.py b/openpype/cli.py index 5c47088a44..a650a9fdcc 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -367,11 +367,15 @@ def run(script): "--timeout", help="Provide specific timeout value for test case", default=None) +@click.option("-so", + "--setup_only", + help="Only create dbs, do not run tests", + default=None) def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant, - timeout): + timeout, setup_only): """Run all automatic tests after proper initialization via start.py""" PypeCommands().run_tests(folder, mark, pyargs, test_data_folder, - persist, app_variant, timeout) + persist, app_variant, timeout, setup_only) @main.command() diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 1c8746c559..2558daef30 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -14,6 +14,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): # Execute after workfile template copy order = 10 app_groups = [ + "3dsmax", "maya", "nuke", "nukex", diff --git a/openpype/hooks/pre_create_extra_workdir_folders.py b/openpype/hooks/pre_create_extra_workdir_folders.py index c5af620c87..8856281120 100644 --- a/openpype/hooks/pre_create_extra_workdir_folders.py +++ b/openpype/hooks/pre_create_extra_workdir_folders.py @@ -3,10 +3,13 @@ from openpype.lib import PreLaunchHook from openpype.pipeline.workfile import create_workdir_extra_folders -class AddLastWorkfileToLaunchArgs(PreLaunchHook): - """Add last workfile path to launch arguments. +class CreateWorkdirExtraFolders(PreLaunchHook): + """Create extra folders for the work directory. + + Based on setting `project_settings/global/tools/Workfiles/extra_folders` + profile filtering will decide whether extra folders need to be created in + the work directory. - This is not possible to do for all applications the same way. """ # Execute after workfile template copy diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 2092d5025d..21ec8e7881 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -7,7 +7,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): Nuke is executed "like" python process so it is required to pass `CREATE_NEW_CONSOLE` flag on windows to trigger creation of new console. - At the same time the newly created console won't create it's own stdout + At the same time the newly created console won't create its own stdout and stderr handlers so they should not be redirected to DEVNULL. """ @@ -18,7 +18,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): def execute(self): # Change `creationflags` to CREATE_NEW_CONSOLE - # - on Windows will nuke create new window using it's console + # - on Windows nuke will create new window using its console # Set `stdout` and `stderr` to None so new created console does not # have redirected output to DEVNULL in build self.launch_context.kwargs.update({ diff --git a/openpype/host/dirmap.py b/openpype/host/dirmap.py index 347c5fbf85..1d084cccad 100644 --- a/openpype/host/dirmap.py +++ b/openpype/host/dirmap.py @@ -39,7 +39,6 @@ class HostDirmap(object): self._project_settings = project_settings self._sync_module = sync_module # to limit reinit of Modules self._log = None - self._mapping = None # cache mapping @property def sync_module(self): @@ -70,29 +69,28 @@ class HostDirmap(object): """Run host dependent remapping from source_path to destination_path""" pass - def process_dirmap(self): + def process_dirmap(self, mapping=None): # type: (dict) -> None """Go through all paths in Settings and set them using `dirmap`. If artists has Site Sync enabled, take dirmap mapping directly from Local Settings when artist is syncing workfile locally. - Args: - project_settings (dict): Settings for current project. """ - if not self._mapping: - self._mapping = self.get_mappings(self.project_settings) - if not self._mapping: + if not mapping: + mapping = self.get_mappings() + if not mapping: return - self.log.info("Processing directory mapping ...") self.on_enable_dirmap() - self.log.info("mapping:: {}".format(self._mapping)) - for k, sp in enumerate(self._mapping["source-path"]): - dst = self._mapping["destination-path"][k] + for k, sp in enumerate(mapping["source-path"]): + dst = mapping["destination-path"][k] try: + # add trailing slash if missing + sp = os.path.join(sp, '') + dst = os.path.join(dst, '') print("{} -> {}".format(sp, dst)) self.dirmap_routine(sp, dst) except IndexError: @@ -110,28 +108,24 @@ class HostDirmap(object): ) continue - def get_mappings(self, project_settings): + def get_mappings(self): """Get translation from source-path to destination-path. It checks if Site Sync is enabled and user chose to use local site, in that case configuration in Local Settings takes precedence """ - local_mapping = self._get_local_sync_dirmap(project_settings) dirmap_label = "{}-dirmap".format(self.host_name) - if ( - not self.project_settings[self.host_name].get(dirmap_label) - and not local_mapping - ): - return {} - mapping_settings = self.project_settings[self.host_name][dirmap_label] - mapping_enabled = mapping_settings["enabled"] or bool(local_mapping) + mapping_sett = self.project_settings[self.host_name].get(dirmap_label, + {}) + local_mapping = self._get_local_sync_dirmap() + mapping_enabled = mapping_sett.get("enabled") or bool(local_mapping) if not mapping_enabled: return {} mapping = ( local_mapping - or mapping_settings["paths"] + or mapping_sett["paths"] or {} ) @@ -141,28 +135,27 @@ class HostDirmap(object): or not mapping.get("source-path") ): return {} + self.log.info("Processing directory mapping ...") + self.log.info("mapping:: {}".format(mapping)) return mapping - def _get_local_sync_dirmap(self, project_settings): + def _get_local_sync_dirmap(self): """ Returns dirmap if synch to local project is enabled. Only valid mapping is from roots of remote site to local site set in Local Settings. - Args: - project_settings (dict) Returns: dict : { "source-path": [XXX], "destination-path": [YYYY]} """ + project_name = os.getenv("AVALON_PROJECT") mapping = {} - - if not project_settings["global"]["sync_server"]["enabled"]: + if (not self.sync_module.enabled or + project_name not in self.sync_module.get_enabled_projects()): return mapping - project_name = os.getenv("AVALON_PROJECT") - active_site = self.sync_module.get_local_normalized_site( self.sync_module.get_active_site(project_name)) remote_site = self.sync_module.get_local_normalized_site( @@ -171,11 +164,7 @@ class HostDirmap(object): "active {} - remote {}".format(active_site, remote_site) ) - if ( - active_site == "local" - and project_name in self.sync_module.get_enabled_projects() - and active_site != remote_site - ): + if active_site == "local" and active_site != remote_site: sync_settings = self.sync_module.get_sync_project_setting( project_name, exclude_locals=False, @@ -188,7 +177,15 @@ class HostDirmap(object): self.log.debug("local overrides {}".format(active_overrides)) self.log.debug("remote overrides {}".format(remote_overrides)) + current_platform = platform.system().lower() + remote_provider = self.sync_module.get_provider_for_site( + project_name, remote_site + ) + # dirmap has sense only with regular disk provider, in the workfile + # wont be root on cloud or sftp provider + if remote_provider != "local_drive": + remote_site = "studio" for root_name, active_site_dir in active_overrides.items(): remote_site_dir = ( remote_overrides.get(root_name) diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index e017d74d91..75a11affde 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -31,10 +31,13 @@ from .lib import ( lsattrs, read, maintained_selection, + maintained_time, get_selection, # unique_name, ) +from .capture import capture + __all__ = [ "install", @@ -56,9 +59,11 @@ __all__ = [ # Utility functions "maintained_selection", + "maintained_time", "lsattr", "lsattrs", "read", "get_selection", + "capture", # "unique_name", ] diff --git a/openpype/hosts/blender/api/capture.py b/openpype/hosts/blender/api/capture.py new file mode 100644 index 0000000000..849f8ee629 --- /dev/null +++ b/openpype/hosts/blender/api/capture.py @@ -0,0 +1,278 @@ + +"""Blender Capture +Playblasting with independent viewport, camera and display options +""" +import contextlib +import bpy + +from .lib import maintained_time +from .plugin import deselect_all, create_blender_context + + +def capture( + camera=None, + width=None, + height=None, + filename=None, + start_frame=None, + end_frame=None, + step_frame=None, + sound=None, + isolate=None, + maintain_aspect_ratio=True, + overwrite=False, + image_settings=None, + display_options=None +): + """Playblast in an independent windows + Arguments: + camera (str, optional): Name of camera, defaults to "Camera" + width (int, optional): Width of output in pixels + height (int, optional): Height of output in pixels + filename (str, optional): Name of output file path. Defaults to current + render output path. + start_frame (int, optional): Defaults to current start frame. + end_frame (int, optional): Defaults to current end frame. + step_frame (int, optional): Defaults to 1. + sound (str, optional): Specify the sound node to be used during + playblast. When None (default) no sound will be used. + isolate (list): List of nodes to isolate upon capturing + maintain_aspect_ratio (bool, optional): Modify height in order to + maintain aspect ratio. + overwrite (bool, optional): Whether or not to overwrite if file + already exists. If disabled and file exists and error will be + raised. + image_settings (dict, optional): Supplied image settings for render, + using `ImageSettings` + display_options (dict, optional): Supplied display options for render + """ + + scene = bpy.context.scene + camera = camera or "Camera" + + # Ensure camera exists. + if camera not in scene.objects and camera != "AUTO": + raise RuntimeError("Camera does not exist: {0}".format(camera)) + + # Ensure resolution. + if width and height: + maintain_aspect_ratio = False + width = width or scene.render.resolution_x + height = height or scene.render.resolution_y + if maintain_aspect_ratio: + ratio = scene.render.resolution_x / scene.render.resolution_y + height = round(width / ratio) + + # Get frame range. + if start_frame is None: + start_frame = scene.frame_start + if end_frame is None: + end_frame = scene.frame_end + if step_frame is None: + step_frame = 1 + frame_range = (start_frame, end_frame, step_frame) + + if filename is None: + filename = scene.render.filepath + + render_options = { + "filepath": "{}.".format(filename.rstrip(".")), + "resolution_x": width, + "resolution_y": height, + "use_overwrite": overwrite, + } + + with _independent_window() as window: + + applied_view(window, camera, isolate, options=display_options) + + with contextlib.ExitStack() as stack: + stack.enter_context(maintain_camera(window, camera)) + stack.enter_context(applied_frame_range(window, *frame_range)) + stack.enter_context(applied_render_options(window, render_options)) + stack.enter_context(applied_image_settings(window, image_settings)) + stack.enter_context(maintained_time()) + + bpy.ops.render.opengl( + animation=True, + render_keyed_only=False, + sequencer=False, + write_still=False, + view_context=True + ) + + return filename + + +ImageSettings = { + "file_format": "FFMPEG", + "color_mode": "RGB", + "ffmpeg": { + "format": "QUICKTIME", + "use_autosplit": False, + "codec": "H264", + "constant_rate_factor": "MEDIUM", + "gopsize": 18, + "use_max_b_frames": False, + }, +} + + +def isolate_objects(window, objects): + """Isolate selection""" + deselect_all() + + for obj in objects: + obj.select_set(True) + + context = create_blender_context(selected=objects, window=window) + + bpy.ops.view3d.view_axis(context, type="FRONT") + bpy.ops.view3d.localview(context) + + deselect_all() + + +def _apply_options(entity, options): + for option, value in options.items(): + if isinstance(value, dict): + _apply_options(getattr(entity, option), value) + else: + setattr(entity, option, value) + + +def applied_view(window, camera, isolate=None, options=None): + """Apply view options to window.""" + area = window.screen.areas[0] + space = area.spaces[0] + + area.ui_type = "VIEW_3D" + + meshes = [obj for obj in window.scene.objects if obj.type == "MESH"] + + if camera == "AUTO": + space.region_3d.view_perspective = "ORTHO" + isolate_objects(window, isolate or meshes) + else: + isolate_objects(window, isolate or meshes) + space.camera = window.scene.objects.get(camera) + space.region_3d.view_perspective = "CAMERA" + + if isinstance(options, dict): + _apply_options(space, options) + else: + space.shading.type = "SOLID" + space.shading.color_type = "MATERIAL" + space.show_gizmo = False + space.overlay.show_overlays = False + + +@contextlib.contextmanager +def applied_frame_range(window, start, end, step): + """Context manager for setting frame range.""" + # Store current frame range + current_frame_start = window.scene.frame_start + current_frame_end = window.scene.frame_end + current_frame_step = window.scene.frame_step + # Apply frame range + window.scene.frame_start = start + window.scene.frame_end = end + window.scene.frame_step = step + try: + yield + finally: + # Restore frame range + window.scene.frame_start = current_frame_start + window.scene.frame_end = current_frame_end + window.scene.frame_step = current_frame_step + + +@contextlib.contextmanager +def applied_render_options(window, options): + """Context manager for setting render options.""" + render = window.scene.render + + # Store current settings + original = {} + for opt in options.copy(): + try: + original[opt] = getattr(render, opt) + except ValueError: + options.pop(opt) + + # Apply settings + _apply_options(render, options) + + try: + yield + finally: + # Restore previous settings + _apply_options(render, original) + + +@contextlib.contextmanager +def applied_image_settings(window, options): + """Context manager to override image settings.""" + + options = options or ImageSettings.copy() + ffmpeg = options.pop("ffmpeg", {}) + render = window.scene.render + + # Store current image settings + original = {} + for opt in options.copy(): + try: + original[opt] = getattr(render.image_settings, opt) + except ValueError: + options.pop(opt) + + # Store current ffmpeg settings + original_ffmpeg = {} + for opt in ffmpeg.copy(): + try: + original_ffmpeg[opt] = getattr(render.ffmpeg, opt) + except ValueError: + ffmpeg.pop(opt) + + # Apply image settings + for opt, value in options.items(): + setattr(render.image_settings, opt, value) + + # Apply ffmpeg settings + for opt, value in ffmpeg.items(): + setattr(render.ffmpeg, opt, value) + + try: + yield + finally: + # Restore previous settings + for opt, value in original.items(): + setattr(render.image_settings, opt, value) + for opt, value in original_ffmpeg.items(): + setattr(render.ffmpeg, opt, value) + + +@contextlib.contextmanager +def maintain_camera(window, camera): + """Context manager to override camera.""" + current_camera = window.scene.camera + if camera in window.scene.objects: + window.scene.camera = window.scene.objects.get(camera) + try: + yield + finally: + window.scene.camera = current_camera + + +@contextlib.contextmanager +def _independent_window(): + """Create capture-window context.""" + context = create_blender_context() + current_windows = set(bpy.context.window_manager.windows) + bpy.ops.wm.window_new(context) + window = list(set(bpy.context.window_manager.windows) - current_windows)[0] + context["window"] = window + try: + yield window + finally: + bpy.ops.wm.window_close(context) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 05912885f7..6526f1fb87 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -284,3 +284,13 @@ def maintained_selection(): # This could happen if the active node was deleted during the # context. log.exception("Failed to set active object.") + + +@contextlib.contextmanager +def maintained_time(): + """Maintain current frame during context.""" + current_time = bpy.context.scene.frame_current + try: + yield + finally: + bpy.context.scene.frame_current = current_time diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index 481c199db2..158c32fb5a 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -84,11 +84,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ print("Executing process in main thread") if self.done: @@ -382,8 +382,8 @@ class TOPBAR_MT_avalon(bpy.types.Menu): layout.operator(LaunchLibrary.bl_idname, text="Library...") layout.separator() layout.operator(LaunchWorkFiles.bl_idname, text="Work Files...") - # TODO (jasper): maybe add 'Reload Pipeline', 'Reset Frame Range' and - # 'Reset Resolution'? + # TODO (jasper): maybe add 'Reload Pipeline', 'Set Frame Range' and + # 'Set Resolution'? def draw_avalon_menu(self, context): diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index c59be8d7ff..1274795c6b 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -62,7 +62,8 @@ def prepare_data(data, container_name=None): def create_blender_context(active: Optional[bpy.types.Object] = None, - selected: Optional[bpy.types.Object] = None,): + selected: Optional[bpy.types.Object] = None, + window: Optional[bpy.types.Window] = None): """Create a new Blender context. If an object is passed as parameter, it is set as selected and active. """ @@ -72,7 +73,9 @@ def create_blender_context(active: Optional[bpy.types.Object] = None, override_context = bpy.context.copy() - for win in bpy.context.window_manager.windows: + windows = [window] if window else bpy.context.window_manager.windows + + for win in windows: for area in win.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: diff --git a/openpype/hosts/blender/plugins/create/create_review.py b/openpype/hosts/blender/plugins/create/create_review.py new file mode 100644 index 0000000000..bf4ea6a7cd --- /dev/null +++ b/openpype/hosts/blender/plugins/create/create_review.py @@ -0,0 +1,47 @@ +"""Create review.""" + +import bpy + +from openpype.pipeline import legacy_io +from openpype.hosts.blender.api import plugin, lib, ops +from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES + + +class CreateReview(plugin.Creator): + """Single baked camera""" + + name = "reviewDefault" + label = "Review" + family = "review" + 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 Container 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 = plugin.asset_name(asset, subset) + asset_group = bpy.data.collections.new(name=name) + instances.children.link(asset_group) + self.data['task'] = legacy_io.Session.get('AVALON_TASK') + lib.imprint(asset_group, self.data) + + if (self.options or {}).get("useSelection"): + selected = lib.get_selection() + for obj in selected: + asset_group.objects.link(obj) + elif (self.options or {}).get("asset_group"): + obj = (self.options or {}).get("asset_group") + asset_group.objects.link(obj) + + return asset_group diff --git a/openpype/hosts/blender/plugins/publish/collect_review.py b/openpype/hosts/blender/plugins/publish/collect_review.py new file mode 100644 index 0000000000..d6abd9d967 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/collect_review.py @@ -0,0 +1,64 @@ +import bpy + +import pyblish.api +from openpype.pipeline import legacy_io + + +class CollectReview(pyblish.api.InstancePlugin): + """Collect Review data + + """ + + order = pyblish.api.CollectorOrder + 0.3 + label = "Collect Review Data" + families = ["review"] + + def process(self, instance): + + self.log.debug(f"instance: {instance}") + + # get cameras + cameras = [ + obj + for obj in instance + if isinstance(obj, bpy.types.Object) and obj.type == "CAMERA" + ] + + assert len(cameras) == 1, ( + f"Not a single camera found in extraction: {cameras}" + ) + camera = cameras[0].name + self.log.debug(f"camera: {camera}") + + # get isolate objects list from meshes instance members . + isolate_objects = [ + obj + for obj in instance + if isinstance(obj, bpy.types.Object) and obj.type == "MESH" + ] + + if not instance.data.get("remove"): + + task = legacy_io.Session.get("AVALON_TASK") + + instance.data.update({ + "subset": f"{task}Review", + "review_camera": camera, + "frameStart": instance.context.data["frameStart"], + "frameEnd": instance.context.data["frameEnd"], + "fps": instance.context.data["fps"], + "isolate": isolate_objects, + }) + + self.log.debug(f"instance data: {instance.data}") + + # TODO : Collect audio + audio_tracks = [] + instance.data["audio"] = [] + for track in audio_tracks: + instance.data["audio"].append( + { + "offset": track.offset.get(), + "filename": track.filename.get(), + } + ) diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py new file mode 100644 index 0000000000..8dc2f66c22 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -0,0 +1,123 @@ +import os +import clique + +import bpy + +import pyblish.api +from openpype.pipeline import publish +from openpype.hosts.blender.api import capture +from openpype.hosts.blender.api.lib import maintained_time + + +class ExtractPlayblast(publish.Extractor): + """ + Extract viewport playblast. + + Takes review camera and creates review Quicktime video based on viewport + capture. + """ + + label = "Extract Playblast" + hosts = ["blender"] + families = ["review"] + optional = True + order = pyblish.api.ExtractorOrder + 0.01 + + def process(self, instance): + self.log.info("Extracting capture..") + + self.log.info(instance.data) + + # get scene fps + fps = instance.data.get("fps") + if fps is None: + fps = bpy.context.scene.render.fps + instance.data["fps"] = fps + + self.log.info(f"fps: {fps}") + + # If start and end frames cannot be determined, + # get them from Blender timeline. + start = instance.data.get("frameStart", bpy.context.scene.frame_start) + end = instance.data.get("frameEnd", bpy.context.scene.frame_end) + + self.log.info(f"start: {start}, end: {end}") + assert end > start, "Invalid time range !" + + # get cameras + camera = instance.data("review_camera", None) + + # get isolate objects list + isolate = instance.data("isolate", None) + + # get ouput path + stagingdir = self.staging_dir(instance) + filename = instance.name + path = os.path.join(stagingdir, filename) + + self.log.info(f"Outputting images to {path}") + + project_settings = instance.context.data["project_settings"]["blender"] + presets = project_settings["publish"]["ExtractPlayblast"]["presets"] + preset = presets.get("default") + preset.update({ + "camera": camera, + "start_frame": start, + "end_frame": end, + "filename": path, + "overwrite": True, + "isolate": isolate, + }) + preset.setdefault( + "image_settings", + { + "file_format": "PNG", + "color_mode": "RGB", + "color_depth": "8", + "compression": 15, + }, + ) + + with maintained_time(): + path = capture(**preset) + + self.log.debug(f"playblast path {path}") + + collected_files = os.listdir(stagingdir) + collections, remainder = clique.assemble( + collected_files, + patterns=[f"{filename}\\.{clique.DIGITS_PATTERN}\\.png$"], + ) + + if len(collections) > 1: + raise RuntimeError( + f"More than one collection found in stagingdir: {stagingdir}" + ) + elif len(collections) == 0: + raise RuntimeError( + f"No collection found in stagingdir: {stagingdir}" + ) + + frame_collection = collections[0] + + self.log.info(f"We found collection of interest {frame_collection}") + + instance.data.setdefault("representations", []) + + tags = ["review"] + if not instance.data.get("keepImages"): + tags.append("delete") + + representation = { + "name": "png", + "ext": "png", + "files": list(frame_collection), + "stagingDir": stagingdir, + "frameStart": start, + "frameEnd": end, + "fps": fps, + "preview": True, + "tags": tags, + "camera_name": camera + } + instance.data["representations"].append(representation) diff --git a/openpype/hosts/blender/plugins/publish/extract_thumbnail.py b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py new file mode 100644 index 0000000000..65c3627375 --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/extract_thumbnail.py @@ -0,0 +1,99 @@ +import os +import glob + +import pyblish.api +from openpype.pipeline import publish +from openpype.hosts.blender.api import capture +from openpype.hosts.blender.api.lib import maintained_time + +import bpy + + +class ExtractThumbnail(publish.Extractor): + """Extract viewport thumbnail. + + Takes review camera and creates a thumbnail based on viewport + capture. + + """ + + label = "Extract Thumbnail" + hosts = ["blender"] + families = ["review"] + order = pyblish.api.ExtractorOrder + 0.01 + presets = {} + + def process(self, instance): + self.log.info("Extracting capture..") + + stagingdir = self.staging_dir(instance) + filename = instance.name + path = os.path.join(stagingdir, filename) + + self.log.info(f"Outputting images to {path}") + + camera = instance.data.get("review_camera", "AUTO") + start = instance.data.get("frameStart", bpy.context.scene.frame_start) + family = instance.data.get("family") + isolate = instance.data("isolate", None) + + preset = self.presets.get(family, {}) + + preset.update({ + "camera": camera, + "start_frame": start, + "end_frame": start, + "filename": path, + "overwrite": True, + "isolate": isolate, + }) + preset.setdefault( + "image_settings", + { + "file_format": "JPEG", + "color_mode": "RGB", + "quality": 100, + }, + ) + + with maintained_time(): + path = capture(**preset) + + thumbnail = os.path.basename(self._fix_output_path(path)) + + self.log.info(f"thumbnail: {thumbnail}") + + instance.data.setdefault("representations", []) + + representation = { + "name": "thumbnail", + "ext": "jpg", + "files": thumbnail, + "stagingDir": stagingdir, + "thumbnail": True + } + instance.data["representations"].append(representation) + + def _fix_output_path(self, filepath): + """"Workaround to return correct filepath. + + To workaround this we just glob.glob() for any file extensions and + assume the latest modified file is the correct file and return it. + + """ + # Catch cancelled playblast + if filepath is None: + self.log.warning( + "Playblast did not result in output path. " + "Playblast is probably interrupted." + ) + return None + + if not os.path.exists(filepath): + files = glob.glob(f"{filepath}.*.jpg") + + if not files: + raise RuntimeError(f"Couldn't find playblast from: {filepath}") + filepath = max(files, key=os.path.getmtime) + + return filepath diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index 62cebf99ed..96e784875c 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -38,8 +38,9 @@ class CelactionPrelaunchHook(PreLaunchHook): ) path_to_cli = os.path.join(CELACTION_SCRIPTS_DIR, "publish_cli.py") - subproces_args = get_openpype_execute_args("run", path_to_cli) - openpype_executable = subproces_args.pop(0) + subprocess_args = get_openpype_execute_args("run", path_to_cli) + openpype_executable = subprocess_args.pop(0) + workfile_settings = self.get_workfile_settings() winreg.SetValueEx( hKey, @@ -49,20 +50,34 @@ class CelactionPrelaunchHook(PreLaunchHook): openpype_executable ) - parameters = subproces_args + [ - "--currentFile", "*SCENE*", - "--chunk", "*CHUNK*", - "--frameStart", "*START*", - "--frameEnd", "*END*", - "--resolutionWidth", "*X*", - "--resolutionHeight", "*Y*" + # add required arguments for workfile path + parameters = subprocess_args + [ + "--currentFile", "*SCENE*" ] + # Add custom parameters from workfile settings + if "render_chunk" in workfile_settings["submission_overrides"]: + parameters += [ + "--chunk", "*CHUNK*" + ] + if "resolution" in workfile_settings["submission_overrides"]: + parameters += [ + "--resolutionWidth", "*X*", + "--resolutionHeight", "*Y*" + ] + if "frame_range" in workfile_settings["submission_overrides"]: + parameters += [ + "--frameStart", "*START*", + "--frameEnd", "*END*" + ] + winreg.SetValueEx( hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, subprocess.list2cmdline(parameters) ) + self.log.debug(f"__ parameters: \"{parameters}\"") + # setting resolution parameters path_submit = "\\".join([ path_user_settings, "Dialogs", "SubmitOutput" @@ -135,3 +150,6 @@ class CelactionPrelaunchHook(PreLaunchHook): self.log.info(f"Workfile to open: \"{workfile_path}\"") return workfile_path + + def get_workfile_settings(self): + return self.data["project_settings"]["celaction"]["workfile"] diff --git a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py index 43b81b83e7..54dea15dff 100644 --- a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py +++ b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py @@ -39,7 +39,7 @@ class CollectCelactionCliKwargs(pyblish.api.Collector): passing_kwargs[key] = value if missing_kwargs: - raise RuntimeError("Missing arguments {}".format( + self.log.debug("Missing arguments {}".format( ", ".join( [f'"{key}"' for key in missing_kwargs] ) diff --git a/openpype/hosts/fusion/__init__.py b/openpype/hosts/fusion/__init__.py index ddae01890b..1da11ba9d1 100644 --- a/openpype/hosts/fusion/__init__.py +++ b/openpype/hosts/fusion/__init__.py @@ -1,10 +1,14 @@ from .addon import ( + get_fusion_version, FusionAddon, FUSION_HOST_DIR, + FUSION_VERSIONS_DICT, ) __all__ = ( + "get_fusion_version", "FusionAddon", "FUSION_HOST_DIR", + "FUSION_VERSIONS_DICT", ) diff --git a/openpype/hosts/fusion/addon.py b/openpype/hosts/fusion/addon.py index d1bd1566b7..45683cfbde 100644 --- a/openpype/hosts/fusion/addon.py +++ b/openpype/hosts/fusion/addon.py @@ -1,8 +1,52 @@ import os +import re from openpype.modules import OpenPypeModule, IHostAddon +from openpype.lib import Logger FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) +# FUSION_VERSIONS_DICT is used by the pre-launch hooks +# The keys correspond to all currently supported Fusion versions +# Each value is a list of corresponding Python home variables and a profile +# number, which is used by the profile hook to set Fusion profile variables. +FUSION_VERSIONS_DICT = { + 9: ("FUSION_PYTHON36_HOME", 9), + 16: ("FUSION16_PYTHON36_HOME", 16), + 17: ("FUSION16_PYTHON36_HOME", 16), + 18: ("FUSION_PYTHON3_HOME", 16), +} + + +def get_fusion_version(app_name): + """ + The function is triggered by the prelaunch hooks to get the fusion version. + + `app_name` is obtained by prelaunch hooks from the + `launch_context.env.get("AVALON_APP_NAME")`. + + To get a correct Fusion version, a version number should be present + in the `applications/fusion/variants` key + of the Blackmagic Fusion Application Settings. + """ + + log = Logger.get_logger(__name__) + + if not app_name: + return + + app_version_candidates = re.findall(r"\d+", app_name) + if not app_version_candidates: + return + for app_version in app_version_candidates: + if int(app_version) in FUSION_VERSIONS_DICT: + return int(app_version) + else: + log.info( + "Unsupported Fusion version: {app_version}".format( + app_version=app_version + ) + ) + class FusionAddon(OpenPypeModule, IHostAddon): name = "fusion" @@ -14,15 +58,11 @@ class FusionAddon(OpenPypeModule, IHostAddon): def get_launch_hook_paths(self, app): if app.host_name != self.host_name: return [] - return [ - os.path.join(FUSION_HOST_DIR, "hooks") - ] + return [os.path.join(FUSION_HOST_DIR, "hooks")] def add_implementation_envs(self, env, _app): # Set default values if are not already set via settings - defaults = { - "OPENPYPE_LOG_NO_COLORS": "Yes" - } + defaults = {"OPENPYPE_LOG_NO_COLORS": "Yes"} for key, value in defaults.items(): if not env.get(key): env[key] = value diff --git a/openpype/hosts/fusion/api/action.py b/openpype/hosts/fusion/api/action.py index 1750920950..ff5dd14caa 100644 --- a/openpype/hosts/fusion/api/action.py +++ b/openpype/hosts/fusion/api/action.py @@ -6,12 +6,13 @@ from openpype.pipeline.publish import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): - """Select invalid nodes in Maya when plug-in failed. + """Select invalid nodes in Fusion when plug-in failed. To retrieve the invalid nodes this assumes a static `get_invalid()` method is available on the plugin. """ + label = "Select invalid" on = "failed" # This action is only available on a failed plug-in icon = "search" # Icon from Awesome Icon @@ -31,8 +32,10 @@ class SelectInvalidAction(pyblish.api.Action): if isinstance(invalid_nodes, (list, tuple)): invalid.extend(invalid_nodes) else: - self.log.warning("Plug-in returned to be invalid, " - "but has no selectable nodes.") + self.log.warning( + "Plug-in returned to be invalid, " + "but has no selectable nodes." + ) if not invalid: # Assume relevant comp is current comp and clear selection @@ -51,4 +54,6 @@ class SelectInvalidAction(pyblish.api.Action): for tool in invalid: flow.Select(tool, True) names.add(tool.Name) - self.log.info("Selecting invalid tools: %s" % ", ".join(sorted(names))) + self.log.info( + "Selecting invalid tools: %s" % ", ".join(sorted(names)) + ) diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 88a3f0b49b..40cc4d2963 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -303,10 +303,18 @@ def get_frame_path(path): return filename, padding, ext -def get_current_comp(): - """Hack to get current comp in this session""" +def get_fusion_module(): + """Get current Fusion instance""" fusion = getattr(sys.modules["__main__"], "fusion", None) - return fusion.CurrentComp if fusion else None + return fusion + + +def get_current_comp(): + """Get current comp in this session""" + fusion = get_fusion_module() + if fusion is not None: + comp = fusion.CurrentComp + return comp @contextlib.contextmanager diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 343f5f803a..92f38a64c2 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -6,7 +6,6 @@ from openpype.tools.utils import host_tools from openpype.style import load_stylesheet from openpype.lib import register_event_callback from openpype.hosts.fusion.scripts import ( - set_rendermode, duplicate_with_inputs, ) from openpype.hosts.fusion.api.lib import ( @@ -60,7 +59,6 @@ class OpenPypeMenu(QtWidgets.QWidget): publish_btn = QtWidgets.QPushButton("Publish...", self) manager_btn = QtWidgets.QPushButton("Manage...", self) libload_btn = QtWidgets.QPushButton("Library...", self) - rendermode_btn = QtWidgets.QPushButton("Set render mode...", self) set_framerange_btn = QtWidgets.QPushButton("Set Frame Range", self) set_resolution_btn = QtWidgets.QPushButton("Set Resolution", self) duplicate_with_inputs_btn = QtWidgets.QPushButton( @@ -91,7 +89,6 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(set_framerange_btn) layout.addWidget(set_resolution_btn) - layout.addWidget(rendermode_btn) layout.addSpacing(20) @@ -108,7 +105,6 @@ class OpenPypeMenu(QtWidgets.QWidget): load_btn.clicked.connect(self.on_load_clicked) manager_btn.clicked.connect(self.on_manager_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rendermode_btn.clicked.connect(self.on_rendermode_clicked) duplicate_with_inputs_btn.clicked.connect( self.on_duplicate_with_inputs_clicked ) @@ -162,15 +158,6 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_libload_clicked(self): host_tools.show_library_loader() - def on_rendermode_clicked(self): - if self.render_mode_widget is None: - window = set_rendermode.SetRenderMode() - window.setStyleSheet(load_stylesheet()) - window.show() - self.render_mode_widget = window - else: - self.render_mode_widget.show() - def on_duplicate_with_inputs_clicked(self): duplicate_with_inputs.duplicate_with_input_connections() diff --git a/openpype/hosts/fusion/deploy/fusion_shared.prefs b/openpype/hosts/fusion/deploy/fusion_shared.prefs index 998c6a6d66..b379ea7c66 100644 --- a/openpype/hosts/fusion/deploy/fusion_shared.prefs +++ b/openpype/hosts/fusion/deploy/fusion_shared.prefs @@ -1,19 +1,19 @@ { Locked = true, Global = { - Paths = { - Map = { - ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy", - ["Reactor:"] = "$(REACTOR)", - - ["Config:"] = "UserPaths:Config;OpenPype:Config", - ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts", - ["UserPaths:"] = "UserData:;AllData:;Fusion:;Reactor:Deploy" - }, - }, - Script = { - PythonVersion = 3, - Python3Forced = true - }, + Paths = { + Map = { + ["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy", + ["Config:"] = "UserPaths:Config;OpenPype:Config", + ["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts", }, -} \ No newline at end of file + }, + Script = { + PythonVersion = 3, + Python3Forced = true + }, + UserInterface = { + Language = "en_US" + }, + }, +} diff --git a/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py new file mode 100644 index 0000000000..fd726ccda1 --- /dev/null +++ b/openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -0,0 +1,161 @@ +import os +import shutil +import platform +from pathlib import Path +from openpype.lib import PreLaunchHook, ApplicationLaunchFailed +from openpype.hosts.fusion import ( + FUSION_HOST_DIR, + FUSION_VERSIONS_DICT, + get_fusion_version, +) + + +class FusionCopyPrefsPrelaunch(PreLaunchHook): + """ + Prepares local Fusion profile directory, copies existing Fusion profile. + This also sets FUSION MasterPrefs variable, which is used + to apply Master.prefs file to override some Fusion profile settings to: + - enable the OpenPype menu + - force Python 3 over Python 2 + - force English interface + Master.prefs is defined in openpype/hosts/fusion/deploy/fusion_shared.prefs + """ + + app_groups = ["fusion"] + order = 2 + + def get_fusion_profile_name(self, profile_version) -> str: + # Returns 'Default', unless FUSION16_PROFILE is set + return os.getenv(f"FUSION{profile_version}_PROFILE", "Default") + + def get_fusion_profile_dir(self, profile_version) -> Path: + # Get FUSION_PROFILE_DIR variable + fusion_profile = self.get_fusion_profile_name(profile_version) + fusion_var_prefs_dir = os.getenv( + f"FUSION{profile_version}_PROFILE_DIR" + ) + + # Check if FUSION_PROFILE_DIR exists + if fusion_var_prefs_dir and Path(fusion_var_prefs_dir).is_dir(): + fu_prefs_dir = Path(fusion_var_prefs_dir, fusion_profile) + self.log.info(f"{fusion_var_prefs_dir} is set to {fu_prefs_dir}") + return fu_prefs_dir + + def get_profile_source(self, profile_version) -> Path: + """Get Fusion preferences profile location. + See Per-User_Preferences_and_Paths on VFXpedia for reference. + """ + fusion_profile = self.get_fusion_profile_name(profile_version) + profile_source = self.get_fusion_profile_dir(profile_version) + if profile_source: + return profile_source + # otherwise get default location of the profile folder + fu_prefs_dir = f"Blackmagic Design/Fusion/Profiles/{fusion_profile}" + if platform.system() == "Windows": + profile_source = Path(os.getenv("AppData"), fu_prefs_dir) + elif platform.system() == "Darwin": + profile_source = Path( + "~/Library/Application Support/", fu_prefs_dir + ).expanduser() + elif platform.system() == "Linux": + profile_source = Path("~/.fusion", fu_prefs_dir).expanduser() + self.log.info( + f"Locating source Fusion prefs directory: {profile_source}" + ) + return profile_source + + def get_copy_fusion_prefs_settings(self): + # Get copy preferences options from the global application settings + + copy_fusion_settings = self.data["project_settings"]["fusion"].get( + "copy_fusion_settings", {} + ) + if not copy_fusion_settings: + self.log.error("Copy prefs settings not found") + copy_status = copy_fusion_settings.get("copy_status", False) + force_sync = copy_fusion_settings.get("force_sync", False) + copy_path = copy_fusion_settings.get("copy_path") or None + if copy_path: + copy_path = Path(copy_path).expanduser() + return copy_status, copy_path, force_sync + + def copy_fusion_profile( + self, copy_from: Path, copy_to: Path, force_sync: bool + ) -> None: + """On the first Fusion launch copy the contents of Fusion profile + directory to the working predefined location. If the Openpype profile + folder exists, skip copying, unless re-sync is checked. + If the prefs were not copied on the first launch, + clean Fusion profile will be created in fu_profile_dir. + """ + if copy_to.exists() and not force_sync: + self.log.info( + "Destination Fusion preferences folder already exists: " + f"{copy_to} " + ) + return + self.log.info("Starting copying Fusion preferences") + self.log.debug(f"force_sync option is set to {force_sync}") + try: + copy_to.mkdir(exist_ok=True, parents=True) + except PermissionError: + self.log.warning(f"Creating the folder not permitted at {copy_to}") + return + if not copy_from.exists(): + self.log.warning(f"Fusion preferences not found in {copy_from}") + return + for file in copy_from.iterdir(): + if file.suffix in ( + ".prefs", + ".def", + ".blocklist", + ".fu", + ".toolbars", + ): + # convert Path to str to be compatible with Python 3.6+ + shutil.copy(str(file), str(copy_to)) + self.log.info( + f"Successfully copied preferences: {copy_from} to {copy_to}" + ) + + def execute(self): + ( + copy_status, + fu_profile_dir, + force_sync, + ) = self.get_copy_fusion_prefs_settings() + + # Get launched application context and return correct app version + app_name = self.launch_context.env.get("AVALON_APP_NAME") + app_version = get_fusion_version(app_name) + if app_version is None: + version_names = ", ".join(str(x) for x in FUSION_VERSIONS_DICT) + raise ApplicationLaunchFailed( + "Unable to detect valid Fusion version number from app " + f"name: {app_name}.\nMake sure to include at least a digit " + "to indicate the Fusion version like '18'.\n" + f"Detectable Fusion versions are: {version_names}" + ) + + _, profile_version = FUSION_VERSIONS_DICT[app_version] + fu_profile = self.get_fusion_profile_name(profile_version) + + # do a copy of Fusion profile if copy_status toggle is enabled + if copy_status and fu_profile_dir is not None: + profile_source = self.get_profile_source(profile_version) + dest_folder = Path(fu_profile_dir, fu_profile) + self.copy_fusion_profile(profile_source, dest_folder, force_sync) + + # Add temporary profile directory variables to customize Fusion + # to define where it can read custom scripts and tools from + fu_profile_dir_variable = f"FUSION{profile_version}_PROFILE_DIR" + self.log.info(f"Setting {fu_profile_dir_variable}: {fu_profile_dir}") + self.launch_context.env[fu_profile_dir_variable] = str(fu_profile_dir) + + # Add custom Fusion Master Prefs and the temporary + # profile directory variables to customize Fusion + # to define where it can read custom scripts and tools from + master_prefs_variable = f"FUSION{profile_version}_MasterPrefs" + master_prefs = Path(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") + self.log.info(f"Setting {master_prefs_variable}: {master_prefs}") + self.launch_context.env[master_prefs_variable] = str(master_prefs) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py index 323b8b0029..f27cd1674b 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,32 +1,43 @@ import os from openpype.lib import PreLaunchHook, ApplicationLaunchFailed -from openpype.hosts.fusion import FUSION_HOST_DIR +from openpype.hosts.fusion import ( + FUSION_HOST_DIR, + FUSION_VERSIONS_DICT, + get_fusion_version, +) class FusionPrelaunch(PreLaunchHook): - """Prepares OpenPype Fusion environment - - Requires FUSION_PYTHON3_HOME to be defined in the environment for Fusion - to point at a valid Python 3 build for Fusion. That is Python 3.3-3.10 - for Fusion 18 and Fusion 3.6 for Fusion 16 and 17. - - This also sets FUSION16_MasterPrefs to apply the fusion master prefs - as set in openpype/hosts/fusion/deploy/fusion_shared.prefs to enable - the OpenPype menu and force Python 3 over Python 2. - """ + Prepares OpenPype Fusion environment. + Requires correct Python home variable to be defined in the environment + settings for Fusion to point at a valid Python 3 build for Fusion. + Python3 versions that are supported by Fusion: + Fusion 9, 16, 17 : Python 3.6 + Fusion 18 : Python 3.6 - 3.10 + """ + app_groups = ["fusion"] + order = 1 def execute(self): # making sure python 3 is installed at provided path # Py 3.3-3.10 for Fusion 18+ or Py 3.6 for Fu 16-17 - py3_var = "FUSION_PYTHON3_HOME" + app_data = self.launch_context.env.get("AVALON_APP_NAME") + app_version = get_fusion_version(app_data) + if not app_version: + raise ApplicationLaunchFailed( + "Fusion version information not found in System settings.\n" + "The key field in the 'applications/fusion/variants' should " + "consist a number, corresponding to major Fusion version." + ) + py3_var, _ = FUSION_VERSIONS_DICT[app_version] fusion_python3_home = self.launch_context.env.get(py3_var, "") - self.log.info(f"Looking for Python 3 in: {fusion_python3_home}") for path in fusion_python3_home.split(os.pathsep): - # Allow defining multiple paths to allow "fallback" to other - # path. But make to set only a single path as final variable. + # Allow defining multiple paths, separated by os.pathsep, + # to allow "fallback" to other path. + # But make to set only a single path as final variable. py3_dir = os.path.normpath(path) if os.path.isdir(py3_dir): break @@ -43,19 +54,10 @@ class FusionPrelaunch(PreLaunchHook): self.launch_context.env[py3_var] = py3_dir # Fusion 18+ requires FUSION_PYTHON3_HOME to also be on PATH - self.launch_context.env["PATH"] += ";" + py3_dir + if app_version >= 18: + self.launch_context.env["PATH"] += os.pathsep + py3_dir - # Fusion 16 and 17 use FUSION16_PYTHON36_HOME instead of - # FUSION_PYTHON3_HOME and will only work with a Python 3.6 version - # TODO: Detect Fusion version to only set for specific Fusion build - self.launch_context.env["FUSION16_PYTHON36_HOME"] = py3_dir + self.launch_context.env[py3_var] = py3_dir - # Add our Fusion Master Prefs which is the only way to customize - # Fusion to define where it can read custom scripts and tools from self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}") self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR - - pref_var = "FUSION16_MasterPrefs" # used by Fusion 16, 17 and 18 - prefs = os.path.join(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs") - self.log.info(f"Setting {pref_var}: {prefs}") - self.launch_context.env[pref_var] = prefs diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index e581bac20f..56085b0a06 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -4,29 +4,34 @@ import qtawesome from openpype.hosts.fusion.api import ( get_current_comp, - comp_lock_and_undo_chunk + comp_lock_and_undo_chunk, ) -from openpype.lib import BoolDef +from openpype.lib import ( + BoolDef, + EnumDef, +) from openpype.pipeline import ( legacy_io, Creator, - CreatedInstance + CreatedInstance, +) +from openpype.client import ( + get_asset_by_name, ) -from openpype.client import get_asset_by_name class CreateSaver(Creator): identifier = "io.openpype.creators.fusion.saver" - name = "saver" - label = "Saver" + label = "Render (saver)" + name = "render" family = "render" - default_variants = ["Main"] - + default_variants = ["Main", "Mask"] description = "Fusion Saver to generate image sequence" - def create(self, subset_name, instance_data, pre_create_data): + instance_attributes = ["reviewable"] + def create(self, subset_name, instance_data, pre_create_data): # TODO: Add pre_create attributes to choose file format? file_format = "OpenEXRFormat" @@ -58,7 +63,8 @@ class CreateSaver(Creator): family=self.family, subset_name=subset_name, data=instance_data, - creator=self) + creator=self, + ) # Insert the transient data instance.transient_data["tool"] = saver @@ -68,11 +74,9 @@ class CreateSaver(Creator): return instance def collect_instances(self): - comp = get_current_comp() tools = comp.GetToolList(False, "Saver").values() for tool in tools: - data = self.get_managed_tool_data(tool) if not data: data = self._collect_unmanaged_saver(tool) @@ -90,7 +94,6 @@ class CreateSaver(Creator): def update_instances(self, update_list): for created_inst, _changes in update_list: - new_data = created_inst.data_to_store() tool = created_inst.transient_data["tool"] self._update_tool_with_data(tool, new_data) @@ -139,7 +142,6 @@ class CreateSaver(Creator): tool.SetAttrs({"TOOLS_Name": subset}) def _collect_unmanaged_saver(self, tool): - # TODO: this should not be done this way - this should actually # get the data as stored on the tool explicitly (however) # that would disallow any 'regular saver' to be collected @@ -153,8 +155,7 @@ class CreateSaver(Creator): asset = legacy_io.Session["AVALON_ASSET"] task = legacy_io.Session["AVALON_TASK"] - asset_doc = get_asset_by_name(project_name=project, - asset_name=asset) + asset_doc = get_asset_by_name(project_name=project, asset_name=asset) path = tool["Clip"][comp.TIME_UNDEFINED] fname = os.path.basename(path) @@ -178,21 +179,20 @@ class CreateSaver(Creator): "variant": variant, "active": not passthrough, "family": self.family, - # Unique identifier for instance and this creator "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier + "creator_identifier": self.identifier, } def get_managed_tool_data(self, tool): """Return data of the tool if it matches creator identifier""" - data = tool.GetData('openpype') + data = tool.GetData("openpype") if not isinstance(data, dict): return required = { "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier + "creator_identifier": self.identifier, } for key, value in required.items(): if key not in data or data[key] != value: @@ -205,11 +205,40 @@ class CreateSaver(Creator): return data - def get_instance_attr_defs(self): - return [ - BoolDef( - "review", - default=True, - label="Review" - ) + def get_pre_create_attr_defs(self): + """Settings for create page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), ] + return attr_defs + + def get_instance_attr_defs(self): + """Settings for publish page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), + ] + return attr_defs + + # These functions below should be moved to another file + # so it can be used by other plugins. plugin.py ? + + def _get_render_target_enum(self): + rendering_targets = { + "local": "Local machine rendering", + "frames": "Use existing frames", + } + if "farm_rendering" in self.instance_attributes: + rendering_targets["farm"] = "Farm rendering" + + return EnumDef( + "render_target", items=rendering_targets, label="Render target" + ) + + def _get_reviewable_bool(self): + return BoolDef( + "review", + default=("reviewable" in self.instance_attributes), + label="Review", + ) diff --git a/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py b/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py new file mode 100644 index 0000000000..0ba777629f --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py @@ -0,0 +1,50 @@ +import pyblish.api +from openpype.pipeline import publish +import os + + +class CollectFusionExpectedFrames( + pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin +): + """Collect all frames needed to publish expected frames""" + + order = pyblish.api.CollectorOrder + 0.5 + label = "Collect Expected Frames" + hosts = ["fusion"] + families = ["render"] + + def process(self, instance): + context = instance.context + + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + basename = os.path.basename(path) + head, ext = os.path.splitext(basename) + files = [ + f"{head}{str(frame).zfill(4)}{ext}" + for frame in range(frame_start, frame_end + 1) + ] + repre = { + "name": ext[1:], + "ext": ext[1:], + "frameStart": f"%0{len(str(frame_end))}d" % frame_start, + "files": files, + "stagingDir": output_dir, + } + + self.set_representation_colorspace( + representation=repre, + context=context, + ) + + # review representation + if instance.data.get("review", False): + repre["tags"] = ["review"] + + # add the repre to the instance + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(repre) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render_target.py b/openpype/hosts/fusion/plugins/publish/collect_render_target.py deleted file mode 100644 index 39017f32e0..0000000000 --- a/openpype/hosts/fusion/plugins/publish/collect_render_target.py +++ /dev/null @@ -1,44 +0,0 @@ -import pyblish.api - - -class CollectFusionRenderMode(pyblish.api.InstancePlugin): - """Collect current comp's render Mode - - Options: - local - farm - - Note that this value is set for each comp separately. When you save the - comp this information will be stored in that file. If for some reason the - available tool does not visualize which render mode is set for the - current comp, please run the following line in the console (Py2) - - comp.GetData("openpype.rendermode") - - This will return the name of the current render mode as seen above under - Options. - - """ - - order = pyblish.api.CollectorOrder + 0.4 - label = "Collect Render Mode" - hosts = ["fusion"] - families = ["render"] - - def process(self, instance): - """Collect all image sequence tools""" - options = ["local", "farm"] - - comp = instance.context.data.get("currentComp") - if not comp: - raise RuntimeError("No comp previously collected, unable to " - "retrieve Fusion version.") - - rendermode = comp.GetData("openpype.rendermode") or "local" - assert rendermode in options, "Must be supported render mode" - - self.log.info("Render mode: {0}".format(rendermode)) - - # Append family - family = "render.{0}".format(rendermode) - instance.data["families"].append(family) diff --git a/openpype/hosts/fusion/plugins/publish/collect_renders.py b/openpype/hosts/fusion/plugins/publish/collect_renders.py new file mode 100644 index 0000000000..7f38e68447 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_renders.py @@ -0,0 +1,25 @@ +import pyblish.api + + +class CollectFusionRenders(pyblish.api.InstancePlugin): + """Collect current saver node's render Mode + + Options: + local (Render locally) + frames (Use existing frames) + + """ + + order = pyblish.api.CollectorOrder + 0.4 + label = "Collect Renders" + hosts = ["fusion"] + families = ["render"] + + def process(self, instance): + render_target = instance.data["render_target"] + family = instance.data["family"] + + # add targeted family to families + instance.data["families"].append( + "{}.{}".format(family, render_target) + ) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py new file mode 100644 index 0000000000..5a0140c525 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -0,0 +1,109 @@ +import logging +import contextlib +import pyblish.api +from openpype.hosts.fusion.api import comp_lock_and_undo_chunk + + +log = logging.getLogger(__name__) + + +@contextlib.contextmanager +def enabled_savers(comp, savers): + """Enable only the `savers` in Comp during the context. + + Any Saver tool in the passed composition that is not in the savers list + will be set to passthrough during the context. + + Args: + comp (object): Fusion composition object. + savers (list): List of Saver tool objects. + + """ + passthrough_key = "TOOLB_PassThrough" + original_states = {} + enabled_save_names = {saver.Name for saver in savers} + try: + all_savers = comp.GetToolList(False, "Saver").values() + for saver in all_savers: + original_state = saver.GetAttrs()[passthrough_key] + original_states[saver] = original_state + + # The passthrough state we want to set (passthrough != enabled) + state = saver.Name not in enabled_save_names + if state != original_state: + saver.SetAttrs({passthrough_key: state}) + yield + finally: + for saver, original_state in original_states.items(): + saver.SetAttrs({"TOOLB_PassThrough": original_state}) + + +class FusionRenderLocal(pyblish.api.InstancePlugin): + """Render the current Fusion composition locally.""" + + order = pyblish.api.ExtractorOrder - 0.2 + label = "Render Local" + hosts = ["fusion"] + families = ["render.local"] + + def process(self, instance): + context = instance.context + + # Start render + self.render_once(context) + + # Log render status + self.log.info( + "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( + nm=instance.data["name"], + ast=instance.data["asset"], + tsk=instance.data["task"], + ) + ) + + def render_once(self, context): + """Render context comp only once, even with more render instances""" + + # This plug-in assumes all render nodes get rendered at the same time + # to speed up the rendering. The check below makes sure that we only + # execute the rendering once and not for each instance. + key = f"__hasRun{self.__class__.__name__}" + + savers_to_render = [ + # Get the saver tool from the instance + instance[0] for instance in context if + # Only active instances + instance.data.get("publish", True) and + # Only render.local instances + "render.local" in instance.data["families"] + ] + + if key not in context.data: + # We initialize as false to indicate it wasn't successful yet + # so we can keep track of whether Fusion succeeded + context.data[key] = False + + current_comp = context.data["currentComp"] + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] + + self.log.info("Starting Fusion render") + self.log.info(f"Start frame: {frame_start}") + self.log.info(f"End frame: {frame_end}") + saver_names = ", ".join(saver.Name for saver in savers_to_render) + self.log.info(f"Rendering tools: {saver_names}") + + with comp_lock_and_undo_chunk(current_comp): + with enabled_savers(current_comp, savers_to_render): + result = current_comp.Render( + { + "Start": frame_start, + "End": frame_end, + "Wait": True, + } + ) + + context.data[key] = bool(result) + + if context.data[key] is False: + raise RuntimeError("Comp render failed") diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py deleted file mode 100644 index 7d5f1a40c7..0000000000 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ /dev/null @@ -1,100 +0,0 @@ -import os -import pyblish.api -from openpype.pipeline import publish -from openpype.hosts.fusion.api import comp_lock_and_undo_chunk - - -class Fusionlocal(pyblish.api.InstancePlugin, - publish.ColormanagedPyblishPluginMixin): - """Render the current Fusion composition locally. - - Extract the result of savers by starting a comp render - This will run the local render of Fusion. - - """ - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Render Local" - hosts = ["fusion"] - families = ["render.local"] - - def process(self, instance): - context = instance.context - - # Start render - self.render_once(context) - - # Log render status - self.log.info( - "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( - nm=instance.data["name"], - ast=instance.data["asset"], - tsk=instance.data["task"], - ) - ) - - frame_start = context.data["frameStartHandle"] - frame_end = context.data["frameEndHandle"] - path = instance.data["path"] - output_dir = instance.data["outputDir"] - - basename = os.path.basename(path) - head, ext = os.path.splitext(basename) - files = [ - f"{head}{str(frame).zfill(4)}{ext}" - for frame in range(frame_start, frame_end + 1) - ] - repre = { - "name": ext[1:], - "ext": ext[1:], - "frameStart": f"%0{len(str(frame_end))}d" % frame_start, - "files": files, - "stagingDir": output_dir, - } - - self.set_representation_colorspace( - representation=repre, - context=context, - ) - - if "representations" not in instance.data: - instance.data["representations"] = [] - instance.data["representations"].append(repre) - - # review representation - if instance.data.get("review", False): - repre["tags"] = ["review", "ftrackreview"] - - def render_once(self, context): - """Render context comp only once, even with more render instances""" - - # This plug-in assumes all render nodes get rendered at the same time - # to speed up the rendering. The check below makes sure that we only - # execute the rendering once and not for each instance. - key = f"__hasRun{self.__class__.__name__}" - if key not in context.data: - # We initialize as false to indicate it wasn't successful yet - # so we can keep track of whether Fusion succeeded - context.data[key] = False - - current_comp = context.data["currentComp"] - frame_start = context.data["frameStartHandle"] - frame_end = context.data["frameEndHandle"] - - self.log.info("Starting Fusion render") - self.log.info(f"Start frame: {frame_start}") - self.log.info(f"End frame: {frame_end}") - - with comp_lock_and_undo_chunk(current_comp): - result = current_comp.Render( - { - "Start": frame_start, - "End": frame_end, - "Wait": True, - } - ) - - context.data[key] = bool(result) - - if context.data[key] is False: - raise RuntimeError("Comp render failed") diff --git a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index ba943abacb..8a91f23578 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -14,22 +14,19 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - actions = [RepairAction] label = "Validate Create Folder Checked" families = ["render"] hosts = ["fusion"] - actions = [SelectInvalidAction] + actions = [RepairAction, SelectInvalidAction] @classmethod def get_invalid(cls, instance): - active = instance.data.get("active", instance.data.get("publish")) - if not active: - return [] - tool = instance[0] create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: - cls.log.error("%s has Create Folder turned off" % instance[0].Name) + cls.log.error( + "%s has Create Folder turned off" % instance[0].Name + ) return [tool] def process(self, instance): @@ -37,7 +34,8 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): if invalid: raise PublishValidationError( "Found Saver with Create Folder During Render checked off", - title=self.label) + title=self.label, + ) @classmethod def repair(cls, instance): diff --git a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py new file mode 100644 index 0000000000..c208b8ef15 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py @@ -0,0 +1,78 @@ +import os +import pyblish.api + +from openpype.pipeline.publish import RepairAction +from openpype.pipeline import PublishValidationError + +from openpype.hosts.fusion.api.action import SelectInvalidAction + + +class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): + """Checks if files for savers that's set + to publish expected frames exists + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Expected Frames Exists" + families = ["render"] + hosts = ["fusion"] + actions = [RepairAction, SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance, non_existing_frames=None): + if non_existing_frames is None: + non_existing_frames = [] + + if instance.data.get("render_target") == "frames": + tool = instance[0] + + frame_start = instance.data["frameStart"] + frame_end = instance.data["frameEnd"] + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + basename = os.path.basename(path) + head, ext = os.path.splitext(basename) + files = [ + f"{head}{str(frame).zfill(4)}{ext}" + for frame in range(frame_start, frame_end + 1) + ] + + for file in files: + if not os.path.exists(os.path.join(output_dir, file)): + cls.log.error( + f"Missing file: {os.path.join(output_dir, file)}" + ) + non_existing_frames.append(file) + + if len(non_existing_frames) > 0: + cls.log.error(f"Some of {tool.Name}'s files does not exist") + return [tool] + + def process(self, instance): + non_existing_frames = [] + invalid = self.get_invalid(instance, non_existing_frames) + if invalid: + raise PublishValidationError( + "{} is set to publish existing frames but " + "some frames are missing. " + "The missing file(s) are:\n\n{}".format( + invalid[0].Name, + "\n\n".join(non_existing_frames), + ), + title=self.label, + ) + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + if invalid: + tool = invalid[0] + + # Change render target to local to render locally + tool.SetData("openpype.creator_attributes.render_target", "local") + + cls.log.info( + f"Reload the publisher and {tool.Name} " + "will be set to render locally" + ) diff --git a/openpype/hosts/fusion/scripts/set_rendermode.py b/openpype/hosts/fusion/scripts/set_rendermode.py deleted file mode 100644 index 9d2bfef310..0000000000 --- a/openpype/hosts/fusion/scripts/set_rendermode.py +++ /dev/null @@ -1,112 +0,0 @@ -from qtpy import QtWidgets -import qtawesome -from openpype.hosts.fusion.api import get_current_comp - - -_help = {"local": "Render the comp on your own machine and publish " - "it from that the destination folder", - "farm": "Submit a Fusion render job to a Render farm to use all other" - " computers and add a publish job"} - - -class SetRenderMode(QtWidgets.QWidget): - - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - - self._comp = get_current_comp() - self._comp_name = self._get_comp_name() - - self.setWindowTitle("Set Render Mode") - self.setFixedSize(300, 175) - - layout = QtWidgets.QVBoxLayout() - - # region comp info - comp_info_layout = QtWidgets.QHBoxLayout() - - update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh", - color="white"), "") - update_btn.setFixedWidth(25) - update_btn.setFixedHeight(25) - - comp_information = QtWidgets.QLineEdit() - comp_information.setEnabled(False) - - comp_info_layout.addWidget(comp_information) - comp_info_layout.addWidget(update_btn) - # endregion comp info - - # region modes - mode_options = QtWidgets.QComboBox() - mode_options.addItems(_help.keys()) - - mode_information = QtWidgets.QTextEdit() - mode_information.setReadOnly(True) - # endregion modes - - accept_btn = QtWidgets.QPushButton("Accept") - - layout.addLayout(comp_info_layout) - layout.addWidget(mode_options) - layout.addWidget(mode_information) - layout.addWidget(accept_btn) - - self.setLayout(layout) - - self.comp_information = comp_information - self.update_btn = update_btn - - self.mode_options = mode_options - self.mode_information = mode_information - - self.accept_btn = accept_btn - - self.connections() - self.update() - - # Force updated render mode help text - self._update_rendermode_info() - - def connections(self): - """Build connections between code and buttons""" - - self.update_btn.clicked.connect(self.update) - self.accept_btn.clicked.connect(self._set_comp_rendermode) - self.mode_options.currentIndexChanged.connect( - self._update_rendermode_info) - - def update(self): - """Update all information in the UI""" - - self._comp = get_current_comp() - self._comp_name = self._get_comp_name() - self.comp_information.setText(self._comp_name) - - # Update current comp settings - mode = self._get_comp_rendermode() - index = self.mode_options.findText(mode) - self.mode_options.setCurrentIndex(index) - - def _update_rendermode_info(self): - rendermode = self.mode_options.currentText() - self.mode_information.setText(_help[rendermode]) - - def _get_comp_name(self): - return self._comp.GetAttrs("COMPS_Name") - - def _get_comp_rendermode(self): - return self._comp.GetData("openpype.rendermode") or "local" - - def _set_comp_rendermode(self): - rendermode = self.mode_options.currentText() - self._comp.SetData("openpype.rendermode", rendermode) - - self._comp.Print("Updated render mode to '%s'\n" % rendermode) - self.hide() - - def _validation(self): - ui_mode = self.mode_options.currentText() - comp_mode = self._get_comp_rendermode() - - return comp_mode == ui_mode diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index bbd1edc14a..0d4368529f 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -1221,7 +1221,7 @@ def set_track_color(track_item, color): def check_inventory_versions(track_items=None): """ - Actual version color idetifier of Loaded containers + Actual version color identifier of Loaded containers Check all track items and filter only Loader nodes for its version. It will get all versions from database @@ -1249,10 +1249,10 @@ def check_inventory_versions(track_items=None): project_name = legacy_io.active_project() filter_result = filter_containers(containers, project_name) for container in filter_result.latest: - set_track_color(container["_item"], clip_color) + set_track_color(container["_item"], clip_color_last) for container in filter_result.outdated: - set_track_color(container["_item"], clip_color_last) + set_track_color(container["_item"], clip_color) def selection_changed_timeline(event): diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 07457db1a4..5ca901caaa 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -146,6 +146,8 @@ class CreatorWidget(QtWidgets.QDialog): return " ".join([str(m.group(0)).capitalize() for m in matches]) def create_row(self, layout, type, text, **kwargs): + value_keys = ["setText", "setCheckState", "setValue", "setChecked"] + # get type attribute from qwidgets attr = getattr(QtWidgets, type) @@ -167,14 +169,27 @@ class CreatorWidget(QtWidgets.QDialog): # assign the created attribute to variable item = getattr(self, attr_name) + + # set attributes to item which are not values for func, val in kwargs.items(): + if func in value_keys: + continue + if getattr(item, func): + log.debug("Setting {} to {}".format(func, val)) func_attr = getattr(item, func) if isinstance(val, tuple): func_attr(*val) else: func_attr(val) + # set values to item + for value_item in value_keys: + if value_item not in kwargs: + continue + if getattr(item, value_item): + getattr(item, value_item)(kwargs[value_item]) + # add to layout layout.addRow(label, item) @@ -276,8 +291,11 @@ class CreatorWidget(QtWidgets.QDialog): elif v["type"] == "QSpinBox": data[k]["value"] = self.create_row( content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMinimum=0, + setValue=v["value"], + setDisplayIntegerBase=10000, + setRange=(0, 99999), setMinimum=0, setMaximum=100000, setToolTip=tool_tip) + return data diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index c08114b71b..47a4653d5d 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -10,7 +10,7 @@ import hou from openpype.tools.utils import host_tools parent = hou.qt.mainWindow() -host_tools.show_creator(parent) +host_tools.show_publisher(parent, tab="create") ]]> @@ -30,7 +30,7 @@ host_tools.show_loader(parent=parent, use_context=True) import hou from openpype.tools.utils import host_tools parent = hou.qt.mainWindow() -host_tools.show_publisher(parent) +host_tools.show_publisher(parent, tab="publish") ]]> @@ -66,8 +66,8 @@ host_tools.show_workfiles(parent) ]]> - - + + dict: + """Get the current assets frame range and handles. + + Returns: + dict: with frame start, frame end, handle start, handle end. + """ + # Set frame start/end + asset = get_current_project_asset() + frame_start = asset["data"].get("frameStart") + frame_end = asset["data"].get("frameEnd") + # Backwards compatibility + if frame_start is None or frame_end is None: + frame_start = asset["data"].get("edit_in") + frame_end = asset["data"].get("edit_out") + if frame_start is None or frame_end is None: + return + handles = asset["data"].get("handles") or 0 + handle_start = asset["data"].get("handleStart") + if handle_start is None: + handle_start = handles + handle_end = asset["data"].get("handleEnd") + if handle_end is None: + handle_end = handles + return { + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end + } + + +def reset_frame_range(fps: bool = True): + """Set frame range to current asset. + This is part of 3dsmax documentation: + + animationRange: A System Global variable which lets you get and + set an Interval value that defines the start and end frames + of the Active Time Segment. + frameRate: A System Global variable which lets you get + and set an Integer value that defines the current + scene frame rate in frames-per-second. + """ + if fps: + data_fps = get_current_project(fields=["data.fps"]) + fps_number = float(data_fps["data"]["fps"]) + rt.frameRate = fps_number + frame_range = get_frame_range() + frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) + frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) + frange_cmd = f"animationRange = interval {frame_start} {frame_end}" + rt.execute(frange_cmd) + + +def set_context_setting(): + """Apply the project settings from the project definition + + Settings can be overwritten by an asset if the asset.data contains + any information regarding those settings. + + Examples of settings: + frame range + resolution + + Returns: + None + """ + reset_scene_resolution() + + def get_max_version(): """ Args: diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a74a6a7426..350eb97661 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -8,6 +8,7 @@ from openpype.hosts.max.api.lib import ( get_current_renderer, get_default_render_folder ) +from openpype.pipeline.context_tools import get_current_project_asset from openpype.settings import get_project_settings from openpype.pipeline import legacy_io @@ -34,14 +35,20 @@ class RenderProducts(object): filename, container) + context = get_current_project_asset() + startFrame = context["data"].get("frameStart") + endFrame = context["data"].get("frameEnd") + 1 + img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - full_render_list = [] - beauty = self.beauty_render_product(output_file, img_fmt) - full_render_list.append(beauty) + full_render_list = self.beauty_render_product(output_file, + startFrame, + endFrame, + img_fmt) renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] + if renderer == "VUE_File_Renderer": return full_render_list @@ -54,6 +61,8 @@ class RenderProducts(object): "Quicksilver_Hardware_Renderer", ]: render_elem_list = self.render_elements_product(output_file, + startFrame, + endFrame, img_fmt) if render_elem_list: full_render_list.extend(iter(render_elem_list)) @@ -61,18 +70,24 @@ class RenderProducts(object): if renderer == "Arnold": aov_list = self.arnold_render_product(output_file, + startFrame, + endFrame, img_fmt) if aov_list: full_render_list.extend(iter(aov_list)) return full_render_list - def beauty_render_product(self, folder, fmt): - beauty_output = f"{folder}.####.{fmt}" - beauty_output = beauty_output.replace("\\", "/") - return beauty_output + def beauty_render_product(self, folder, startFrame, endFrame, fmt): + beauty_frame_range = [] + for f in range(startFrame, endFrame): + beauty_output = f"{folder}.{f}.{fmt}" + beauty_output = beauty_output.replace("\\", "/") + beauty_frame_range.append(beauty_output) + + return beauty_frame_range # TODO: Get the arnold render product - def arnold_render_product(self, folder, fmt): + def arnold_render_product(self, folder, startFrame, endFrame, fmt): """Get all the Arnold AOVs""" aovs = [] @@ -85,15 +100,17 @@ class RenderProducts(object): for i in range(aov_group_num): # get the specific AOV group for aov in aov_mgr.drivers[i].aov_list: - render_element = f"{folder}_{aov.name}.####.{fmt}" - render_element = render_element.replace("\\", "/") - aovs.append(render_element) + for f in range(startFrame, endFrame): + render_element = f"{folder}_{aov.name}.{f}.{fmt}" + render_element = render_element.replace("\\", "/") + aovs.append(render_element) + # close the AOVs manager window amw.close() return aovs - def render_elements_product(self, folder, fmt): + def render_elements_product(self, folder, startFrame, endFrame, fmt): """Get all the render element output files. """ render_dirname = [] @@ -104,9 +121,10 @@ class RenderProducts(object): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") if renderlayer_name.enabled: - render_element = f"{folder}_{renderpass}.####.{fmt}" - render_element = render_element.replace("\\", "/") - render_dirname.append(render_element) + for f in range(startFrame, endFrame): + render_element = f"{folder}_{renderpass}.{f}.{fmt}" + render_element = render_element.replace("\\", "/") + render_dirname.append(render_element) return render_dirname diff --git a/openpype/hosts/max/api/menu.py b/openpype/hosts/max/api/menu.py index 5c273b49b4..066cc90039 100644 --- a/openpype/hosts/max/api/menu.py +++ b/openpype/hosts/max/api/menu.py @@ -4,6 +4,7 @@ from qtpy import QtWidgets, QtCore from pymxs import runtime as rt from openpype.tools.utils import host_tools +from openpype.hosts.max.api import lib class OpenPypeMenu(object): @@ -107,6 +108,17 @@ class OpenPypeMenu(object): workfiles_action = QtWidgets.QAction("Work Files...", openpype_menu) workfiles_action.triggered.connect(self.workfiles_callback) openpype_menu.addAction(workfiles_action) + + openpype_menu.addSeparator() + + res_action = QtWidgets.QAction("Set Resolution", openpype_menu) + res_action.triggered.connect(self.resolution_callback) + openpype_menu.addAction(res_action) + + frame_action = QtWidgets.QAction("Set Frame Range", openpype_menu) + frame_action.triggered.connect(self.frame_range_callback) + openpype_menu.addAction(frame_action) + return openpype_menu def load_callback(self): @@ -128,3 +140,11 @@ class OpenPypeMenu(object): def workfiles_callback(self): """Callback to show Workfiles tool.""" host_tools.show_workfiles(parent=self.main_widget) + + def resolution_callback(self): + """Callback to reset scene resolution""" + return lib.reset_scene_resolution() + + def frame_range_callback(self): + """Callback to reset frame range""" + return lib.reset_frame_range() diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index f8a7b8ea5c..dacc402318 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -50,6 +50,11 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): self._has_been_setup = True + def context_setting(): + return lib.set_context_setting() + rt.callbacks.addScript(rt.Name('systemPostNew'), + context_setting) + def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? return True diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index c16d9e61ec..b54568b360 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -101,7 +101,9 @@ class MaxCreator(Creator, MaxCreatorBase): instance_node = rt.getNodeByName( instance.data.get("instance_node")) if instance_node: - rt.delete(rt.getNodeByName(instance_node)) + rt.select(instance_node) + rt.execute(f'for o in selection do for c in o.children do c.parent = undefined') # noqa + rt.delete(instance_node) self._remove_instance_from_context(instance) diff --git a/openpype/hosts/max/plugins/create/create_maxScene.py b/openpype/hosts/max/plugins/create/create_maxScene.py new file mode 100644 index 0000000000..7900336f32 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_maxScene.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating raw max scene.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance + + +class CreateMaxScene(plugin.MaxCreator): + identifier = "io.openpype.creators.max.maxScene" + label = "Max Scene" + family = "maxScene" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + sel_obj = list(rt.selection) + instance = super(CreateMaxScene, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + container = rt.getNodeByName(instance.data.get("instance_node")) + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + for obj in sel_obj: + obj.parent = container + # for additional work on the node: + # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcloud.py b/openpype/hosts/max/plugins/create/create_pointcloud.py new file mode 100644 index 0000000000..c83acac3df --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_pointcloud.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating point cloud.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance + + +class CreatePointCloud(plugin.MaxCreator): + identifier = "io.openpype.creators.max.pointcloud" + label = "Point Cloud" + family = "pointcloud" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + from pymxs import runtime as rt + sel_obj = list(rt.selection) + instance = super(CreatePointCloud, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + container = rt.getNodeByName(instance.data.get("instance_node")) + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + for obj in sel_obj: + obj.parent = container + # for additional work on the node: + # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/load/load_max_scene.py b/openpype/hosts/max/plugins/load/load_max_scene.py index b863b9363f..460f4822a6 100644 --- a/openpype/hosts/max/plugins/load/load_max_scene.py +++ b/openpype/hosts/max/plugins/load/load_max_scene.py @@ -9,7 +9,8 @@ from openpype.hosts.max.api import lib class MaxSceneLoader(load.LoaderPlugin): """Max Scene Loader""" - families = ["camera"] + families = ["camera", + "maxScene"] representations = ["max"] order = -8 icon = "code-fork" @@ -46,8 +47,7 @@ class MaxSceneLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - - max_objects = self.get_container_children(node) + max_objects = node.Children for max_object in max_objects: max_object.source = path diff --git a/openpype/hosts/max/plugins/load/load_pointcloud.py b/openpype/hosts/max/plugins/load/load_pointcloud.py new file mode 100644 index 0000000000..27bc88b4f3 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_pointcloud.py @@ -0,0 +1,51 @@ +import os +from openpype.pipeline import ( + load, get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class PointCloudLoader(load.LoaderPlugin): + """Point Cloud Loader""" + + families = ["pointcloud"] + representations = ["prt"] + order = -8 + icon = "code-fork" + color = "green" + + def load(self, context, name=None, namespace=None, data=None): + """load point cloud by tyCache""" + from pymxs import runtime as rt + + filepath = os.path.normpath(self.fname) + obj = rt.tyCache() + obj.filename = filepath + + prt_container = rt.getNodeByName(f"{obj.name}") + + return containerise( + name, [prt_container], context, loader=self.__class__.__name__) + + def update(self, container, representation): + """update the container""" + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + + prt_objects = self.get_container_children(node) + for prt_object in prt_objects: + prt_object.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def remove(self, container): + """remove the container""" + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 7c9e311c2f..63e4108c84 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -61,7 +61,7 @@ class CollectRender(pyblish.api.InstancePlugin): "plugin": "3dsmax", "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], - "version": version_int + "version": version_int, } self.log.info("data: {0}".format(data)) instance.data.update(data) diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index cacc84c591..969f87be48 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -20,7 +20,8 @@ class ExtractMaxSceneRaw(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.2 label = "Extract Max Scene (Raw)" hosts = ["max"] - families = ["camera"] + families = ["camera", + "maxScene"] optional = True def process(self, instance): diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py new file mode 100644 index 0000000000..e8d58ab713 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -0,0 +1,207 @@ +import os +import pyblish.api +from openpype.pipeline import publish +from pymxs import runtime as rt +from openpype.hosts.max.api import ( + maintained_selection +) +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io + + +def get_setting(project_setting=None): + project_setting = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + return (project_setting["max"]["PointCloud"]) + + +class ExtractPointCloud(publish.Extractor): + """ + Extract PRT format with tyFlow operators + + Notes: + Currently only works for the default partition setting + + Args: + export_particle(): sets up all job arguments for attributes + to be exported in MAXscript + + get_operators(): get the export_particle operator + + get_custom_attr(): get all custom channel attributes from Openpype + setting and sets it as job arguments before exporting + + get_files(): get the files with tyFlow naming convention + before publishing + + partition_output_name(): get the naming with partition settings. + get_partition(): get partition value + + """ + + order = pyblish.api.ExtractorOrder - 0.2 + label = "Extract Point Cloud" + hosts = ["max"] + families = ["pointcloud"] + + def process(self, instance): + start = int(instance.context.data.get("frameStart")) + end = int(instance.context.data.get("frameEnd")) + container = instance.data["instance_node"] + self.log.info("Extracting PRT...") + + stagingdir = self.staging_dir(instance) + filename = "{name}.prt".format(**instance.data) + path = os.path.join(stagingdir, filename) + + with maintained_selection(): + job_args = self.export_particle(container, + start, + end, + path) + for job in job_args: + rt.execute(job) + + self.log.info("Performing Extraction ...") + if "representations" not in instance.data: + instance.data["representations"] = [] + + self.log.info("Writing PRT with TyFlow Plugin...") + filenames = self.get_files(container, path, start, end) + self.log.debug("filenames: {0}".format(filenames)) + + partition = self.partition_output_name(container) + + representation = { + 'name': 'prt', + 'ext': 'prt', + 'files': filenames if len(filenames) > 1 else filenames[0], + "stagingDir": stagingdir, + "outputName": partition # partition value + } + instance.data["representations"].append(representation) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + path)) + + def export_particle(self, + container, + start, + end, + filepath): + job_args = [] + opt_list = self.get_operators(container) + for operator in opt_list: + start_frame = "{0}.frameStart={1}".format(operator, + start) + job_args.append(start_frame) + end_frame = "{0}.frameEnd={1}".format(operator, + end) + job_args.append(end_frame) + filepath = filepath.replace("\\", "/") + prt_filename = '{0}.PRTFilename="{1}"'.format(operator, + filepath) + + job_args.append(prt_filename) + # Partition + mode = "{0}.PRTPartitionsMode=2".format(operator) + job_args.append(mode) + + additional_args = self.get_custom_attr(operator) + for args in additional_args: + job_args.append(args) + + prt_export = "{0}.exportPRT()".format(operator) + job_args.append(prt_export) + + return job_args + + def get_operators(self, container): + """Get Export Particles Operator""" + + opt_list = [] + node = rt.getNodebyName(container) + selection_list = list(node.Children) + for sel in selection_list: + obj = sel.baseobject + # TODO: to see if it can be used maxscript instead + anim_names = rt.getsubanimnames(obj) + for anim_name in anim_names: + sub_anim = rt.getsubanim(obj, anim_name) + boolean = rt.isProperty(sub_anim, "Export_Particles") + event_name = sub_anim.name + if boolean: + opt = "${0}.{1}.export_particles".format(sel.name, + event_name) + opt_list.append(opt) + + return opt_list + + def get_custom_attr(self, operator): + """Get Custom Attributes""" + + custom_attr_list = [] + attr_settings = get_setting()["attribute"] + for key, value in attr_settings.items(): + custom_attr = "{0}.PRTChannels_{1}=True".format(operator, + value) + self.log.debug( + "{0} will be added as custom attribute".format(key) + ) + custom_attr_list.append(custom_attr) + + return custom_attr_list + + def get_files(self, + container, + path, + start_frame, + end_frame): + """ + Note: + Set the filenames accordingly to the tyFlow file + naming extension for the publishing purpose + + Actual File Output from tyFlow: + __partof..prt + e.g. tyFlow_cloth_CCCS_blobbyFill_001__part1of1_00004.prt + """ + filenames = [] + filename = os.path.basename(path) + orig_name, ext = os.path.splitext(filename) + partition_count, partition_start = self.get_partition(container) + for frame in range(int(start_frame), int(end_frame) + 1): + actual_name = "{}__part{:03}of{}_{:05}".format(orig_name, + partition_start, + partition_count, + frame) + actual_filename = path.replace(orig_name, actual_name) + filenames.append(os.path.basename(actual_filename)) + + return filenames + + def partition_output_name(self, container): + """ + Notes: + Partition output name set for mapping + the published file output + + todo: + Customizes the setting for the output + """ + partition_count, partition_start = self.get_partition(container) + partition = "_part{:03}of{}".format(partition_start, + partition_count) + + return partition + + def get_partition(self, container): + """ + Get Partition Value + """ + opt_list = self.get_operators(container) + for operator in opt_list: + count = rt.execute(f'{operator}.PRTPartitionsCount') + start = rt.execute(f'{operator}.PRTPartitionsFrom') + + return count, start diff --git a/openpype/hosts/max/plugins/publish/increment_workfile_version.py b/openpype/hosts/max/plugins/publish/increment_workfile_version.py new file mode 100644 index 0000000000..3dec214f77 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/increment_workfile_version.py @@ -0,0 +1,19 @@ +import pyblish.api +from openpype.lib import version_up +from pymxs import runtime as rt + + +class IncrementWorkfileVersion(pyblish.api.ContextPlugin): + """Increment current workfile version.""" + + order = pyblish.api.IntegratorOrder + 0.9 + label = "Increment Workfile Version" + hosts = ["max"] + families = ["workfile"] + + def process(self, context): + path = context.data["currentFile"] + filepath = version_up(path) + + rt.saveMaxFile(filepath) + self.log.info("Incrementing file version") diff --git a/openpype/hosts/max/plugins/publish/validate_no_max_content.py b/openpype/hosts/max/plugins/publish/validate_no_max_content.py new file mode 100644 index 0000000000..c20a1968ed --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_no_max_content.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from openpype.pipeline import PublishValidationError +from pymxs import runtime as rt + + +class ValidateMaxContents(pyblish.api.InstancePlugin): + """Validates Max contents. + + Check if MaxScene container includes any contents underneath. + """ + + order = pyblish.api.ValidatorOrder + families = ["camera", + "maxScene", + "maxrender"] + hosts = ["max"] + label = "Max Scene Contents" + + def process(self, instance): + container = rt.getNodeByName(instance.data["instance_node"]) + if not list(container.Children): + raise PublishValidationError("No content found in the container") diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py new file mode 100644 index 0000000000..f654058648 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -0,0 +1,191 @@ +import pyblish.api +from openpype.pipeline import PublishValidationError +from pymxs import runtime as rt +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io + + +def get_setting(project_setting=None): + project_setting = get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) + return (project_setting["max"]["PointCloud"]) + + +class ValidatePointCloud(pyblish.api.InstancePlugin): + """Validate that workfile was saved.""" + + order = pyblish.api.ValidatorOrder + families = ["pointcloud"] + hosts = ["max"] + label = "Validate Point Cloud" + + def process(self, instance): + """ + Notes: + + 1. Validate the container only include tyFlow objects + 2. Validate if tyFlow operator Export Particle exists + 3. Validate if the export mode of Export Particle is at PRT format + 4. Validate the partition count and range set as default value + Partition Count : 100 + Partition Range : 1 to 1 + 5. Validate if the custom attribute(s) exist as parameter(s) + of export_particle operator + + """ + invalid = self.get_tyFlow_object(instance) + if invalid: + raise PublishValidationError("Non tyFlow object " + "found: {}".format(invalid)) + invalid = self.get_tyFlow_operator(instance) + if invalid: + raise PublishValidationError("tyFlow ExportParticle operator " + "not found: {}".format(invalid)) + + invalid = self.validate_export_mode(instance) + if invalid: + raise PublishValidationError("The export mode is not at PRT") + + invalid = self.validate_partition_value(instance) + if invalid: + raise PublishValidationError("tyFlow Partition setting is " + "not at the default value") + invalid = self.validate_custom_attribute(instance) + if invalid: + raise PublishValidationError("Custom Attribute not found " + ":{}".format(invalid)) + + def get_tyFlow_object(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info("Validating tyFlow container " + "for {}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + for sel in selection_list: + sel_tmp = str(sel) + if rt.classOf(sel) in [rt.tyFlow, + rt.Editable_Mesh]: + if "tyFlow" not in sel_tmp: + invalid.append(sel) + else: + invalid.append(sel) + + return invalid + + def get_tyFlow_operator(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info("Validating tyFlow object " + "for {}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + bool_list = [] + for sel in selection_list: + obj = sel.baseobject + anim_names = rt.getsubanimnames(obj) + for anim_name in anim_names: + # get all the names of the related tyFlow nodes + sub_anim = rt.getsubanim(obj, anim_name) + # check if there is export particle operator + boolean = rt.isProperty(sub_anim, "Export_Particles") + bool_list.append(str(boolean)) + # if the export_particles property is not there + # it means there is not a "Export Particle" operator + if "True" not in bool_list: + self.log.error("Operator 'Export Particles' not found!") + invalid.append(sel) + + return invalid + + def validate_custom_attribute(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info("Validating tyFlow custom " + "attributes for {}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + for sel in selection_list: + obj = sel.baseobject + anim_names = rt.getsubanimnames(obj) + for anim_name in anim_names: + # get all the names of the related tyFlow nodes + sub_anim = rt.getsubanim(obj, anim_name) + # check if there is export particle operator + boolean = rt.isProperty(sub_anim, "Export_Particles") + event_name = sub_anim.name + if boolean: + opt = "${0}.{1}.export_particles".format(sel.name, + event_name) + attributes = get_setting()["attribute"] + for key, value in attributes.items(): + custom_attr = "{0}.PRTChannels_{1}".format(opt, + value) + try: + rt.execute(custom_attr) + except RuntimeError: + invalid.add(key) + + return invalid + + def validate_partition_value(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info("Validating tyFlow partition " + "value for {}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + for sel in selection_list: + obj = sel.baseobject + anim_names = rt.getsubanimnames(obj) + for anim_name in anim_names: + # get all the names of the related tyFlow nodes + sub_anim = rt.getsubanim(obj, anim_name) + # check if there is export particle operator + boolean = rt.isProperty(sub_anim, "Export_Particles") + event_name = sub_anim.name + if boolean: + opt = "${0}.{1}.export_particles".format(sel.name, + event_name) + count = rt.execute(f'{opt}.PRTPartitionsCount') + if count != 100: + invalid.append(count) + start = rt.execute(f'{opt}.PRTPartitionsFrom') + if start != 1: + invalid.append(start) + end = rt.execute(f'{opt}.PRTPartitionsTo') + if end != 1: + invalid.append(end) + + return invalid + + def validate_export_mode(self, instance): + invalid = [] + container = instance.data["instance_node"] + self.log.info("Validating tyFlow export " + "mode for {}".format(container)) + + con = rt.getNodeByName(container) + selection_list = list(con.Children) + for sel in selection_list: + obj = sel.baseobject + anim_names = rt.getsubanimnames(obj) + for anim_name in anim_names: + # get all the names of the related tyFlow nodes + sub_anim = rt.getsubanim(obj, anim_name) + # check if there is export particle operator + boolean = rt.isProperty(sub_anim, "Export_Particles") + event_name = sub_anim.name + if boolean: + opt = "${0}.{1}.export_particles".format(sel.name, + event_name) + export_mode = rt.execute(f'{opt}.exportMode') + if export_mode != 1: + invalid.append(export_mode) + + return invalid diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index f66858dfb6..f4c4d6ed88 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -11,6 +11,7 @@ import maya.mel as mel from openpype import resources from openpype.tools.utils import host_tools from .lib import get_main_window +from ..tools import show_look_assigner log = logging.getLogger(__name__) @@ -112,7 +113,7 @@ def override_toolbox_ui(): annotation="Look Manager", label="Look Manager", image=os.path.join(icons, "lookmanager.png"), - command=host_tools.show_look_assigner, + command=show_look_assigner, width=icon_size, height=icon_size, parent=parent diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 954576f02e..aa1e501578 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2099,29 +2099,40 @@ def get_frame_range(): } -def reset_frame_range(): - """Set frame range to current asset""" +def reset_frame_range(playback=True, render=True, fps=True): + """Set frame range to current asset - fps = convert_to_maya_fps( - float(legacy_io.Session.get("AVALON_FPS", 25)) - ) - set_scene_fps(fps) + Args: + playback (bool, Optional): Whether to set the maya timeline playback + frame range. Defaults to True. + render (bool, Optional): Whether to set the maya render frame range. + Defaults to True. + fps (bool, Optional): Whether to set scene FPS. Defaults to True. + """ + + if fps: + fps = convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + set_scene_fps(fps) frame_range = get_frame_range() frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) - cmds.playbackOptions(animationStartTime=frame_start) - cmds.playbackOptions(animationEndTime=frame_end) - cmds.playbackOptions(minTime=frame_start) - cmds.playbackOptions(maxTime=frame_end) - cmds.currentTime(frame_start) + if playback: + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.playbackOptions(animationStartTime=frame_start) + cmds.playbackOptions(animationEndTime=frame_end) + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.currentTime(frame_start) - cmds.setAttr("defaultRenderGlobals.startFrame", frame_start) - cmds.setAttr("defaultRenderGlobals.endFrame", frame_end) + if render: + cmds.setAttr("defaultRenderGlobals.startFrame", frame_start) + cmds.setAttr("defaultRenderGlobals.endFrame", frame_end) def reset_scene_resolution(): @@ -3576,6 +3587,65 @@ def get_color_management_output_transform(): return colorspace +def image_info(file_path): + # type: (str) -> dict + """Based on tha texture path, get its bit depth and format information. + Take reference from makeTx.py in Arnold: + ImageInfo(filename): Get Image Information for colorspace + AiTextureGetFormat(filename): Get Texture Format + AiTextureGetBitDepth(filename): Get Texture bit depth + Args: + file_path (str): Path to the texture file. + Returns: + dict: Dictionary with the information about the texture file. + """ + from arnold import ( + AiTextureGetBitDepth, + AiTextureGetFormat + ) + # Get Texture Information + img_info = {'filename': file_path} + if os.path.isfile(file_path): + img_info['bit_depth'] = AiTextureGetBitDepth(file_path) # noqa + img_info['format'] = AiTextureGetFormat(file_path) # noqa + else: + img_info['bit_depth'] = 8 + img_info['format'] = "unknown" + return img_info + + +def guess_colorspace(img_info): + # type: (dict) -> str + """Guess the colorspace of the input image filename. + Note: + Reference from makeTx.py + Args: + img_info (dict): Image info generated by :func:`image_info` + Returns: + str: color space name use in the `--colorconvert` + option of maketx. + """ + from arnold import ( + AiTextureInvalidate, + # types + AI_TYPE_BYTE, + AI_TYPE_INT, + AI_TYPE_UINT + ) + try: + if img_info['bit_depth'] <= 16: + if img_info['format'] in (AI_TYPE_BYTE, AI_TYPE_INT, AI_TYPE_UINT): # noqa + return 'sRGB' + else: + return 'linear' + # now discard the image file as AiTextureGetFormat has loaded it + AiTextureInvalidate(img_info['filename']) # noqa + except ValueError: + print(("[maketx] Error: Could not guess" + "colorspace for {}").format(img_info["filename"])) + return "linear" + + def len_flattened(components): """Return the length of the list as if it was flattened. diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index f19deb0351..eaa728a2f6 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -158,7 +158,7 @@ class RenderSettings(object): cmds.setAttr( "defaultArnoldDriver.mergeAOVs", multi_exr) self._additional_attribs_setter(additional_options) - reset_frame_range() + reset_frame_range(playback=False, fps=False, render=True) def _set_redshift_settings(self, width, height): """Sets settings for Redshift.""" @@ -336,7 +336,8 @@ class RenderSettings(object): ) # Set render file format to exr - cmds.setAttr("{}.imageFormatStr".format(node), "exr", type="string") + ext = vray_render_presets["image_format"] + cmds.setAttr("{}.imageFormatStr".format(node), ext, type="string") # animType cmds.setAttr("{}.animType".format(node), 1) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 0f48a133a6..5284c0249d 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -12,6 +12,7 @@ from openpype.pipeline.workfile import BuildWorkfile from openpype.tools.utils import host_tools from openpype.hosts.maya.api import lib, lib_rendersettings from .lib import get_main_window, IS_HEADLESS +from ..tools import show_look_assigner from .workfile_template_builder import ( create_placeholder, @@ -111,12 +112,12 @@ def install(): ) cmds.menuItem( - "Reset Frame Range", + "Set Frame Range", command=lambda *args: lib.reset_frame_range() ) cmds.menuItem( - "Reset Resolution", + "Set Resolution", command=lambda *args: lib.reset_scene_resolution() ) @@ -139,7 +140,7 @@ def install(): cmds.menuItem( "Look assigner...", - command=lambda *args: host_tools.show_look_assigner( + command=lambda *args: show_look_assigner( parent_widget ) ) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index a4b6e86598..f992ff2c1a 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -13,6 +13,7 @@ class CreateAnimation(plugin.Creator): icon = "male" write_color_sets = False write_face_sets = False + include_parent_hierarchy = False include_user_defined_attributes = False def __init__(self, *args, **kwargs): @@ -37,7 +38,7 @@ class CreateAnimation(plugin.Creator): self.data["visibleOnly"] = False # Include the groups above the out_SET content - self.data["includeParentHierarchy"] = False # Include parent groups + self.data["includeParentHierarchy"] = self.include_parent_hierarchy # Default to exporting world-space self.data["worldSpace"] = True diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index f1b626c06b..e709239ae7 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -26,6 +26,7 @@ class CreateReview(plugin.Creator): "alpha cut" ] useMayaTimeline = True + panZoom = False def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -45,5 +46,6 @@ class CreateReview(plugin.Creator): data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane data["transparency"] = self.transparency + data["panZoom"] = self.panZoom self.data = data diff --git a/openpype/hosts/maya/plugins/inventory/connect_geometry.py b/openpype/hosts/maya/plugins/inventory/connect_geometry.py index a12487cf7e..03154b7afe 100644 --- a/openpype/hosts/maya/plugins/inventory/connect_geometry.py +++ b/openpype/hosts/maya/plugins/inventory/connect_geometry.py @@ -134,7 +134,7 @@ class ConnectGeometry(InventoryAction): bool """ - from Qt import QtWidgets + from qtpy import QtWidgets accept = QtWidgets.QMessageBox.Ok if show_cancel: diff --git a/openpype/hosts/maya/plugins/inventory/connect_xgen.py b/openpype/hosts/maya/plugins/inventory/connect_xgen.py index 933a1b4025..177971f176 100644 --- a/openpype/hosts/maya/plugins/inventory/connect_xgen.py +++ b/openpype/hosts/maya/plugins/inventory/connect_xgen.py @@ -149,7 +149,7 @@ class ConnectXgen(InventoryAction): bool """ - from Qt import QtWidgets + from qtpy import QtWidgets accept = QtWidgets.QMessageBox.Ok if show_cancel: diff --git a/openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py b/openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py new file mode 100644 index 0000000000..924a1a4627 --- /dev/null +++ b/openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py @@ -0,0 +1,178 @@ +import os +import json +from collections import defaultdict + +from maya import cmds + +from openpype.pipeline import ( + InventoryAction, get_representation_context, get_representation_path +) +from openpype.hosts.maya.api.lib import get_container_members, get_id + + +class ConnectYetiRig(InventoryAction): + """Connect Yeti Rig with an animation or pointcache.""" + + label = "Connect Yeti Rig" + icon = "link" + color = "white" + + def process(self, containers): + # Validate selection is more than 1. + message = ( + "Only 1 container selected. 2+ containers needed for this action." + ) + if len(containers) == 1: + self.display_warning(message) + return + + # Categorize containers by family. + containers_by_family = defaultdict(list) + for container in containers: + family = get_representation_context( + container["representation"] + )["subset"]["data"]["family"] + containers_by_family[family].append(container) + + # Validate to only 1 source container. + source_containers = containers_by_family.get("animation", []) + source_containers += containers_by_family.get("pointcache", []) + source_container_namespaces = [ + x["namespace"] for x in source_containers + ] + message = ( + "{} animation containers selected:\n\n{}\n\nOnly select 1 of type " + "\"animation\" or \"pointcache\".".format( + len(source_containers), source_container_namespaces + ) + ) + if len(source_containers) != 1: + self.display_warning(message) + return + + source_container = source_containers[0] + source_ids = self.nodes_by_id(source_container) + + # Target containers. + target_ids = {} + inputs = [] + + yeti_rig_containers = containers_by_family.get("yetiRig") + if not yeti_rig_containers: + self.display_warning( + "Select at least one yetiRig container" + ) + return + + for container in yeti_rig_containers: + target_ids.update(self.nodes_by_id(container)) + + maya_file = get_representation_path( + get_representation_context( + container["representation"] + )["representation"] + ) + _, ext = os.path.splitext(maya_file) + settings_file = maya_file.replace(ext, ".rigsettings") + if not os.path.exists(settings_file): + continue + + with open(settings_file) as f: + inputs.extend(json.load(f)["inputs"]) + + # Compare loaded connections to scene. + for input in inputs: + source_node = source_ids.get(input["sourceID"]) + target_node = target_ids.get(input["destinationID"]) + + if not source_node or not target_node: + self.log.debug( + "Could not find nodes for input:\n" + + json.dumps(input, indent=4, sort_keys=True) + ) + continue + source_attr, target_attr = input["connections"] + + if not cmds.attributeQuery( + source_attr, node=source_node, exists=True + ): + self.log.debug( + "Could not find attribute {} on node {} for " + "input:\n{}".format( + source_attr, + source_node, + json.dumps(input, indent=4, sort_keys=True) + ) + ) + continue + + if not cmds.attributeQuery( + target_attr, node=target_node, exists=True + ): + self.log.debug( + "Could not find attribute {} on node {} for " + "input:\n{}".format( + target_attr, + target_node, + json.dumps(input, indent=4, sort_keys=True) + ) + ) + continue + + source_plug = "{}.{}".format( + source_node, source_attr + ) + target_plug = "{}.{}".format( + target_node, target_attr + ) + if cmds.isConnected( + source_plug, target_plug, ignoreUnitConversion=True + ): + self.log.debug( + "Connection already exists: {} -> {}".format( + source_plug, target_plug + ) + ) + continue + + cmds.connectAttr(source_plug, target_plug, force=True) + self.log.debug( + "Connected attributes: {} -> {}".format( + source_plug, target_plug + ) + ) + + def nodes_by_id(self, container): + ids = {} + for member in get_container_members(container): + id = get_id(member) + if not id: + continue + ids[id] = member + + return ids + + def display_warning(self, message, show_cancel=False): + """Show feedback to user. + + Returns: + bool + """ + + from qtpy import QtWidgets + + accept = QtWidgets.QMessageBox.Ok + if show_cancel: + buttons = accept | QtWidgets.QMessageBox.Cancel + else: + buttons = accept + + state = QtWidgets.QMessageBox.warning( + None, + "", + message, + buttons=buttons, + defaultButton=accept + ) + + return state == accept diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index ab69d62ef5..11a2bd1966 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -2,7 +2,6 @@ import os import clique import maya.cmds as cmds -import mtoa.ui.arnoldmenu from openpype.settings import get_project_settings from openpype.pipeline import ( @@ -36,6 +35,11 @@ class ArnoldStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): + + # Make sure to load arnold before importing `mtoa.ui.arnoldmenu` + cmds.loadPlugin("mtoa", quiet=True) + import mtoa.ui.arnoldmenu + version = context['version'] version_data = version.get("data", {}) diff --git a/openpype/hosts/maya/plugins/load/load_image.py b/openpype/hosts/maya/plugins/load/load_image.py new file mode 100644 index 0000000000..b464c268fc --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_image.py @@ -0,0 +1,332 @@ +import os +import copy + +from openpype.lib import EnumDef +from openpype.pipeline import ( + load, + get_representation_context +) +from openpype.pipeline.load.utils import get_representation_path_from_context +from openpype.pipeline.colorspace import ( + get_imageio_colorspace_from_filepath, + get_imageio_config, + get_imageio_file_rules +) +from openpype.settings import get_project_settings + +from openpype.hosts.maya.api.pipeline import containerise +from openpype.hosts.maya.api.lib import ( + unique_namespace, + namespaced +) + +from maya import cmds + + +def create_texture(): + """Create place2dTexture with file node with uv connections + + Mimics Maya "file [Texture]" creation. + """ + + place = cmds.shadingNode("place2dTexture", asUtility=True, name="place2d") + file = cmds.shadingNode("file", asTexture=True, name="file") + + connections = ["coverage", "translateFrame", "rotateFrame", "rotateUV", + "mirrorU", "mirrorV", "stagger", "wrapV", "wrapU", + "repeatUV", "offset", "noiseUV", "vertexUvThree", + "vertexUvTwo", "vertexUvOne", "vertexCameraOne"] + for attr in connections: + src = "{}.{}".format(place, attr) + dest = "{}.{}".format(file, attr) + cmds.connectAttr(src, dest) + + cmds.connectAttr(place + '.outUV', file + '.uvCoord') + cmds.connectAttr(place + '.outUvFilterSize', file + '.uvFilterSize') + + return file, place + + +def create_projection(): + """Create texture with place3dTexture and projection + + Mimics Maya "file [Projection]" creation. + """ + + file, place = create_texture() + projection = cmds.shadingNode("projection", asTexture=True, + name="projection") + place3d = cmds.shadingNode("place3dTexture", asUtility=True, + name="place3d") + + cmds.connectAttr(place3d + '.worldInverseMatrix[0]', + projection + ".placementMatrix") + cmds.connectAttr(file + '.outColor', projection + ".image") + + return file, place, projection, place3d + + +def create_stencil(): + """Create texture with extra place2dTexture offset and stencil + + Mimics Maya "file [Stencil]" creation. + """ + + file, place = create_texture() + + place_stencil = cmds.shadingNode("place2dTexture", asUtility=True, + name="place2d_stencil") + stencil = cmds.shadingNode("stencil", asTexture=True, name="stencil") + + for src_attr, dest_attr in [ + ("outUV", "uvCoord"), + ("outUvFilterSize", "uvFilterSize") + ]: + src_plug = "{}.{}".format(place_stencil, src_attr) + cmds.connectAttr(src_plug, "{}.{}".format(place, dest_attr)) + cmds.connectAttr(src_plug, "{}.{}".format(stencil, dest_attr)) + + return file, place, stencil, place_stencil + + +class FileNodeLoader(load.LoaderPlugin): + """File node loader.""" + + families = ["image", "plate", "render"] + label = "Load file node" + representations = ["exr", "tif", "png", "jpg"] + icon = "image" + color = "orange" + order = 2 + + options = [ + EnumDef( + "mode", + items={ + "texture": "Texture", + "projection": "Projection", + "stencil": "Stencil" + }, + default="texture", + label="Texture Mode" + ) + ] + + def load(self, context, name, namespace, data): + + asset = context['asset']['name'] + namespace = namespace or unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + with namespaced(namespace, new=True) as namespace: + # Create the nodes within the namespace + nodes = { + "texture": create_texture, + "projection": create_projection, + "stencil": create_stencil + }[data.get("mode", "texture")]() + + file_node = cmds.ls(nodes, type="file")[0] + + self._apply_representation_context(context, file_node) + + # For ease of access for the user select all the nodes and select + # the file node last so that UI shows its attributes by default + cmds.select(list(nodes) + [file_node], replace=True) + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__ + ) + + def update(self, container, representation): + + members = cmds.sets(container['objectName'], query=True) + file_node = cmds.ls(members, type="file")[0] + + context = get_representation_context(representation) + self._apply_representation_context(context, file_node) + + # Update representation + cmds.setAttr( + container["objectName"] + ".representation", + str(representation["_id"]), + type="string" + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass + + def _apply_representation_context(self, context, file_node): + """Update the file node to match the context. + + This sets the file node's attributes for: + - file path + - udim tiling mode (if it is an udim tile) + - use frame extension (if it is a sequence) + - colorspace + + """ + + repre_context = context["representation"]["context"] + has_frames = repre_context.get("frame") is not None + has_udim = repre_context.get("udim") is not None + + # Set UV tiling mode if UDIM tiles + if has_udim: + cmds.setAttr(file_node + ".uvTilingMode", 3) # UDIM-tiles + else: + cmds.setAttr(file_node + ".uvTilingMode", 0) # off + + # Enable sequence if publish has `startFrame` and `endFrame` and + # `startFrame != endFrame` + if has_frames and self._is_sequence(context): + # When enabling useFrameExtension maya automatically + # connects an expression to .frameExtension to set + # the current frame. However, this expression is generated + # with some delay and thus it'll show a warning if frame 0 + # doesn't exist because we're explicitly setting the + # token. + cmds.setAttr(file_node + ".useFrameExtension", True) + else: + cmds.setAttr(file_node + ".useFrameExtension", False) + + # Set the file node path attribute + path = self._format_path(context) + cmds.setAttr(file_node + ".fileTextureName", path, type="string") + + # Set colorspace + colorspace = self._get_colorspace(context) + if colorspace: + cmds.setAttr(file_node + ".colorSpace", colorspace, type="string") + else: + self.log.debug("Unknown colorspace - setting colorspace skipped.") + + def _is_sequence(self, context): + """Check whether frameStart and frameEnd are not the same.""" + version = context.get("version", {}) + representation = context.get("representation", {}) + + for doc in [representation, version]: + # Frame range can be set on version or representation. + # When set on representation it overrides version data. + data = doc.get("data", {}) + start = data.get("frameStartHandle", data.get("frameStart", None)) + end = data.get("frameEndHandle", data.get("frameEnd", None)) + + if start is None or end is None: + continue + + if start != end: + return True + else: + return False + + return False + + def _get_colorspace(self, context): + """Return colorspace of the file to load. + + Retrieves the explicit colorspace from the publish. If no colorspace + data is stored with published content then project imageio settings + are used to make an assumption of the colorspace based on the file + rules. If no file rules match then None is returned. + + Returns: + str or None: The colorspace of the file or None if not detected. + + """ + + # We can't apply color spaces if management is not enabled + if not cmds.colorManagementPrefs(query=True, cmEnabled=True): + return + + representation = context["representation"] + colorspace_data = representation.get("data", {}).get("colorspaceData") + if colorspace_data: + return colorspace_data["colorspace"] + + # Assume colorspace from filepath based on project settings + project_name = context["project"]["name"] + host_name = os.environ.get("AVALON_APP") + project_settings = get_project_settings(project_name) + + config_data = get_imageio_config( + project_name, host_name, + project_settings=project_settings + ) + file_rules = get_imageio_file_rules( + project_name, host_name, + project_settings=project_settings + ) + + path = get_representation_path_from_context(context) + colorspace = get_imageio_colorspace_from_filepath( + path=path, + host_name=host_name, + project_name=project_name, + config_data=config_data, + file_rules=file_rules, + project_settings=project_settings + ) + + return colorspace + + def _format_path(self, context): + """Format the path with correct tokens for frames and udim tiles.""" + + context = copy.deepcopy(context) + representation = context["representation"] + template = representation.get("data", {}).get("template") + if not template: + # No template to find token locations for + return get_representation_path_from_context(context) + + def _placeholder(key): + # Substitute with a long placeholder value so that potential + # custom formatting with padding doesn't find its way into + # our formatting, so that wouldn't be padded as 0 + return "___{}___".format(key) + + # We format UDIM and Frame numbers with their specific tokens. To do so + # we in-place change the representation context data to format the path + # with our own data + tokens = { + "frame": "", + "udim": "" + } + has_tokens = False + repre_context = representation["context"] + for key, _token in tokens.items(): + if key in repre_context: + repre_context[key] = _placeholder(key) + has_tokens = True + + # Replace with our custom template that has the tokens set + representation["data"]["template"] = template + path = get_representation_path_from_context(context) + + if has_tokens: + for key, token in tokens.items(): + if key in repre_context: + path = path.replace(_placeholder(key), token) + + return path diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 1600cd49bd..7e6cabc77c 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -3,7 +3,7 @@ import os import maya.cmds as cmds import xgenm -from Qt import QtWidgets +from qtpy import QtWidgets import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.lib import ( diff --git a/openpype/hosts/maya/plugins/load/load_yeti_rig.py b/openpype/hosts/maya/plugins/load/load_yeti_rig.py index 651607de8a..6a13d2e145 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_rig.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_rig.py @@ -1,17 +1,12 @@ -import os -from collections import defaultdict +import maya.cmds as cmds -from openpype.settings import get_project_settings +from openpype.settings import get_current_project_settings import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api import lib class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """ - This loader will load Yeti rig. You can select something in scene and if it - has same ID as mesh published with rig, their shapes will be linked - together. - """ + """This loader will load Yeti rig.""" families = ["yetiRig"] representations = ["ma"] @@ -22,72 +17,31 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): color = "orange" def process_reference( - self, context, name=None, namespace=None, options=None): + self, context, name=None, namespace=None, options=None + ): - import maya.cmds as cmds - - # get roots of selected hierarchies - selected_roots = [] - for sel in cmds.ls(sl=True, long=True): - selected_roots.append(sel.split("|")[1]) - - # get all objects under those roots - selected_hierarchy = [] - for root in selected_roots: - selected_hierarchy.append(cmds.listRelatives( - root, - allDescendents=True) or []) - - # flatten the list and filter only shapes - shapes_flat = [] - for root in selected_hierarchy: - shapes = cmds.ls(root, long=True, type="mesh") or [] - for shape in shapes: - shapes_flat.append(shape) - - # create dictionary of cbId and shape nodes - scene_lookup = defaultdict(list) - for node in shapes_flat: - cb_id = lib.get_id(node) - scene_lookup[cb_id] = node - - # load rig + group_name = "{}:{}".format(namespace, name) with lib.maintained_selection(): - file_url = self.prepare_root_value(self.fname, - context["project"]["name"]) - nodes = cmds.file(file_url, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName="{}:{}".format(namespace, name)) + file_url = self.prepare_root_value( + self.fname, context["project"]["name"] + ) + nodes = cmds.file( + file_url, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=group_name + ) - # for every shape node we've just loaded find matching shape by its - # cbId in selection. If found outMesh of scene shape will connect to - # inMesh of loaded shape. - for destination_node in nodes: - source_node = scene_lookup[lib.get_id(destination_node)] - if source_node: - self.log.info("found: {}".format(source_node)) - self.log.info( - "creating connection to {}".format(destination_node)) - - cmds.connectAttr("{}.outMesh".format(source_node), - "{}.inMesh".format(destination_node), - force=True) - - groupName = "{}:{}".format(namespace, name) - - settings = get_project_settings(os.environ['AVALON_PROJECT']) - colors = settings['maya']['load']['colors'] - - c = colors.get('yetiRig') + settings = get_current_project_settings() + colors = settings["maya"]["load"]["colors"] + c = colors.get("yetiRig") if c is not None: - cmds.setAttr(groupName + ".useOutlinerColor", 1) - cmds.setAttr(groupName + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + cmds.setAttr(group_name + ".useOutlinerColor", 1) + cmds.setAttr( + group_name + ".outlinerColor", + (float(c[0]) / 255), (float(c[1]) / 255), (float(c[2]) / 255) ) self[:] = nodes diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index eb872c2935..00565c5819 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -5,6 +5,7 @@ import pyblish.api from openpype.client import get_subset_by_name from openpype.pipeline import legacy_io +from openpype.hosts.maya.api.lib import get_attribute_input class CollectReview(pyblish.api.InstancePlugin): @@ -23,6 +24,11 @@ class CollectReview(pyblish.api.InstancePlugin): task = legacy_io.Session["AVALON_TASK"] + # Get panel. + instance.data["panel"] = cmds.playblast( + activeEditor=True + ).split("|")[-1] + # get cameras members = instance.data['setMembers'] cameras = cmds.ls(members, long=True, @@ -74,6 +80,8 @@ class CollectReview(pyblish.api.InstancePlugin): data['review_width'] = instance.data['review_width'] data['review_height'] = instance.data['review_height'] data["isolate"] = instance.data["isolate"] + data["panZoom"] = instance.data.get("panZoom", False) + data["panel"] = instance.data["panel"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) instance.context[i].data.update(data) @@ -139,3 +147,21 @@ class CollectReview(pyblish.api.InstancePlugin): "filename": node.filename.get() } ) + + # Collect focal length. + attr = camera + ".focalLength" + focal_length = None + if get_attribute_input(attr): + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + 1 + focal_length = [ + cmds.getAttr(attr, time=t) for t in range(int(start), int(end)) + ] + else: + focal_length = cmds.getAttr(attr) + + key = "focalLength" + try: + instance.data["burninDataMembers"][key] = focal_length + except KeyError: + instance.data["burninDataMembers"] = {key: focal_length} diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index df07a674dc..447c9a615c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- """Maya look extractor.""" import os -import sys import json import tempfile import platform import contextlib -import subprocess from collections import OrderedDict from maya import cmds # noqa @@ -16,40 +14,20 @@ import pyblish.api from openpype.lib import source_hash, run_subprocess from openpype.pipeline import legacy_io, publish from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.lib import image_info, guess_colorspace # Modes for transfer COPY = 1 HARDLINK = 2 -def escape_space(path): - """Ensure path is enclosed by quotes to allow paths with spaces""" - return '"{}"'.format(path) if " " in path else path - - -def get_ocio_config_path(profile_folder): - """Path to OpenPype vendorized OCIO. - - Vendorized OCIO config file path is grabbed from the specific path - hierarchy specified below. - - "{OPENPYPE_ROOT}/vendor/OpenColorIO-Configs/{profile_folder}/config.ocio" - Args: - profile_folder (str): Name of folder to grab config file from. - - Returns: - str: Path to vendorized config file. - """ - - return os.path.join( - os.environ["OPENPYPE_ROOT"], - "vendor", - "bin", - "ocioconfig", - "OpenColorIOConfigs", - profile_folder, - "config.ocio" - ) +def _has_arnold(): + """Return whether the arnold package is available and can be imported.""" + try: + import arnold # noqa: F401 + return True + except (ImportError, ModuleNotFoundError): + return False def find_paths_by_hash(texture_hash): @@ -367,16 +345,25 @@ class ExtractLook(publish.Extractor): for filepath in files_metadata: linearize = False - if do_maketx and files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501 - linearize = True - # set its file node to 'raw' as tx will be linearized - files_metadata[filepath]["color_space"] = "Raw" + # if OCIO color management enabled + # it won't take the condition of the files_metadata + + ocio_maya = cmds.colorManagementPrefs(q=True, + cmConfigFileEnabled=True, + cmEnabled=True) + + if do_maketx and not ocio_maya: + if files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501 + linearize = True + # set its file node to 'raw' as tx will be linearized + files_metadata[filepath]["color_space"] = "Raw" # if do_maketx: # color_space = "Raw" source, mode, texture_hash = self._process_texture( filepath, + resource, do_maketx, staging=staging_dir, linearize=linearize, @@ -482,7 +469,8 @@ class ExtractLook(publish.Extractor): resources_dir, basename + ext ) - def _process_texture(self, filepath, do_maketx, staging, linearize, force): + def _process_texture(self, filepath, resource, + do_maketx, staging, linearize, force): """Process a single texture file on disk for publishing. This will: 1. Check whether it's already published, if so it will do hardlink @@ -524,10 +512,47 @@ class ExtractLook(publish.Extractor): texture_hash ] if linearize: - self.log.info("tx: converting sRGB -> linear") - additional_args.extend(["--colorconvert", "sRGB", "linear"]) + if cmds.colorManagementPrefs(query=True, cmEnabled=True): + render_colorspace = cmds.colorManagementPrefs(query=True, + renderingSpaceName=True) # noqa + config_path = cmds.colorManagementPrefs(query=True, + configFilePath=True) # noqa + if not os.path.exists(config_path): + raise RuntimeError("No OCIO config path found!") + + color_space_attr = resource["node"] + ".colorSpace" + try: + color_space = cmds.getAttr(color_space_attr) + except ValueError: + # node doesn't have color space attribute + if _has_arnold(): + img_info = image_info(filepath) + color_space = guess_colorspace(img_info) + else: + color_space = "Raw" + self.log.info("tx: converting {0} -> {1}".format(color_space, render_colorspace)) # noqa + + additional_args.extend(["--colorconvert", + color_space, + render_colorspace]) + else: + + if _has_arnold(): + img_info = image_info(filepath) + color_space = guess_colorspace(img_info) + if color_space == "sRGB": + self.log.info("tx: converting sRGB -> linear") + additional_args.extend(["--colorconvert", + "sRGB", + "Raw"]) + else: + self.log.info("tx: texture's colorspace " + "is already linear") + else: + self.log.warning("cannot guess the colorspace" + "color conversion won't be available!") # noqa + - config_path = get_ocio_config_path("nuke-default") additional_args.extend(["--colorconfig", config_path]) # Ensure folder exists if not os.path.exists(os.path.dirname(converted)): diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 1966ad7b66..72b1489522 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,5 +1,6 @@ import os import json +import contextlib import clique import capture @@ -11,6 +12,16 @@ from maya import cmds import pymel.core as pm +@contextlib.contextmanager +def panel_camera(panel, camera): + original_camera = cmds.modelPanel(panel, query=True, camera=True) + try: + cmds.modelPanel(panel, edit=True, camera=camera) + yield + finally: + cmds.modelPanel(panel, edit=True, camera=original_camera) + + class ExtractPlayblast(publish.Extractor): """Extract viewport playblast. @@ -25,6 +36,16 @@ class ExtractPlayblast(publish.Extractor): optional = True capture_preset = {} + def _capture(self, preset): + self.log.info( + "Using preset:\n{}".format( + json.dumps(preset, sort_keys=True, indent=4) + ) + ) + + path = capture.capture(log=self.log, **preset) + self.log.debug("playblast path {}".format(path)) + def process(self, instance): self.log.info("Extracting capture..") @@ -43,7 +64,7 @@ class ExtractPlayblast(publish.Extractor): self.log.info("start: {}, end: {}".format(start, end)) # get cameras - camera = instance.data['review_camera'] + camera = instance.data["review_camera"] preset = lib.load_capture_preset(data=self.capture_preset) # Grab capture presets from the project settings @@ -57,23 +78,23 @@ class ExtractPlayblast(publish.Extractor): asset_height = asset_data.get("resolutionHeight") review_instance_width = instance.data.get("review_width") review_instance_height = instance.data.get("review_height") - preset['camera'] = camera + preset["camera"] = camera # Tests if project resolution is set, # if it is a value other than zero, that value is # used, if not then the asset resolution is # used if review_instance_width and review_instance_height: - preset['width'] = review_instance_width - preset['height'] = review_instance_height + preset["width"] = review_instance_width + preset["height"] = review_instance_height elif width_preset and height_preset: - preset['width'] = width_preset - preset['height'] = height_preset + preset["width"] = width_preset + preset["height"] = height_preset elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height - preset['start_frame'] = start - preset['end_frame'] = end + preset["width"] = asset_width + preset["height"] = asset_height + preset["start_frame"] = start + preset["end_frame"] = end # Enforce persisting camera depth of field camera_options = preset.setdefault("camera_options", {}) @@ -86,8 +107,8 @@ class ExtractPlayblast(publish.Extractor): self.log.info("Outputting images to %s" % path) - preset['filename'] = path - preset['overwrite'] = True + preset["filename"] = path + preset["overwrite"] = True pm.refresh(f=True) @@ -114,11 +135,11 @@ class ExtractPlayblast(publish.Extractor): # Disable Pan/Zoom. pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + preset.pop("pan_zoom", None) + preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"] # Need to explicitly enable some viewport changes so the viewport is # refreshed ahead of playblasting. - panel = cmds.getPanel(withFocus=True) keys = [ "useDefaultMaterial", "wireframeOnShaded", @@ -129,56 +150,67 @@ class ExtractPlayblast(publish.Extractor): viewport_defaults = {} for key in keys: viewport_defaults[key] = cmds.modelEditor( - panel, query=True, **{key: True} + instance.data["panel"], query=True, **{key: True} ) if preset["viewport_options"][key]: - cmds.modelEditor(panel, edit=True, **{key: True}) + cmds.modelEditor( + instance.data["panel"], edit=True, **{key: True} + ) override_viewport_options = ( - capture_presets['Viewport Options']['override_viewport_options'] + capture_presets["Viewport Options"]["override_viewport_options"] ) - with lib.maintained_time(): - filename = preset.get("filename", "%TEMP%") - # Force viewer to False in call to capture because we have our own - # viewer opening call to allow a signal to trigger between - # playblast and viewer - preset['viewer'] = False + # Force viewer to False in call to capture because we have our own + # viewer opening call to allow a signal to trigger between + # playblast and viewer + preset["viewer"] = False - # Update preset with current panel setting - # if override_viewport_options is turned off - panel = cmds.getPanel(withFocus=True) or "" - if not override_viewport_options and "modelPanel" in panel: - panel_preset = capture.parse_active_view() - panel_preset.pop("camera") - preset.update(panel_preset) - cmds.setFocus(panel) + # Update preset with current panel setting + # if override_viewport_options is turned off + if not override_viewport_options: + panel_preset = capture.parse_view(instance.data["panel"]) + panel_preset.pop("camera") + preset.update(panel_preset) - self.log.info( - "Using preset:\n{}".format( - json.dumps(preset, sort_keys=True, indent=4) + # Need to ensure Python 2 compatibility. + # TODO: Remove once dropping Python 2. + if getattr(contextlib, "nested", None): + # Python 3 compatibility. + with contextlib.nested( + lib.maintained_time(), + panel_camera(instance.data["panel"], preset["camera"]) + ): + self._capture(preset) + else: + # Python 2 compatibility. + with contextlib.ExitStack() as stack: + stack.enter_context(lib.maintained_time()) + stack.enter_context( + panel_camera(instance.data["panel"], preset["camera"]) ) - ) - path = capture.capture(log=self.log, **preset) + self._capture(preset) # Restoring viewport options. - cmds.modelEditor(panel, edit=True, **viewport_defaults) + if viewport_defaults: + cmds.modelEditor( + instance.data["panel"], edit=True, **viewport_defaults + ) cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) - self.log.debug("playblast path {}".format(path)) - collected_files = os.listdir(stagingdir) patterns = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble(collected_files, minimum_items=1, patterns=patterns) + filename = preset.get("filename", "%TEMP%") self.log.debug("filename {}".format(filename)) frame_collection = None for collection in collections: - filebase = collection.format('{head}').rstrip(".") + filebase = collection.format("{head}").rstrip(".") self.log.debug("collection head {}".format(filebase)) if filebase in filename: frame_collection = collection @@ -202,15 +234,15 @@ class ExtractPlayblast(publish.Extractor): collected_files = collected_files[0] representation = { - 'name': 'png', - 'ext': 'png', - 'files': collected_files, + "name": self.capture_preset["Codec"]["compression"], + "ext": self.capture_preset["Codec"]["compression"], + "files": collected_files, "stagingDir": stagingdir, "frameStart": start, "frameEnd": end, - 'fps': fps, - 'preview': True, - 'tags': tags, - 'camera_name': camera_node_name + "fps": fps, + "preview": True, + "tags": tags, + "camera_name": camera_node_name } instance.data["representations"].append(representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 1d94bd58c5..f2d084b828 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -26,28 +26,28 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): self.log.info("Extracting capture..") - camera = instance.data['review_camera'] + camera = instance.data["review_camera"] - capture_preset = ( - instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']['capture_preset'] - ) + maya_setting = instance.context.data["project_settings"]["maya"] + plugin_setting = maya_setting["publish"]["ExtractPlayblast"] + capture_preset = plugin_setting["capture_preset"] override_viewport_options = ( - capture_preset['Viewport Options']['override_viewport_options'] + capture_preset["Viewport Options"]["override_viewport_options"] ) try: preset = lib.load_capture_preset(data=capture_preset) except KeyError as ke: - self.log.error('Error loading capture presets: {}'.format(str(ke))) + self.log.error("Error loading capture presets: {}".format(str(ke))) preset = {} - self.log.info('Using viewport preset: {}'.format(preset)) + self.log.info("Using viewport preset: {}".format(preset)) # preset["off_screen"] = False - preset['camera'] = camera - preset['start_frame'] = instance.data["frameStart"] - preset['end_frame'] = instance.data["frameStart"] - preset['camera_options'] = { + preset["camera"] = camera + preset["start_frame"] = instance.data["frameStart"] + preset["end_frame"] = instance.data["frameStart"] + preset["camera_options"] = { "displayGateMask": False, "displayResolution": False, "displayFilmGate": False, @@ -74,14 +74,14 @@ class ExtractThumbnail(publish.Extractor): # used, if not then the asset resolution is # used if review_instance_width and review_instance_height: - preset['width'] = review_instance_width - preset['height'] = review_instance_height + preset["width"] = review_instance_width + preset["height"] = review_instance_height elif width_preset and height_preset: - preset['width'] = width_preset - preset['height'] = height_preset + preset["width"] = width_preset + preset["height"] = height_preset elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height + preset["width"] = asset_width + preset["height"] = asset_height # Create temp directory for thumbnail # - this is to avoid "override" of source file @@ -96,8 +96,8 @@ class ExtractThumbnail(publish.Extractor): self.log.info("Outputting images to %s" % path) - preset['filename'] = path - preset['overwrite'] = True + preset["filename"] = path + preset["overwrite"] = True pm.refresh(f=True) @@ -123,14 +123,14 @@ class ExtractThumbnail(publish.Extractor): preset["viewport_options"] = {"imagePlane": image_plane} # Disable Pan/Zoom. - pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + preset.pop("pan_zoom", None) + preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"] with lib.maintained_time(): # Force viewer to False in call to capture because we have our own # viewer opening call to allow a signal to trigger between # playblast and viewer - preset['viewer'] = False + preset["viewer"] = False # Update preset with current panel setting # if override_viewport_options is turned off @@ -145,17 +145,15 @@ class ExtractThumbnail(publish.Extractor): _, thumbnail = os.path.split(playblast) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) - self.log.info("file list {}".format(thumbnail)) if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'thumbnail', - 'ext': 'jpg', - 'files': thumbnail, + "name": "thumbnail", + "ext": "jpg", + "files": thumbnail, "stagingDir": dst_staging, "thumbnail": True } diff --git a/openpype/hosts/maya/plugins/publish/validate_look_color_space.py b/openpype/hosts/maya/plugins/publish/validate_look_color_space.py new file mode 100644 index 0000000000..b1bdeb7541 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_look_color_space.py @@ -0,0 +1,26 @@ +from maya import cmds + +import pyblish.api +from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline import PublishValidationError + + +class ValidateMayaColorSpace(pyblish.api.InstancePlugin): + """ + Check if the OCIO Color Management and maketx options + enabled at the same time + """ + + order = ValidateContentsOrder + families = ['look'] + hosts = ['maya'] + label = 'Color Management with maketx' + + def process(self, instance): + ocio_maya = cmds.colorManagementPrefs(q=True, + cmConfigFileEnabled=True, + cmEnabled=True) + maketx = instance.data["maketx"] + + if ocio_maya and maketx: + raise PublishValidationError("Maya is color managed and maketx option is on. OpenPype doesn't support this combination yet.") # noqa diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 94e2633593..53f340cd2c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -13,6 +13,22 @@ from openpype.pipeline.publish import ( from openpype.hosts.maya.api import lib +def convert_to_int_or_float(string_value): + # Order of types are important here since float can convert string + # representation of integer. + types = [int, float] + for t in types: + try: + result = t(string_value) + except ValueError: + continue + else: + return result + + # Neither integer or float. + return string_value + + def get_redshift_image_format_labels(): """Return nice labels for Redshift image formats.""" var = "$g_redshiftImageFormatLabels" @@ -242,10 +258,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) # load validation definitions from settings - validation_settings = ( - instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501 - "{}_render_attributes".format(renderer)) or [] - ) settings_lights_flag = instance.context.data["project_settings"].get( "maya", {}).get( "RenderSettings", {}).get( @@ -253,17 +265,67 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): instance_lights_flag = instance.data.get("renderSetupIncludeLights") if settings_lights_flag != instance_lights_flag: - cls.log.warning('Instance flag for "Render Setup Include Lights" is set to {0} and Settings flag is set to {1}'.format(instance_lights_flag, settings_lights_flag)) # noqa + cls.log.warning( + "Instance flag for \"Render Setup Include Lights\" is set to " + "{} and Settings flag is set to {}".format( + instance_lights_flag, settings_lights_flag + ) + ) # go through definitions and test if such node.attribute exists. # if so, compare its value from the one required. - for attr, value in OrderedDict(validation_settings).items(): - cls.log.debug("{}: {}".format(attr, value)) - if "." not in attr: - cls.log.warning("Skipping invalid attribute defined in " - "validation settings: '{}'".format(attr)) + for attribute, data in cls.get_nodes(instance, renderer).items(): + # Validate the settings has values. + if not data["values"]: + cls.log.error( + "Settings for {}.{} is missing values.".format( + node, attribute + ) + ) continue + for node in data["nodes"]: + try: + render_value = cmds.getAttr( + "{}.{}".format(node, attribute) + ) + except RuntimeError: + invalid = True + cls.log.error( + "Cannot get value of {}.{}".format(node, attribute) + ) + else: + if render_value not in data["values"]: + invalid = True + cls.log.error( + "Invalid value {} set on {}.{}. Expecting " + "{}".format( + render_value, node, attribute, data["values"] + ) + ) + + return invalid + + @classmethod + def get_nodes(cls, instance, renderer): + maya_settings = instance.context.data["project_settings"]["maya"] + validation_settings = ( + maya_settings["publish"]["ValidateRenderSettings"].get( + "{}_render_attributes".format(renderer) + ) or [] + ) + result = {} + for attr, values in OrderedDict(validation_settings).items(): + cls.log.debug("{}: {}".format(attr, values)) + if "." not in attr: + cls.log.warning( + "Skipping invalid attribute defined in validation " + "settings: \"{}\"".format(attr) + ) + continue + + values = [convert_to_int_or_float(v) for v in values] + node_type, attribute_name = attr.split(".", 1) # first get node of that type @@ -271,28 +333,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): if not nodes: cls.log.warning( - "No nodes of type '{}' found.".format(node_type)) + "No nodes of type \"{}\" found.".format(node_type) + ) continue - for node in nodes: - try: - render_value = cmds.getAttr( - "{}.{}".format(node, attribute_name)) - except RuntimeError: - invalid = True - cls.log.error( - "Cannot get value of {}.{}".format( - node, attribute_name)) - else: - if str(value) != str(render_value): - invalid = True - cls.log.error( - ("Invalid value {} set on {}.{}. " - "Expecting {}").format( - render_value, node, attribute_name, value) - ) + result[attribute_name] = {"nodes": nodes, "values": values} - return invalid + return result @classmethod def repair(cls, instance): @@ -305,6 +352,12 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "{aov_separator}", instance.data.get("aovSeparator", "_") ) + for attribute, data in cls.get_nodes(instance, renderer).items(): + if not data["values"]: + continue + for node in data["nodes"]: + lib.set_attribute(attribute, data["values"][0], node) + with lib.renderlayer(layer_node): default = lib.RENDER_ATTRS['default'] render_attrs = lib.RENDER_ATTRS.get(renderer, default) diff --git a/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py index a864a18cee..06250f5779 100644 --- a/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py @@ -48,6 +48,18 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): yeti_loaded = cmds.pluginInfo("pgYetiMaya", query=True, loaded=True) + if not yeti_loaded and not cmds.ls(type="pgYetiMaya"): + # The yeti plug-in is available and loaded so at + # this point we don't really care whether the scene + # has any yeti callback set or not since if the callback + # is there it wouldn't error and if it weren't then + # nothing happens because there are no yeti nodes. + cls.log.info( + "Yeti is loaded but no yeti nodes were found. " + "Callback validation skipped.." + ) + return False + renderer = instance.data["renderer"] if renderer == "redshift": cls.log.info("Redshift ignores any pre and post render callbacks") diff --git a/openpype/hosts/maya/tools/__init__.py b/openpype/hosts/maya/tools/__init__.py new file mode 100644 index 0000000000..bd1e302cd2 --- /dev/null +++ b/openpype/hosts/maya/tools/__init__.py @@ -0,0 +1,27 @@ +from openpype.tools.utils.host_tools import qt_app_context + + +class MayaToolsSingleton: + _look_assigner = None + + +def get_look_assigner_tool(parent): + """Create, cache and return look assigner tool window.""" + if MayaToolsSingleton._look_assigner is None: + from .mayalookassigner import MayaLookAssignerWindow + mayalookassigner_window = MayaLookAssignerWindow(parent) + MayaToolsSingleton._look_assigner = mayalookassigner_window + return MayaToolsSingleton._look_assigner + + +def show_look_assigner(parent=None): + """Look manager is Maya specific tool for look management.""" + + with qt_app_context(): + look_assigner_tool = get_look_assigner_tool(parent) + look_assigner_tool.show() + + # Pull window to the front. + look_assigner_tool.raise_() + look_assigner_tool.activateWindow() + look_assigner_tool.showNormal() diff --git a/openpype/tools/mayalookassigner/LICENSE b/openpype/hosts/maya/tools/mayalookassigner/LICENSE similarity index 100% rename from openpype/tools/mayalookassigner/LICENSE rename to openpype/hosts/maya/tools/mayalookassigner/LICENSE diff --git a/openpype/tools/mayalookassigner/__init__.py b/openpype/hosts/maya/tools/mayalookassigner/__init__.py similarity index 100% rename from openpype/tools/mayalookassigner/__init__.py rename to openpype/hosts/maya/tools/mayalookassigner/__init__.py diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/hosts/maya/tools/mayalookassigner/app.py similarity index 100% rename from openpype/tools/mayalookassigner/app.py rename to openpype/hosts/maya/tools/mayalookassigner/app.py diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/hosts/maya/tools/mayalookassigner/commands.py similarity index 93% rename from openpype/tools/mayalookassigner/commands.py rename to openpype/hosts/maya/tools/mayalookassigner/commands.py index 2e7a51efde..3d9746511d 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/hosts/maya/tools/mayalookassigner/commands.py @@ -80,21 +80,7 @@ def get_all_asset_nodes(): Returns: list: list of dictionaries """ - - host = registered_host() - - nodes = [] - for container in host.ls(): - # We are not interested in looks but assets! - if container["loader"] == "LookLoader": - continue - - # Gather all information - container_name = container["objectName"] - nodes += lib.get_container_members(container_name) - - nodes = list(set(nodes)) - return nodes + return cmds.ls(dag=True, noIntermediate=True, long=True) def create_asset_id_hash(nodes): diff --git a/openpype/tools/mayalookassigner/models.py b/openpype/hosts/maya/tools/mayalookassigner/models.py similarity index 100% rename from openpype/tools/mayalookassigner/models.py rename to openpype/hosts/maya/tools/mayalookassigner/models.py diff --git a/openpype/tools/mayalookassigner/views.py b/openpype/hosts/maya/tools/mayalookassigner/views.py similarity index 100% rename from openpype/tools/mayalookassigner/views.py rename to openpype/hosts/maya/tools/mayalookassigner/views.py diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py similarity index 100% rename from openpype/tools/mayalookassigner/vray_proxies.py rename to openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py diff --git a/openpype/tools/mayalookassigner/widgets.py b/openpype/hosts/maya/tools/mayalookassigner/widgets.py similarity index 100% rename from openpype/tools/mayalookassigner/widgets.py rename to openpype/hosts/maya/tools/mayalookassigner/widgets.py diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a5a631cc70..2a14096f0e 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2861,10 +2861,10 @@ class NukeDirmap(HostDirmap): pass def dirmap_routine(self, source_path, destination_path): - log.debug("{}: {}->{}".format(self.file_name, - source_path, destination_path)) source_path = source_path.lower().replace(os.sep, '/') destination_path = destination_path.lower().replace(os.sep, '/') + log.debug("Map: {} with: {}->{}".format(self.file_name, + source_path, destination_path)) if platform.system().lower() == "windows": self.file_name = self.file_name.lower().replace( source_path, destination_path) @@ -2878,6 +2878,7 @@ class DirmapCache: _project_name = None _project_settings = None _sync_module = None + _mapping = None @classmethod def project_name(cls): @@ -2897,6 +2898,36 @@ class DirmapCache: cls._sync_module = ModulesManager().modules_by_name["sync_server"] return cls._sync_module + @classmethod + def mapping(cls): + return cls._mapping + + @classmethod + def set_mapping(cls, mapping): + cls._mapping = mapping + + +def dirmap_file_name_filter(file_name): + """Nuke callback function with single full path argument. + + Checks project settings for potential mapping from source to dest. + """ + + dirmap_processor = NukeDirmap( + file_name, + "nuke", + DirmapCache.project_name(), + DirmapCache.project_settings(), + DirmapCache.sync_module(), + ) + if not DirmapCache.mapping(): + DirmapCache.set_mapping(dirmap_processor.get_mappings()) + + dirmap_processor.process_dirmap(DirmapCache.mapping()) + if os.path.exists(dirmap_processor.file_name): + return dirmap_processor.file_name + return file_name + @contextlib.contextmanager def node_tempfile(): @@ -2942,25 +2973,6 @@ def duplicate_node(node): return dupli_node -def dirmap_file_name_filter(file_name): - """Nuke callback function with single full path argument. - - Checks project settings for potential mapping from source to dest. - """ - - dirmap_processor = NukeDirmap( - file_name, - "nuke", - DirmapCache.project_name(), - DirmapCache.project_settings(), - DirmapCache.sync_module(), - ) - dirmap_processor.process_dirmap() - if os.path.exists(dirmap_processor.file_name): - return dirmap_processor.file_name - return file_name - - def get_group_io_nodes(nodes): """Get the input and the output of a group of nodes.""" diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index f227aa161a..67c7877e60 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -54,22 +54,19 @@ class LoadBackdropNodes(load.LoaderPlugin): version = context['version'] version_data = version.get("data", {}) vname = version.get("name", None) - first = version_data.get("frameStart", None) - last = version_data.get("frameEnd", None) namespace = namespace or context['asset']['name'] colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) # prepare data for imprinting # add additional metadata from the version to imprint to Avalon knob - add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", - "source", "author", "fps"] + add_keys = ["source", "author", "fps"] - data_imprint = {"frameStart": first, - "frameEnd": last, - "version": vname, - "colorspaceInput": colorspace, - "objectName": object_name} + data_imprint = { + "version": vname, + "colorspaceInput": colorspace, + "objectName": object_name + } for k in add_keys: data_imprint.update({k: version_data[k]}) @@ -204,18 +201,13 @@ class LoadBackdropNodes(load.LoaderPlugin): name = container['name'] version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) - first = version_data.get("frameStart", None) - last = version_data.get("frameEnd", None) namespace = container['namespace'] colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", - "source", "author", "fps"] + add_keys = ["source", "author", "fps"] data_imprint = {"representation": str(representation["_id"]), - "frameStart": first, - "frameEnd": last, "version": vname, "colorspaceInput": colorspace, "objectName": object_name} diff --git a/openpype/hosts/nuke/plugins/publish/collect_backdrop.py b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py index 8eaefa6854..7d51af7e9e 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py @@ -51,38 +51,10 @@ class CollectBackdrops(pyblish.api.InstancePlugin): instance.data["label"] = "{0} ({1} nodes)".format( bckn.name(), len(instance.data["transientData"]["childNodes"])) - instance.data["families"].append(instance.data["family"]) - - # Get frame range - handle_start = instance.context.data["handleStart"] - handle_end = instance.context.data["handleEnd"] - first_frame = int(nuke.root()["first_frame"].getValue()) - last_frame = int(nuke.root()["last_frame"].getValue()) - # get version version = instance.context.data.get('version') - if not version: - raise RuntimeError("Script name has no version in the name.") + if version: + instance.data['version'] = version - instance.data['version'] = version - - # Add version data to instance - version_data = { - "handles": handle_start, - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": first_frame + handle_start, - "frameEnd": last_frame - handle_end, - "version": int(version), - "families": [instance.data["family"]] + instance.data["families"], - "subset": instance.data["subset"], - "fps": instance.context.data["fps"] - } - - instance.data.update({ - "versionData": version_data, - "frameStart": first_frame, - "frameEnd": last_frame - }) self.log.info("Backdrop instance collected: `{}`".format(instance)) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data.py b/openpype/hosts/nuke/plugins/publish/extract_review_data.py index dee8248295..c221af40fb 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data.py @@ -25,7 +25,7 @@ class ExtractReviewData(publish.Extractor): # review can be removed since `ProcessSubmittedJobOnFarm` will create # reviewable representation if needed if ( - "render.farm" in instance.data["families"] + instance.data.get("farm") and "review" in instance.data["families"] ): instance.data["families"].remove("review") diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 67779e9599..e4b7b155cd 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -49,7 +49,12 @@ class ExtractReviewDataLut(publish.Extractor): exporter.stagingDir, exporter.file).replace("\\", "/") instance.data["representations"] += data["representations"] - if "render.farm" in families: + # review can be removed since `ProcessSubmittedJobOnFarm` will create + # reviewable representation if needed + if ( + instance.data.get("farm") + and "review" in instance.data["families"] + ): instance.data["families"].remove("review") self.log.debug( diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 3fcfc2a4b5..956d1a54a3 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -105,10 +105,7 @@ class ExtractReviewDataMov(publish.Extractor): self, instance, o_name, o_data["extension"], multiple_presets) - if ( - "render.farm" in families or - "prerender.farm" in families - ): + if instance.data.get("farm"): if "review" in instance.data["families"]: instance.data["families"].remove("review") diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index a1a0e241c0..f391ca1e7c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -31,7 +31,7 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): - if "render.farm" in instance.data["families"]: + if instance.data.get("farm"): return with napi.maintained_selection(): diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py index 89ba6ad4e6..25732446b5 100644 --- a/openpype/hosts/photoshop/api/launch_logic.py +++ b/openpype/hosts/photoshop/api/launch_logic.py @@ -66,11 +66,11 @@ class MainThreadItem: return self._result def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index eeb9e65dec..b3717e01ea 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -69,7 +69,7 @@ class OpenPypeMenu(QtWidgets.QWidget): # "Set colorspace from presets", self # ) # reset_resolution_btn = QtWidgets.QPushButton( - # "Reset Resolution from peresets", self + # "Set Resolution from presets", self # ) layout = QtWidgets.QVBoxLayout(self) @@ -108,7 +108,7 @@ class OpenPypeMenu(QtWidgets.QWidget): libload_btn.clicked.connect(self.on_libload_clicked) # rename_btn.clicked.connect(self.on_rename_clicked) # set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) - # reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + # reset_resolution_btn.clicked.connect(self.on_set_resolution_clicked) experimental_btn.clicked.connect(self.on_experimental_clicked) def on_workfile_clicked(self): @@ -145,8 +145,8 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_set_colorspace_clicked(self): print("Clicked Set Colorspace") - def on_reset_resolution_clicked(self): - print("Clicked Reset Resolution") + def on_set_resolution_clicked(self): + print("Clicked Set Resolution") def on_experimental_clicked(self): host_tools.show_experimental_tools_dialog() diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index e94e64e04a..6f76c25e0c 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -389,11 +389,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py index 538c6e4c5e..5cfa1faa50 100644 --- a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py +++ b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py @@ -55,7 +55,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): self._convert_render_layers( to_convert["renderLayer"], current_instances) self._convert_render_passes( - to_convert["renderpass"], current_instances) + to_convert["renderPass"], current_instances) self._convert_render_scenes( to_convert["renderScene"], current_instances) self._convert_workfiles( @@ -116,7 +116,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): render_layers_by_group_id = {} for instance in current_instances: if instance.get("creator_identifier") == "render.layer": - group_id = instance["creator_identifier"]["group_id"] + group_id = instance["creator_attributes"]["group_id"] render_layers_by_group_id[group_id] = instance for render_pass in render_passes: diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 9711024c79..2369c7329f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -415,11 +415,11 @@ class CreateRenderPass(TVPaintCreator): .get("creator_attributes", {}) .get("render_layer_instance_id") ) - render_layer_info = render_layers.get(render_layer_instance_id) + render_layer_info = render_layers.get(render_layer_instance_id, {}) self.update_instance_labels( instance_data, - render_layer_info["variant"], - render_layer_info["template_data"] + render_layer_info.get("variant"), + render_layer_info.get("template_data") ) instance = CreatedInstance.from_existing(instance_data, self) self._add_instance_to_context(instance) @@ -607,11 +607,11 @@ class CreateRenderPass(TVPaintCreator): current_instances = self.host.list_instances() render_layers = [ { - "value": instance["instance_id"], - "label": instance["subset"] + "value": inst["instance_id"], + "label": inst["subset"] } - for instance in current_instances - if instance["creator_identifier"] == CreateRenderlayer.identifier + for inst in current_instances + if inst.get("creator_identifier") == CreateRenderlayer.identifier ] if not render_layers: render_layers.append({"value": None, "label": "N/A"}) @@ -697,6 +697,7 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): ["create"] ["auto_detect_render"] ) + self.enabled = plugin_settings.get("enabled", False) self.allow_group_rename = plugin_settings["allow_group_rename"] self.group_name_template = plugin_settings["group_name_template"] self.group_idx_offset = plugin_settings["group_idx_offset"] diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py index 5eb702a1da..63f04cf3ce 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -22,9 +22,11 @@ class CollectOutputFrameRange(pyblish.api.InstancePlugin): context = instance.context frame_start = asset_doc["data"]["frameStart"] + fps = asset_doc["data"]["fps"] frame_end = frame_start + ( context.data["sceneMarkOut"] - context.data["sceneMarkIn"] ) + instance.data["fps"] = fps instance.data["frameStart"] = frame_start instance.data["frameEnd"] = frame_end self.log.info( diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py index 7e35726030..d7984ce971 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py @@ -1,5 +1,8 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.pipeline import ( list_instances, write_instances, @@ -31,7 +34,10 @@ class FixAssetNames(pyblish.api.Action): write_instances(new_instance_items) -class ValidateAssetNames(pyblish.api.ContextPlugin): +class ValidateAssetName( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate assset name present on instance. Asset name on instance should be the same as context's. @@ -43,6 +49,8 @@ class ValidateAssetNames(pyblish.api.ContextPlugin): actions = [FixAssetNames] def process(self, context): + if not self.is_active(context.data): + return context_asset_name = context.data["asset"] for instance in context: asset_name = instance.data.get("asset") diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py index 6a496a2e49..8e52a636f4 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py @@ -11,7 +11,7 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin): families = ["review", "render"] def process(self, instance): - layers = instance.data["layers"] + layers = instance.data.get("layers") # Instance have empty layers # - it is not job of this validator to check that if not layers: diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index 0030b0fd1c..7b2cc62bb5 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -1,7 +1,10 @@ import json import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.lib import execute_george @@ -23,7 +26,10 @@ class ValidateMarksRepair(pyblish.api.Action): ) -class ValidateMarks(pyblish.api.ContextPlugin): +class ValidateMarks( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate mark in and out are enabled and it's duration. Mark In/Out does not have to match frameStart and frameEnd but duration is @@ -59,6 +65,9 @@ class ValidateMarks(pyblish.api.ContextPlugin): } def process(self, context): + if not self.is_active(context.data): + return + current_data = { "markIn": context.data["sceneMarkIn"], "markInState": context.data["sceneMarkInState"], diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py index 4473e4b1b7..0ab8e811f5 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py @@ -1,11 +1,17 @@ import json import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) # TODO @iLliCiTiT add fix action for fps -class ValidateProjectSettings(pyblish.api.ContextPlugin): +class ValidateProjectSettings( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate scene settings against database.""" label = "Validate Scene Settings" @@ -13,6 +19,9 @@ class ValidateProjectSettings(pyblish.api.ContextPlugin): optional = True def process(self, context): + if not self.is_active(context.data): + return + expected_data = context.data["assetEntity"]["data"] scene_data = { "fps": context.data.get("sceneFps"), diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py index 066e54c670..229ccfcd18 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py @@ -1,5 +1,8 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.lib import execute_george @@ -14,7 +17,10 @@ class RepairStartFrame(pyblish.api.Action): execute_george("tv_startframe 0") -class ValidateStartFrame(pyblish.api.ContextPlugin): +class ValidateStartFrame( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate start frame being at frame 0.""" label = "Validate Start Frame" @@ -24,6 +30,9 @@ class ValidateStartFrame(pyblish.api.ContextPlugin): optional = True def process(self, context): + if not self.is_active(context.data): + return + start_frame = execute_george("tv_startframe") if start_frame == 0: return diff --git a/openpype/hosts/unreal/ue_workers.py b/openpype/hosts/unreal/ue_workers.py index 00f83a7d7a..d1740124a8 100644 --- a/openpype/hosts/unreal/ue_workers.py +++ b/openpype/hosts/unreal/ue_workers.py @@ -5,29 +5,38 @@ import re import subprocess from distutils import dir_util from pathlib import Path -from typing import List +from typing import List, Union import openpype.hosts.unreal.lib as ue_lib from qtpy import QtCore -def parse_comp_progress(line: str, progress_signal: QtCore.Signal(int)) -> int: - match = re.search('\[[1-9]+/[0-9]+\]', line) +def parse_comp_progress(line: str, progress_signal: QtCore.Signal(int)): + match = re.search(r"\[[1-9]+/[0-9]+]", line) if match is not None: - split: list[str] = match.group().split('/') + split: list[str] = match.group().split("/") curr: float = float(split[0][1:]) total: float = float(split[1][:-1]) progress_signal.emit(int((curr / total) * 100.0)) -def parse_prj_progress(line: str, progress_signal: QtCore.Signal(int)) -> int: - match = re.search('@progress', line) +def parse_prj_progress(line: str, progress_signal: QtCore.Signal(int)): + match = re.search("@progress", line) if match is not None: - percent_match = re.search('\d{1,3}', line) + percent_match = re.search(r"\d{1,3}", line) progress_signal.emit(int(percent_match.group())) +def retrieve_exit_code(line: str): + match = re.search(r"ExitCode=\d+", line) + if match is not None: + split: list[str] = match.group().split("=") + return int(split[1]) + + return None + + class UEProjectGenerationWorker(QtCore.QObject): finished = QtCore.Signal(str) failed = QtCore.Signal(str) @@ -77,16 +86,19 @@ class UEProjectGenerationWorker(QtCore.QObject): if self.dev_mode: stage_count = 4 - self.stage_begin.emit(f'Generating a new UE project ... 1 out of ' - f'{stage_count}') + self.stage_begin.emit( + ("Generating a new UE project ... 1 out of " + f"{stage_count}")) - commandlet_cmd = [f'{ue_editor_exe.as_posix()}', - f'{cmdlet_project.as_posix()}', - f'-run=OPGenerateProject', - f'{project_file.resolve().as_posix()}'] + commandlet_cmd = [ + f"{ue_editor_exe.as_posix()}", + f"{cmdlet_project.as_posix()}", + "-run=OPGenerateProject", + f"{project_file.resolve().as_posix()}", + ] if self.dev_mode: - commandlet_cmd.append('-GenerateCode') + commandlet_cmd.append("-GenerateCode") gen_process = subprocess.Popen(commandlet_cmd, stdout=subprocess.PIPE, @@ -94,24 +106,27 @@ class UEProjectGenerationWorker(QtCore.QObject): for line in gen_process.stdout: decoded_line = line.decode(errors="replace") - print(decoded_line, end='') + print(decoded_line, end="") self.log.emit(decoded_line) gen_process.stdout.close() return_code = gen_process.wait() if return_code and return_code != 0: - msg = 'Failed to generate ' + self.project_name \ - + f' project! Exited with return code {return_code}' + msg = ( + f"Failed to generate {self.project_name} " + f"project! Exited with return code {return_code}" + ) self.failed.emit(msg, return_code) raise RuntimeError(msg) print("--- Project has been generated successfully.") - self.stage_begin.emit(f'Writing the Engine ID of the build UE ... 1' - f' out of {stage_count}') + self.stage_begin.emit( + (f"Writing the Engine ID of the build UE ... 1" + f" out of {stage_count}")) if not project_file.is_file(): - msg = "Failed to write the Engine ID into .uproject file! Can " \ - "not read!" + msg = ("Failed to write the Engine ID into .uproject file! Can " + "not read!") self.failed.emit(msg) raise RuntimeError(msg) @@ -125,13 +140,14 @@ class UEProjectGenerationWorker(QtCore.QObject): pf.seek(0) json.dump(pf_json, pf, indent=4) pf.truncate() - print(f'--- Engine ID has been written into the project file') + print("--- Engine ID has been written into the project file") self.progress.emit(90) if self.dev_mode: # 2nd stage - self.stage_begin.emit(f'Generating project files ... 2 out of ' - f'{stage_count}') + self.stage_begin.emit( + (f"Generating project files ... 2 out of " + f"{stage_count}")) self.progress.emit(0) ubt_path = ue_lib.get_path_to_ubt(self.engine_path, @@ -154,8 +170,8 @@ class UEProjectGenerationWorker(QtCore.QObject): stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in gen_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) parse_prj_progress(decoded_line, self.progress) @@ -163,13 +179,13 @@ class UEProjectGenerationWorker(QtCore.QObject): return_code = gen_proc.wait() if return_code and return_code != 0: - msg = 'Failed to generate project files! ' \ - f'Exited with return code {return_code}' + msg = ("Failed to generate project files! " + f"Exited with return code {return_code}") self.failed.emit(msg, return_code) raise RuntimeError(msg) - self.stage_begin.emit(f'Building the project ... 3 out of ' - f'{stage_count}') + self.stage_begin.emit( + f"Building the project ... 3 out of {stage_count}") self.progress.emit(0) # 3rd stage build_prj_cmd = [ubt_path.as_posix(), @@ -177,16 +193,16 @@ class UEProjectGenerationWorker(QtCore.QObject): arch, "Development", "-TargetType=Editor", - f'-Project={project_file}', - f'{project_file}', + f"-Project={project_file}", + f"{project_file}", "-IgnoreJunk"] build_prj_proc = subprocess.Popen(build_prj_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in build_prj_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) parse_comp_progress(decoded_line, self.progress) @@ -194,16 +210,17 @@ class UEProjectGenerationWorker(QtCore.QObject): return_code = build_prj_proc.wait() if return_code and return_code != 0: - msg = 'Failed to build project! ' \ - f'Exited with return code {return_code}' + msg = ("Failed to build project! " + f"Exited with return code {return_code}") self.failed.emit(msg, return_code) raise RuntimeError(msg) # ensure we have PySide2 installed in engine self.progress.emit(0) - self.stage_begin.emit(f'Checking PySide2 installation... {stage_count}' - f' out of {stage_count}') + self.stage_begin.emit( + (f"Checking PySide2 installation... {stage_count} " + f" out of {stage_count}")) python_path = None if platform.system().lower() == "windows": python_path = self.engine_path / ("Engine/Binaries/ThirdParty/" @@ -225,9 +242,30 @@ class UEProjectGenerationWorker(QtCore.QObject): msg = f"Unreal Python not found at {python_path}" self.failed.emit(msg, 1) raise RuntimeError(msg) - subprocess.check_call( - [python_path.as_posix(), "-m", "pip", "install", "pyside2"] - ) + pyside_cmd = [python_path.as_posix(), + "-m", + "pip", + "install", + "pyside2"] + + pyside_install = subprocess.Popen(pyside_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + for line in pyside_install.stdout: + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") + self.log.emit(decoded_line) + + pyside_install.stdout.close() + return_code = pyside_install.wait() + + if return_code and return_code != 0: + msg = ("Failed to create the project! " + "The installation of PySide2 has failed!") + self.failed.emit(msg, return_code) + raise RuntimeError(msg) + self.progress.emit(100) self.finished.emit("Project successfully built!") @@ -266,26 +304,30 @@ class UEPluginInstallWorker(QtCore.QObject): # in order to successfully build the plugin, # It must be built outside the Engine directory and then moved - build_plugin_cmd: List[str] = [f'{uat_path.as_posix()}', - 'BuildPlugin', - f'-Plugin={uplugin_path.as_posix()}', - f'-Package={temp_dir.as_posix()}'] + build_plugin_cmd: List[str] = [f"{uat_path.as_posix()}", + "BuildPlugin", + f"-Plugin={uplugin_path.as_posix()}", + f"-Package={temp_dir.as_posix()}"] build_proc = subprocess.Popen(build_plugin_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return_code: Union[None, int] = None for line in build_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) + if return_code is None: + return_code = retrieve_exit_code(decoded_line) parse_comp_progress(decoded_line, self.progress) build_proc.stdout.close() - return_code = build_proc.wait() + build_proc.wait() if return_code and return_code != 0: - msg = 'Failed to build plugin' \ - f' project! Exited with return code {return_code}' + msg = ("Failed to build plugin" + f" project! Exited with return code {return_code}") + dir_util.remove_tree(temp_dir.as_posix()) self.failed.emit(msg, return_code) raise RuntimeError(msg) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 7cc296f47b..127d31d042 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -889,7 +889,8 @@ class ApplicationLaunchContext: self.modules_manager = ModulesManager() # Logger - logger_name = "{}-{}".format(self.__class__.__name__, self.app_name) + logger_name = "{}-{}".format(self.__class__.__name__, + self.application.full_name) self.log = Logger.get_logger(logger_name) self.executable = executable @@ -1246,7 +1247,7 @@ class ApplicationLaunchContext: args_len_str = " ({})".format(len(args)) self.log.info( "Launching \"{}\" with args{}: {}".format( - self.app_name, args_len_str, args + self.application.full_name, args_len_str, args ) ) self.launch_args = args @@ -1271,7 +1272,9 @@ class ApplicationLaunchContext: exc_info=True ) - self.log.debug("Launch of {} finished.".format(self.app_name)) + self.log.debug("Launch of {} finished.".format( + self.application.full_name + )) return self.process @@ -1508,8 +1511,8 @@ def prepare_app_environments( if key in source_env: source_env[key] = value - # `added_env_keys` has debug purpose - added_env_keys = {app.group.name, app.name} + # `app_and_tool_labels` has debug purpose + app_and_tool_labels = [app.full_name] # Environments for application environments = [ app.group.environment, @@ -1532,15 +1535,14 @@ def prepare_app_environments( for group_name in sorted(groups_by_name.keys()): group = groups_by_name[group_name] environments.append(group.environment) - added_env_keys.add(group_name) for tool_name in sorted(tool_by_group_name[group_name].keys()): tool = tool_by_group_name[group_name][tool_name] environments.append(tool.environment) - added_env_keys.add(tool.name) + app_and_tool_labels.append(tool.full_name) log.debug( "Will add environments for apps and tools: {}".format( - ", ".join(added_env_keys) + ", ".join(app_and_tool_labels) ) ) diff --git a/openpype/lib/execute.py b/openpype/lib/execute.py index 759a4db0cb..6f9a095285 100644 --- a/openpype/lib/execute.py +++ b/openpype/lib/execute.py @@ -8,6 +8,8 @@ import tempfile from .log import Logger from .vendor_bin_utils import find_executable +from .openpype_version import is_running_from_build + # MSDN process creation flag (Windows only) CREATE_NO_WINDOW = 0x08000000 @@ -102,6 +104,10 @@ def run_subprocess(*args, **kwargs): if ( platform.system().lower() == "windows" and "creationflags" not in kwargs + # shell=True already tries to hide the console window + # and passing these creationflags then shows the window again + # so we avoid it for shell=True cases + and kwargs.get("shell") is not True ): kwargs["creationflags"] = ( subprocess.CREATE_NEW_PROCESS_GROUP @@ -196,6 +202,11 @@ def run_openpype_process(*args, **kwargs): # Skip envs that can affect OpenPype process # - fill more if you find more env = clean_envs_for_openpype_process(os.environ) + + # Only keep OpenPype version if we are running from build. + if not is_running_from_build(): + env.pop("OPENPYPE_VERSION", None) + return run_subprocess(args, env=env, **kwargs) diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index fe70b37cb1..81332a8891 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -13,6 +13,16 @@ else: from shutil import copyfile +class DuplicateDestinationError(ValueError): + """Error raised when transfer destination already exists in queue. + + The error is only raised if `allow_queue_replacements` is False on the + FileTransaction instance and the added file to transfer is of a different + src file than the one already detected in the queue. + + """ + + class FileTransaction(object): """File transaction with rollback options. @@ -44,7 +54,7 @@ class FileTransaction(object): MODE_COPY = 0 MODE_HARDLINK = 1 - def __init__(self, log=None): + def __init__(self, log=None, allow_queue_replacements=False): if log is None: log = logging.getLogger("FileTransaction") @@ -60,6 +70,8 @@ class FileTransaction(object): # Backup file location mapping to original locations self._backup_to_original = {} + self._allow_queue_replacements = allow_queue_replacements + def add(self, src, dst, mode=MODE_COPY): """Add a new file to transfer queue. @@ -82,6 +94,14 @@ class FileTransaction(object): src, dst)) return else: + if not self._allow_queue_replacements: + raise DuplicateDestinationError( + "Transfer to destination is already in queue: " + "{} -> {}. It's not allowed to be replaced by " + "a new transfer from {}".format( + queued_src, dst, src + )) + self.log.warning("File transfer in queue replaced..") self.log.debug( "Removed from queue: {} -> {} replaced by {} -> {}".format( diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index b6797dbba0..e5deb7a6b2 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -224,18 +224,26 @@ def find_tool_in_custom_paths(paths, tool, validation_func=None): def _check_args_returncode(args): try: - # Python 2 compatibility where DEVNULL is not available + kwargs = {} + if platform.system().lower() == "windows": + kwargs["creationflags"] = ( + subprocess.CREATE_NEW_PROCESS_GROUP + | getattr(subprocess, "DETACHED_PROCESS", 0) + | getattr(subprocess, "CREATE_NO_WINDOW", 0) + ) + if hasattr(subprocess, "DEVNULL"): proc = subprocess.Popen( args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + **kwargs ) proc.wait() else: with open(os.devnull, "w") as devnull: proc = subprocess.Popen( - args, stdout=devnull, stderr=devnull, + args, stdout=devnull, stderr=devnull, **kwargs ) proc.wait() @@ -375,7 +383,7 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): # Look to PATH for the tool if not tool_executable_path: from_path = find_executable(tool) - if from_path and _oiio_executable_validation(from_path): + if from_path and _ffmpeg_executable_validation(from_path): tool_executable_path = from_path CachedToolPaths.cache_executable_path(tool, tool_executable_path) diff --git a/openpype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py index 6af911fffc..80979c83ab 100644 --- a/openpype/modules/clockify/clockify_api.py +++ b/openpype/modules/clockify/clockify_api.py @@ -6,34 +6,22 @@ import datetime import requests from .constants import ( CLOCKIFY_ENDPOINT, - ADMIN_PERMISSION_NAMES + ADMIN_PERMISSION_NAMES, ) from openpype.lib.local_settings import OpenPypeSecureRegistry - - -def time_check(obj): - if obj.request_counter < 10: - obj.request_counter += 1 - return - - wait_time = 1 - (time.time() - obj.request_time) - if wait_time > 0: - time.sleep(wait_time) - - obj.request_time = time.time() - obj.request_counter = 0 +from openpype.lib import Logger class ClockifyAPI: + log = Logger.get_logger(__name__) + def __init__(self, api_key=None, master_parent=None): self.workspace_name = None - self.workspace_id = None self.master_parent = master_parent self.api_key = api_key - self.request_counter = 0 - self.request_time = time.time() - + self._workspace_id = None + self._user_id = None self._secure_registry = None @property @@ -44,11 +32,19 @@ class ClockifyAPI: @property def headers(self): - return {"X-Api-Key": self.api_key} + return {"x-api-key": self.api_key} + + @property + def workspace_id(self): + return self._workspace_id + + @property + def user_id(self): + return self._user_id def verify_api(self): for key, value in self.headers.items(): - if value is None or value.strip() == '': + if value is None or value.strip() == "": return False return True @@ -59,65 +55,55 @@ class ClockifyAPI: if api_key is not None and self.validate_api_key(api_key) is True: self.api_key = api_key self.set_workspace() + self.set_user_id() if self.master_parent: self.master_parent.signed_in() return True return False def validate_api_key(self, api_key): - test_headers = {'X-Api-Key': api_key} - action_url = 'workspaces/' - time_check(self) + test_headers = {"x-api-key": api_key} + action_url = "user" response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=test_headers + CLOCKIFY_ENDPOINT + action_url, headers=test_headers ) if response.status_code != 200: return False return True - def validate_workspace_perm(self, workspace_id=None): - user_id = self.get_user_id() + def validate_workspace_permissions(self, workspace_id=None, user_id=None): if user_id is None: + self.log.info("No user_id found during validation") return False if workspace_id is None: workspace_id = self.workspace_id - action_url = "/workspaces/{}/users/{}/permissions".format( - workspace_id, user_id - ) - time_check(self) + action_url = f"workspaces/{workspace_id}/users?includeRoles=1" response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) - user_permissions = response.json() - for perm in user_permissions: - if perm['name'] in ADMIN_PERMISSION_NAMES: + data = response.json() + for user in data: + if user.get("id") == user_id: + roles_data = user.get("roles") + for entities in roles_data: + if entities.get("role") in ADMIN_PERMISSION_NAMES: return True return False def get_user_id(self): - action_url = 'v1/user/' - time_check(self) + action_url = "user" response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) - # this regex is neccessary: UNICODE strings are crashing - # during json serialization - id_regex = '\"{1}id\"{1}\:{1}\"{1}\w+\"{1}' - result = re.findall(id_regex, str(response.content)) - if len(result) != 1: - # replace with log and better message? - print('User ID was not found (this is a BUG!!!)') - return None - return json.loads('{'+result[0]+'}')['id'] + result = response.json() + user_id = result.get("id", None) + + return user_id def set_workspace(self, name=None): if name is None: - name = os.environ.get('CLOCKIFY_WORKSPACE', None) + name = os.environ.get("CLOCKIFY_WORKSPACE", None) self.workspace_name = name - self.workspace_id = None if self.workspace_name is None: return try: @@ -125,7 +111,7 @@ class ClockifyAPI: except Exception: result = False if result is not False: - self.workspace_id = result + self._workspace_id = result if self.master_parent is not None: self.master_parent.start_timer_check() return True @@ -139,6 +125,14 @@ class ClockifyAPI: return all_workspaces[name] return False + def set_user_id(self): + try: + user_id = self.get_user_id() + except Exception: + user_id = None + if user_id is not None: + self._user_id = user_id + def get_api_key(self): return self.secure_registry.get_item("api_key", None) @@ -146,11 +140,9 @@ class ClockifyAPI: self.secure_registry.set_item("api_key", api_key) def get_workspaces(self): - action_url = 'workspaces/' - time_check(self) + action_url = "workspaces/" response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return { workspace["name"]: workspace["id"] for workspace in response.json() @@ -159,27 +151,22 @@ class ClockifyAPI: def get_projects(self, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/projects/'.format(workspace_id) - time_check(self) + action_url = f"workspaces/{workspace_id}/projects" response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) - - return { - project["name"]: project["id"] for project in response.json() - } + if response.status_code != 403: + result = response.json() + return {project["name"]: project["id"] for project in result} def get_project_by_id(self, project_id, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/projects/{}/'.format( + action_url = "workspaces/{}/projects/{}".format( workspace_id, project_id ) - time_check(self) response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json() @@ -187,32 +174,24 @@ class ClockifyAPI: def get_tags(self, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/tags/'.format(workspace_id) - time_check(self) + action_url = "workspaces/{}/tags".format(workspace_id) response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) - return { - tag["name"]: tag["id"] for tag in response.json() - } + return {tag["name"]: tag["id"] for tag in response.json()} def get_tasks(self, project_id, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/projects/{}/tasks/'.format( + action_url = "workspaces/{}/projects/{}/tasks".format( workspace_id, project_id ) - time_check(self) response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) - return { - task["name"]: task["id"] for task in response.json() - } + return {task["name"]: task["id"] for task in response.json()} def get_workspace_id(self, workspace_name): all_workspaces = self.get_workspaces() @@ -236,48 +215,64 @@ class ClockifyAPI: return None return all_tasks[tag_name] - def get_task_id( - self, task_name, project_id, workspace_id=None - ): + def get_task_id(self, task_name, project_id, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - all_tasks = self.get_tasks( - project_id, workspace_id - ) + all_tasks = self.get_tasks(project_id, workspace_id) if task_name not in all_tasks: return None return all_tasks[task_name] def get_current_time(self): - return str(datetime.datetime.utcnow().isoformat())+'Z' + return str(datetime.datetime.utcnow().isoformat()) + "Z" def start_time_entry( - self, description, project_id, task_id=None, tag_ids=[], - workspace_id=None, billable=True + self, + description, + project_id, + task_id=None, + tag_ids=None, + workspace_id=None, + user_id=None, + billable=True, ): # Workspace if workspace_id is None: workspace_id = self.workspace_id + # User ID + if user_id is None: + user_id = self._user_id + + # get running timer to check if we need to start it + current_timer = self.get_in_progress() # Check if is currently run another times and has same values - current = self.get_in_progress(workspace_id) - if current is not None: + # DO not restart the timer, if it is already running for curent task + if current_timer: + current_timer_hierarchy = current_timer.get("description") + current_project_id = current_timer.get("projectId") + current_task_id = current_timer.get("taskId") if ( - current.get("description", None) == description and - current.get("projectId", None) == project_id and - current.get("taskId", None) == task_id + description == current_timer_hierarchy + and project_id == current_project_id + and task_id == current_task_id ): + self.log.info( + "Timer for the current project is already running" + ) self.bool_timer_run = True return self.bool_timer_run - self.finish_time_entry(workspace_id) + self.finish_time_entry() # Convert billable to strings if billable: - billable = 'true' + billable = "true" else: - billable = 'false' + billable = "false" # Rest API Action - action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) + action_url = "workspaces/{}/user/{}/time-entries".format( + workspace_id, user_id + ) start = self.get_current_time() body = { "start": start, @@ -285,169 +280,135 @@ class ClockifyAPI: "description": description, "projectId": project_id, "taskId": task_id, - "tagIds": tag_ids + "tagIds": tag_ids, } - time_check(self) response = requests.post( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) - - success = False if response.status_code < 300: - success = True - return success + return True + return False - def get_in_progress(self, workspace_id=None): - if workspace_id is None: - workspace_id = self.workspace_id - action_url = 'workspaces/{}/timeEntries/inProgress'.format( - workspace_id - ) - time_check(self) - response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers - ) + def _get_current_timer_values(self, response): + if response is None: + return try: output = response.json() except json.decoder.JSONDecodeError: - output = None - return output + return None + if output and isinstance(output, list): + return output[0] + return None - def finish_time_entry(self, workspace_id=None): + def get_in_progress(self, user_id=None, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - current = self.get_in_progress(workspace_id) - if current is None: - return + if user_id is None: + user_id = self.user_id - current_id = current["id"] - action_url = 'workspaces/{}/timeEntries/{}'.format( - workspace_id, current_id + action_url = ( + f"workspaces/{workspace_id}/user/" + f"{user_id}/time-entries?in-progress=1" ) - body = { - "start": current["timeInterval"]["start"], - "billable": current["billable"], - "description": current["description"], - "projectId": current["projectId"], - "taskId": current["taskId"], - "tagIds": current["tagIds"], - "end": self.get_current_time() - } - time_check(self) - response = requests.put( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + response = requests.get( + CLOCKIFY_ENDPOINT + action_url, headers=self.headers + ) + return self._get_current_timer_values(response) + + def finish_time_entry(self, workspace_id=None, user_id=None): + if workspace_id is None: + workspace_id = self.workspace_id + if user_id is None: + user_id = self.user_id + current_timer = self.get_in_progress() + if not current_timer: + return + action_url = "workspaces/{}/user/{}/time-entries".format( + workspace_id, user_id + ) + body = {"end": self.get_current_time()} + response = requests.patch( + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) return response.json() - def get_time_entries( - self, workspace_id=None, quantity=10 - ): + def get_time_entries(self, workspace_id=None, user_id=None, quantity=10): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) - time_check(self) + if user_id is None: + user_id = self.user_id + action_url = "workspaces/{}/user/{}/time-entries".format( + workspace_id, user_id + ) response = requests.get( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json()[:quantity] - def remove_time_entry(self, tid, workspace_id=None): + def remove_time_entry(self, tid, workspace_id=None, user_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/timeEntries/{}'.format( - workspace_id, tid + action_url = "workspaces/{}/user/{}/time-entries/{}".format( + workspace_id, user_id, tid ) - time_check(self) response = requests.delete( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json() def add_project(self, name, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/projects/'.format(workspace_id) + action_url = "workspaces/{}/projects".format(workspace_id) body = { "name": name, "clientId": "", "isPublic": "false", - "estimate": { - "estimate": 0, - "type": "AUTO" - }, + "estimate": {"estimate": 0, "type": "AUTO"}, "color": "#f44336", - "billable": "true" + "billable": "true", } - time_check(self) response = requests.post( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) return response.json() def add_workspace(self, name): - action_url = 'workspaces/' + action_url = "workspaces/" body = {"name": name} - time_check(self) response = requests.post( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) return response.json() - def add_task( - self, name, project_id, workspace_id=None - ): + def add_task(self, name, project_id, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/projects/{}/tasks/'.format( + action_url = "workspaces/{}/projects/{}/tasks".format( workspace_id, project_id ) - body = { - "name": name, - "projectId": project_id - } - time_check(self) + body = {"name": name, "projectId": project_id} response = requests.post( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) return response.json() def add_tag(self, name, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = 'workspaces/{}/tags'.format(workspace_id) - body = { - "name": name - } - time_check(self) + action_url = "workspaces/{}/tags".format(workspace_id) + body = {"name": name} response = requests.post( - CLOCKIFY_ENDPOINT + action_url, - headers=self.headers, - json=body + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) return response.json() - def delete_project( - self, project_id, workspace_id=None - ): + def delete_project(self, project_id, workspace_id=None): if workspace_id is None: workspace_id = self.workspace_id - action_url = '/workspaces/{}/projects/{}'.format( + action_url = "/workspaces/{}/projects/{}".format( workspace_id, project_id ) - time_check(self) response = requests.delete( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -455,12 +416,12 @@ class ClockifyAPI: return response.json() def convert_input( - self, entity_id, entity_name, mode='Workspace', project_id=None + self, entity_id, entity_name, mode="Workspace", project_id=None ): if entity_id is None: error = False error_msg = 'Missing information "{}"' - if mode.lower() == 'workspace': + if mode.lower() == "workspace": if entity_id is None and entity_name is None: if self.workspace_id is not None: entity_id = self.workspace_id @@ -471,14 +432,14 @@ class ClockifyAPI: else: if entity_id is None and entity_name is None: error = True - elif mode.lower() == 'project': + elif mode.lower() == "project": entity_id = self.get_project_id(entity_name) - elif mode.lower() == 'task': + elif mode.lower() == "task": entity_id = self.get_task_id( task_name=entity_name, project_id=project_id ) else: - raise TypeError('Unknown type') + raise TypeError("Unknown type") # Raise error if error: raise ValueError(error_msg.format(mode)) diff --git a/openpype/modules/clockify/clockify_module.py b/openpype/modules/clockify/clockify_module.py index 300d5576e2..200a268ad7 100644 --- a/openpype/modules/clockify/clockify_module.py +++ b/openpype/modules/clockify/clockify_module.py @@ -2,24 +2,13 @@ import os import threading import time -from openpype.modules import ( - OpenPypeModule, - ITrayModule, - IPluginPaths -) +from openpype.modules import OpenPypeModule, ITrayModule, IPluginPaths +from openpype.client import get_asset_by_name -from .clockify_api import ClockifyAPI -from .constants import ( - CLOCKIFY_FTRACK_USER_PATH, - CLOCKIFY_FTRACK_SERVER_PATH -) +from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH -class ClockifyModule( - OpenPypeModule, - ITrayModule, - IPluginPaths -): +class ClockifyModule(OpenPypeModule, ITrayModule, IPluginPaths): name = "clockify" def initialize(self, modules_settings): @@ -33,18 +22,23 @@ class ClockifyModule( self.timer_manager = None self.MessageWidgetClass = None self.message_widget = None - - self.clockapi = ClockifyAPI(master_parent=self) + self._clockify_api = None # TimersManager attributes # - set `timers_manager_connector` only in `tray_init` self.timers_manager_connector = None self._timers_manager_module = None + @property + def clockify_api(self): + if self._clockify_api is None: + from .clockify_api import ClockifyAPI + + self._clockify_api = ClockifyAPI(master_parent=self) + return self._clockify_api + def get_global_environments(self): - return { - "CLOCKIFY_WORKSPACE": self.workspace_name - } + return {"CLOCKIFY_WORKSPACE": self.workspace_name} def tray_init(self): from .widgets import ClockifySettings, MessageWidget @@ -52,7 +46,7 @@ class ClockifyModule( self.MessageWidgetClass = MessageWidget self.message_widget = None - self.widget_settings = ClockifySettings(self.clockapi) + self.widget_settings = ClockifySettings(self.clockify_api) self.widget_settings_required = None self.thread_timer_check = None @@ -61,7 +55,7 @@ class ClockifyModule( self.bool_api_key_set = False self.bool_workspace_set = False self.bool_timer_run = False - self.bool_api_key_set = self.clockapi.set_api() + self.bool_api_key_set = self.clockify_api.set_api() # Define itself as TimersManager connector self.timers_manager_connector = self @@ -71,12 +65,11 @@ class ClockifyModule( self.show_settings() return - self.bool_workspace_set = self.clockapi.workspace_id is not None + self.bool_workspace_set = self.clockify_api.workspace_id is not None if self.bool_workspace_set is False: return self.start_timer_check() - self.set_menu_visibility() def tray_exit(self, *_a, **_kw): @@ -85,23 +78,19 @@ class ClockifyModule( def get_plugin_paths(self): """Implementaton of IPluginPaths to get plugin paths.""" actions_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "launcher_actions" + os.path.dirname(os.path.abspath(__file__)), "launcher_actions" ) - return { - "actions": [actions_path] - } + return {"actions": [actions_path]} def get_ftrack_event_handler_paths(self): """Function for Ftrack module to add ftrack event handler paths.""" return { "user": [CLOCKIFY_FTRACK_USER_PATH], - "server": [CLOCKIFY_FTRACK_SERVER_PATH] + "server": [CLOCKIFY_FTRACK_SERVER_PATH], } def clockify_timer_stopped(self): self.bool_timer_run = False - # Call `ITimersManager` method self.timer_stopped() def start_timer_check(self): @@ -122,45 +111,44 @@ class ClockifyModule( def check_running(self): while self.bool_thread_check_running is True: bool_timer_run = False - if self.clockapi.get_in_progress() is not None: + if self.clockify_api.get_in_progress() is not None: bool_timer_run = True if self.bool_timer_run != bool_timer_run: if self.bool_timer_run is True: self.clockify_timer_stopped() elif self.bool_timer_run is False: - actual_timer = self.clockapi.get_in_progress() - if not actual_timer: + current_timer = self.clockify_api.get_in_progress() + if current_timer is None: + continue + current_proj_id = current_timer.get("projectId") + if not current_proj_id: continue - actual_proj_id = actual_timer["projectId"] - if not actual_proj_id: - continue - - project = self.clockapi.get_project_by_id(actual_proj_id) + project = self.clockify_api.get_project_by_id( + current_proj_id + ) if project and project.get("code") == 501: continue - project_name = project["name"] + project_name = project.get("name") - actual_timer_hierarchy = actual_timer["description"] - hierarchy_items = actual_timer_hierarchy.split("/") + current_timer_hierarchy = current_timer.get("description") + if not current_timer_hierarchy: + continue + hierarchy_items = current_timer_hierarchy.split("/") # Each pype timer must have at least 2 items! if len(hierarchy_items) < 2: continue + task_name = hierarchy_items[-1] hierarchy = hierarchy_items[:-1] - task_type = None - if len(actual_timer.get("tags", [])) > 0: - task_type = actual_timer["tags"][0].get("name") data = { "task_name": task_name, "hierarchy": hierarchy, "project_name": project_name, - "task_type": task_type } - # Call `ITimersManager` method self.timer_started(data) self.bool_timer_run = bool_timer_run @@ -184,6 +172,7 @@ class ClockifyModule( def tray_menu(self, parent_menu): # Menu for Tray App from qtpy import QtWidgets + menu = QtWidgets.QMenu("Clockify", parent_menu) menu.setProperty("submenu", "on") @@ -204,7 +193,9 @@ class ClockifyModule( parent_menu.addMenu(menu) def show_settings(self): - self.widget_settings.input_api_key.setText(self.clockapi.get_api_key()) + self.widget_settings.input_api_key.setText( + self.clockify_api.get_api_key() + ) self.widget_settings.show() def set_menu_visibility(self): @@ -218,72 +209,82 @@ class ClockifyModule( def timer_started(self, data): """Tell TimersManager that timer started.""" if self._timers_manager_module is not None: - self._timers_manager_module.timer_started(self._module.id, data) + self._timers_manager_module.timer_started(self.id, data) def timer_stopped(self): """Tell TimersManager that timer stopped.""" if self._timers_manager_module is not None: - self._timers_manager_module.timer_stopped(self._module.id) + self._timers_manager_module.timer_stopped(self.id) def stop_timer(self): """Called from TimersManager to stop timer.""" - self.clockapi.finish_time_entry() + self.clockify_api.finish_time_entry() - def start_timer(self, input_data): - """Called from TimersManager to start timer.""" - # If not api key is not entered then skip - if not self.clockapi.get_api_key(): - return - - actual_timer = self.clockapi.get_in_progress() - actual_timer_hierarchy = None - actual_project_id = None - if actual_timer is not None: - actual_timer_hierarchy = actual_timer.get("description") - actual_project_id = actual_timer.get("projectId") - - # Concatenate hierarchy and task to get description - desc_items = [val for val in input_data.get("hierarchy", [])] - desc_items.append(input_data["task_name"]) - description = "/".join(desc_items) - - # Check project existence - project_name = input_data["project_name"] - project_id = self.clockapi.get_project_id(project_name) + def _verify_project_exists(self, project_name): + project_id = self.clockify_api.get_project_id(project_name) if not project_id: - self.log.warning(( - "Project \"{}\" was not found in Clockify. Timer won't start." - ).format(project_name)) + self.log.warning( + 'Project "{}" was not found in Clockify. Timer won\'t start.' + ).format(project_name) if not self.MessageWidgetClass: return msg = ( - "Project \"{}\" is not" - " in Clockify Workspace \"{}\"." + 'Project "{}" is not' + ' in Clockify Workspace "{}".' "

Please inform your Project Manager." - ).format(project_name, str(self.clockapi.workspace_name)) + ).format(project_name, str(self.clockify_api.workspace_name)) self.message_widget = self.MessageWidgetClass( msg, "Clockify - Info Message" ) self.message_widget.closed.connect(self.on_message_widget_close) self.message_widget.show() + return False + return project_id + def start_timer(self, input_data): + """Called from TimersManager to start timer.""" + # If not api key is not entered then skip + if not self.clockify_api.get_api_key(): return - if ( - actual_timer is not None and - description == actual_timer_hierarchy and - project_id == actual_project_id - ): + task_name = input_data.get("task_name") + + # Concatenate hierarchy and task to get description + description_items = list(input_data.get("hierarchy", [])) + description_items.append(task_name) + description = "/".join(description_items) + + # Check project existence + project_name = input_data.get("project_name") + project_id = self._verify_project_exists(project_name) + if not project_id: return + # Setup timer tags tag_ids = [] - task_tag_id = self.clockapi.get_tag_id(input_data["task_type"]) + tag_name = input_data.get("task_type") + if not tag_name: + # no task_type found in the input data + # if the timer is restarted by idle time (bug?) + asset_name = input_data["hierarchy"][-1] + asset_doc = get_asset_by_name(project_name, asset_name) + task_info = asset_doc["data"]["tasks"][task_name] + tag_name = task_info.get("type", "") + if not tag_name: + self.log.info("No tag information found for the timer") + + task_tag_id = self.clockify_api.get_tag_id(tag_name) if task_tag_id is not None: tag_ids.append(task_tag_id) - self.clockapi.start_time_entry( - description, project_id, tag_ids=tag_ids + # Start timer + self.clockify_api.start_time_entry( + description, + project_id, + tag_ids=tag_ids, + workspace_id=self.clockify_api.workspace_id, + user_id=self.clockify_api.user_id, ) diff --git a/openpype/modules/clockify/constants.py b/openpype/modules/clockify/constants.py index 66f6cb899a..4574f91be1 100644 --- a/openpype/modules/clockify/constants.py +++ b/openpype/modules/clockify/constants.py @@ -9,4 +9,4 @@ CLOCKIFY_FTRACK_USER_PATH = os.path.join( ) ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"] -CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/" +CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/v1/" diff --git a/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py index c6b55947da..985cf49b97 100644 --- a/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -4,7 +4,7 @@ from openpype_modules.ftrack.lib import ServerAction from openpype_modules.clockify.clockify_api import ClockifyAPI -class SyncClocifyServer(ServerAction): +class SyncClockifyServer(ServerAction): '''Synchronise project names and task types.''' identifier = "clockify.sync.server" @@ -14,12 +14,12 @@ class SyncClocifyServer(ServerAction): role_list = ["Pypeclub", "Administrator", "project Manager"] def __init__(self, *args, **kwargs): - super(SyncClocifyServer, self).__init__(*args, **kwargs) + super(SyncClockifyServer, self).__init__(*args, **kwargs) workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") api_key = os.environ.get("CLOCKIFY_API_KEY") - self.clockapi = ClockifyAPI(api_key) - self.clockapi.set_workspace(workspace_name) + self.clockify_api = ClockifyAPI(api_key) + self.clockify_api.set_workspace(workspace_name) if api_key is None: modified_key = "None" else: @@ -48,13 +48,16 @@ class SyncClocifyServer(ServerAction): return True def launch(self, session, entities, event): - if self.clockapi.workspace_id is None: + self.clockify_api.set_api() + if self.clockify_api.workspace_id is None: return { "success": False, "message": "Clockify Workspace or API key are not set!" } - if self.clockapi.validate_workspace_perm() is False: + if not self.clockify_api.validate_workspace_permissions( + self.clockify_api.workspace_id, self.clockify_api.user_id + ): return { "success": False, "message": "Missing permissions for this action!" @@ -88,9 +91,9 @@ class SyncClocifyServer(ServerAction): task_type["name"] for task_type in task_types ] try: - clockify_projects = self.clockapi.get_projects() + clockify_projects = self.clockify_api.get_projects() if project_name not in clockify_projects: - response = self.clockapi.add_project(project_name) + response = self.clockify_api.add_project(project_name) if "id" not in response: self.log.warning( "Project \"{}\" can't be created. Response: {}".format( @@ -105,7 +108,7 @@ class SyncClocifyServer(ServerAction): ).format(project_name) } - clockify_workspace_tags = self.clockapi.get_tags() + clockify_workspace_tags = self.clockify_api.get_tags() for task_type_name in task_type_names: if task_type_name in clockify_workspace_tags: self.log.debug( @@ -113,7 +116,7 @@ class SyncClocifyServer(ServerAction): ) continue - response = self.clockapi.add_tag(task_type_name) + response = self.clockify_api.add_tag(task_type_name) if "id" not in response: self.log.warning( "Task \"{}\" can't be created. Response: {}".format( @@ -138,4 +141,4 @@ class SyncClocifyServer(ServerAction): def register(session, **kw): - SyncClocifyServer(session).register() + SyncClockifyServer(session).register() diff --git a/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py index a430791906..0e8cf6bd37 100644 --- a/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -3,7 +3,7 @@ from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.clockify.clockify_api import ClockifyAPI -class SyncClocifyLocal(BaseAction): +class SyncClockifyLocal(BaseAction): '''Synchronise project names and task types.''' #: Action identifier. @@ -18,9 +18,9 @@ class SyncClocifyLocal(BaseAction): icon = statics_icon("app_icons", "clockify-white.png") def __init__(self, *args, **kwargs): - super(SyncClocifyLocal, self).__init__(*args, **kwargs) + super(SyncClockifyLocal, self).__init__(*args, **kwargs) #: CLockifyApi - self.clockapi = ClockifyAPI() + self.clockify_api = ClockifyAPI() def discover(self, session, entities, event): if ( @@ -31,14 +31,18 @@ class SyncClocifyLocal(BaseAction): return False def launch(self, session, entities, event): - self.clockapi.set_api() - if self.clockapi.workspace_id is None: + self.clockify_api.set_api() + if self.clockify_api.workspace_id is None: return { "success": False, "message": "Clockify Workspace or API key are not set!" } - if self.clockapi.validate_workspace_perm() is False: + if ( + self.clockify_api.validate_workspace_permissions( + self.clockify_api.workspace_id, self.clockify_api.user_id) + is False + ): return { "success": False, "message": "Missing permissions for this action!" @@ -74,9 +78,9 @@ class SyncClocifyLocal(BaseAction): task_type["name"] for task_type in task_types ] try: - clockify_projects = self.clockapi.get_projects() + clockify_projects = self.clockify_api.get_projects() if project_name not in clockify_projects: - response = self.clockapi.add_project(project_name) + response = self.clockify_api.add_project(project_name) if "id" not in response: self.log.warning( "Project \"{}\" can't be created. Response: {}".format( @@ -91,7 +95,7 @@ class SyncClocifyLocal(BaseAction): ).format(project_name) } - clockify_workspace_tags = self.clockapi.get_tags() + clockify_workspace_tags = self.clockify_api.get_tags() for task_type_name in task_type_names: if task_type_name in clockify_workspace_tags: self.log.debug( @@ -99,7 +103,7 @@ class SyncClocifyLocal(BaseAction): ) continue - response = self.clockapi.add_tag(task_type_name) + response = self.clockify_api.add_tag(task_type_name) if "id" not in response: self.log.warning( "Task \"{}\" can't be created. Response: {}".format( @@ -121,4 +125,4 @@ class SyncClocifyLocal(BaseAction): def register(session, **kw): - SyncClocifyLocal(session).register() + SyncClockifyLocal(session).register() diff --git a/openpype/modules/clockify/launcher_actions/ClockifyStart.py b/openpype/modules/clockify/launcher_actions/ClockifyStart.py index 7663aecc31..4a653c1b8d 100644 --- a/openpype/modules/clockify/launcher_actions/ClockifyStart.py +++ b/openpype/modules/clockify/launcher_actions/ClockifyStart.py @@ -6,9 +6,9 @@ from openpype_modules.clockify.clockify_api import ClockifyAPI class ClockifyStart(LauncherAction): name = "clockify_start_timer" label = "Clockify - Start Timer" - icon = "clockify_icon" + icon = "app_icons/clockify.png" order = 500 - clockapi = ClockifyAPI() + clockify_api = ClockifyAPI() def is_compatible(self, session): """Return whether the action is compatible with the session""" @@ -17,23 +17,39 @@ class ClockifyStart(LauncherAction): return False def process(self, session, **kwargs): + self.clockify_api.set_api() + user_id = self.clockify_api.user_id + workspace_id = self.clockify_api.workspace_id project_name = session["AVALON_PROJECT"] asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] - description = asset_name - asset_doc = get_asset_by_name( - project_name, asset_name, fields=["data.parents"] - ) - if asset_doc is not None: - desc_items = asset_doc.get("data", {}).get("parents", []) - desc_items.append(asset_name) - desc_items.append(task_name) - description = "/".join(desc_items) - project_id = self.clockapi.get_project_id(project_name) - tag_ids = [] - tag_ids.append(self.clockapi.get_tag_id(task_name)) - self.clockapi.start_time_entry( - description, project_id, tag_ids=tag_ids + # fetch asset docs + asset_doc = get_asset_by_name(project_name, asset_name) + + # get task type to fill the timer tag + task_info = asset_doc["data"]["tasks"][task_name] + task_type = task_info["type"] + + # check if the task has hierarchy and fill the + parents_data = asset_doc["data"] + if parents_data is not None: + description_items = parents_data.get("parents", []) + description_items.append(asset_name) + description_items.append(task_name) + description = "/".join(description_items) + + project_id = self.clockify_api.get_project_id( + project_name, workspace_id + ) + tag_ids = [] + tag_name = task_type + tag_ids.append(self.clockify_api.get_tag_id(tag_name, workspace_id)) + self.clockify_api.start_time_entry( + description, + project_id, + tag_ids=tag_ids, + workspace_id=workspace_id, + user_id=user_id, ) diff --git a/openpype/modules/clockify/launcher_actions/ClockifySync.py b/openpype/modules/clockify/launcher_actions/ClockifySync.py index c346a1b4f6..cbd2519a04 100644 --- a/openpype/modules/clockify/launcher_actions/ClockifySync.py +++ b/openpype/modules/clockify/launcher_actions/ClockifySync.py @@ -3,20 +3,39 @@ from openpype_modules.clockify.clockify_api import ClockifyAPI from openpype.pipeline import LauncherAction -class ClockifySync(LauncherAction): +class ClockifyPermissionsCheckFailed(Exception): + """Timer start failed due to user permissions check. + Message should be self explanatory as traceback won't be shown. + """ + pass + + +class ClockifySync(LauncherAction): name = "sync_to_clockify" label = "Sync to Clockify" - icon = "clockify_white_icon" + icon = "app_icons/clockify-white.png" order = 500 - clockapi = ClockifyAPI() - have_permissions = clockapi.validate_workspace_perm() + clockify_api = ClockifyAPI() def is_compatible(self, session): - """Return whether the action is compatible with the session""" - return self.have_permissions + """Check if there's some projects to sync""" + try: + next(get_projects()) + return True + except StopIteration: + return False def process(self, session, **kwargs): + self.clockify_api.set_api() + workspace_id = self.clockify_api.workspace_id + user_id = self.clockify_api.user_id + if not self.clockify_api.validate_workspace_permissions( + workspace_id, user_id + ): + raise ClockifyPermissionsCheckFailed( + "Current CLockify user is missing permissions for this action!" + ) project_name = session.get("AVALON_PROJECT") or "" projects_to_sync = [] @@ -30,24 +49,28 @@ class ClockifySync(LauncherAction): task_types = project["config"]["tasks"].keys() projects_info[project["name"]] = task_types - clockify_projects = self.clockapi.get_projects() + clockify_projects = self.clockify_api.get_projects(workspace_id) for project_name, task_types in projects_info.items(): if project_name in clockify_projects: continue - response = self.clockapi.add_project(project_name) + response = self.clockify_api.add_project( + project_name, workspace_id + ) if "id" not in response: - self.log.error("Project {} can't be created".format( - project_name - )) + self.log.error( + "Project {} can't be created".format(project_name) + ) continue - clockify_workspace_tags = self.clockapi.get_tags() + clockify_workspace_tags = self.clockify_api.get_tags(workspace_id) for task_type in task_types: if task_type not in clockify_workspace_tags: - response = self.clockapi.add_tag(task_type) + response = self.clockify_api.add_tag( + task_type, workspace_id + ) if "id" not in response: - self.log.error('Task {} can\'t be created'.format( - task_type - )) + self.log.error( + "Task {} can't be created".format(task_type) + ) continue diff --git a/openpype/modules/clockify/widgets.py b/openpype/modules/clockify/widgets.py index 122b6212c0..8c28f38b6e 100644 --- a/openpype/modules/clockify/widgets.py +++ b/openpype/modules/clockify/widgets.py @@ -77,15 +77,15 @@ class MessageWidget(QtWidgets.QWidget): class ClockifySettings(QtWidgets.QWidget): - SIZE_W = 300 + SIZE_W = 500 SIZE_H = 130 loginSignal = QtCore.Signal(object, object, object) - def __init__(self, clockapi, optional=True): + def __init__(self, clockify_api, optional=True): super(ClockifySettings, self).__init__() - self.clockapi = clockapi + self.clockify_api = clockify_api self.optional = optional self.validated = False @@ -162,17 +162,17 @@ class ClockifySettings(QtWidgets.QWidget): def click_ok(self): api_key = self.input_api_key.text().strip() if self.optional is True and api_key == '': - self.clockapi.save_api_key(None) - self.clockapi.set_api(api_key) + self.clockify_api.save_api_key(None) + self.clockify_api.set_api(api_key) self.validated = False self._close_widget() return - validation = self.clockapi.validate_api_key(api_key) + validation = self.clockify_api.validate_api_key(api_key) if validation: - self.clockapi.save_api_key(api_key) - self.clockapi.set_api(api_key) + self.clockify_api.save_api_key(api_key) + self.clockify_api.set_api(api_key) self.validated = True self._close_widget() else: diff --git a/openpype/modules/deadline/plugins/publish/collect_pools.py b/openpype/modules/deadline/plugins/publish/collect_pools.py index 48130848d5..e221eb00ea 100644 --- a/openpype/modules/deadline/plugins/publish/collect_pools.py +++ b/openpype/modules/deadline/plugins/publish/collect_pools.py @@ -3,21 +3,60 @@ """ import pyblish.api +from openpype.lib import TextDef +from openpype.pipeline.publish import OpenPypePyblishPluginMixin -class CollectDeadlinePools(pyblish.api.InstancePlugin): +class CollectDeadlinePools(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): """Collect pools from instance if present, from Setting otherwise.""" order = pyblish.api.CollectorOrder + 0.420 label = "Collect Deadline Pools" - families = ["rendering", "render.farm", "renderFarm", "renderlayer"] + families = ["rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender"] primary_pool = None secondary_pool = None + @classmethod + def apply_settings(cls, project_settings, system_settings): + # deadline.publish.CollectDeadlinePools + settings = project_settings["deadline"]["publish"]["CollectDeadlinePools"] # noqa + cls.primary_pool = settings.get("primary_pool", None) + cls.secondary_pool = settings.get("secondary_pool", None) + def process(self, instance): + + attr_values = self.get_attr_values_from_data(instance.data) if not instance.data.get("primaryPool"): - instance.data["primaryPool"] = self.primary_pool or "none" + instance.data["primaryPool"] = ( + attr_values.get("primaryPool") or self.primary_pool or "none" + ) if not instance.data.get("secondaryPool"): - instance.data["secondaryPool"] = self.secondary_pool or "none" + instance.data["secondaryPool"] = ( + attr_values.get("secondaryPool") or self.secondary_pool or "none" # noqa + ) + + @classmethod + def get_attribute_defs(cls): + # TODO: Preferably this would be an enum for the user + # but the Deadline server URL can be dynamic and + # can be set per render instance. Since get_attribute_defs + # can't be dynamic unfortunately EnumDef isn't possible (yet?) + # pool_names = self.deadline_module.get_deadline_pools(deadline_url, + # self.log) + # secondary_pool_names = ["-"] + pool_names + + return [ + TextDef("primaryPool", + label="Primary Pool", + default=cls.primary_pool), + TextDef("secondaryPool", + label="Secondary Pool", + default=cls.secondary_pool) + ] diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index 038ee4fc03..bcf0850768 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -106,7 +106,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): # define chunk and priority chunk_size = instance.context.data.get("chunk") - if chunk_size == 0: + if not chunk_size: chunk_size = self.deadline_chunk_size # search for %02d pattern in name, and padding number diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 417a03de74..c728b6b9c7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -3,7 +3,15 @@ import getpass import copy import attr -from openpype.pipeline import legacy_io +from openpype.lib import ( + TextDef, + BoolDef, + NumberDef, +) +from openpype.pipeline import ( + legacy_io, + OpenPypePyblishPluginMixin +) from openpype.settings import get_project_settings from openpype.hosts.max.api.lib import ( get_current_renderer, @@ -22,7 +30,8 @@ class MaxPluginInfo(object): IgnoreInputs = attr.ib(default=True) -class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): +class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, + OpenPypePyblishPluginMixin): label = "Submit Render to Deadline" hosts = ["max"] @@ -31,14 +40,22 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): use_published = True priority = 50 - tile_priority = 50 chunk_size = 1 jobInfo = {} pluginInfo = {} group = None - deadline_pool = None - deadline_pool_secondary = None - framePerTask = 1 + + @classmethod + def apply_settings(cls, project_settings, system_settings): + settings = project_settings["deadline"]["publish"]["MaxSubmitDeadline"] # noqa + + # Take some defaults from settings + cls.use_published = settings.get("use_published", + cls.use_published) + cls.priority = settings.get("priority", + cls.priority) + cls.chuck_size = settings.get("chunk_size", cls.chunk_size) + cls.group = settings.get("group", cls.group) def get_job_info(self): job_info = DeadlineJobInfo(Plugin="3dsmax") @@ -49,11 +66,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): instance = self._instance context = instance.context - # Always use the original work file name for the Job name even when # rendering is done from the published Work File. The original work # file name is clearer because it can also have subversion strings, # etc. which are stripped for the published file. + src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) @@ -71,13 +88,13 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - job_info.ChunkSize = instance.data.get("chunkSize", 1) - job_info.Comment = context.data.get("comment") - job_info.Priority = instance.data.get("priority", self.priority) - job_info.FramesPerTask = instance.data.get("framesPerTask", 1) - if self.group: - job_info.Group = self.group + attr_values = self.get_attr_values_from_data(instance.data) + + job_info.ChunkSize = attr_values.get("chunkSize", 1) + job_info.Comment = context.data.get("comment") + job_info.Priority = attr_values.get("priority", self.priority) + job_info.Group = attr_values.get("group", self.group) # Add options from RenderGlobals render_globals = instance.data.get("renderGlobals", {}) @@ -216,3 +233,32 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): plugin_info.update(plugin_data) return job_info, plugin_info + + @classmethod + def get_attribute_defs(cls): + defs = super(MaxSubmitDeadline, cls).get_attribute_defs() + defs.extend([ + BoolDef("use_published", + default=cls.use_published, + label="Use Published Scene"), + + NumberDef("priority", + minimum=1, + maximum=250, + decimals=0, + default=cls.priority, + label="Priority"), + + NumberDef("chunkSize", + minimum=1, + maximum=50, + decimals=0, + default=cls.chunk_size, + label="Frame Per Task"), + + TextDef("group", + default=cls.group, + label="Group Name"), + ]) + + return defs diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 15025e47f2..062732c059 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -422,6 +422,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_job_info.Priority = instance.data.get( "tile_priority", self.tile_priority ) + assembly_job_info.TileJob = False pool = instance.context.data["project_settings"]["deadline"] pool = pool["publish"]["ProcessSubmittedJobOnFarm"]["deadline_pool"] @@ -450,15 +451,14 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): frame_assembly_job_info.ExtraInfo[0] = file_hash frame_assembly_job_info.ExtraInfo[1] = file frame_assembly_job_info.JobDependencies = tile_job_id + frame_assembly_job_info.Frames = frame # write assembly job config files - now = datetime.now() - config_file = os.path.join( output_dir, "{}_config_{}.txt".format( os.path.splitext(file)[0], - now.strftime("%Y_%m_%d_%H_%M_%S") + datetime.now().strftime("%Y_%m_%d_%H_%M_%S") ) ) try: @@ -469,6 +469,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): self.log.warning("Path is unreachable: " "`{}`".format(output_dir)) + assembly_plugin_info["ConfigFile"] = config_file + with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) print("ImageFileName={}".format(file), file=cf) @@ -477,25 +479,30 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): print("ImageHeight={}".format( instance.data.get("resolutionHeight")), file=cf) + with open(config_file, "a") as cf: + # Need to reverse the order of the y tiles, because image + # coordinates are calculated from bottom left corner. tiles = _format_tiles( file, 0, instance.data.get("tilesX"), instance.data.get("tilesY"), instance.data.get("resolutionWidth"), instance.data.get("resolutionHeight"), - payload_plugin_info["OutputFilePrefix"] + payload_plugin_info["OutputFilePrefix"], + reversed_y=True )[1] for k, v in sorted(tiles.items()): print("{}={}".format(k, v), file=cf) - payload = self.assemble_payload( - job_info=frame_assembly_job_info, - plugin_info=assembly_plugin_info.copy(), - # todo: aux file transfers don't work with deadline webservice - # add config file as job auxFile - # aux_files=[config_file] + assembly_payloads.append( + self.assemble_payload( + job_info=frame_assembly_job_info, + plugin_info=assembly_plugin_info.copy(), + # This would fail if the client machine and webserice are + # using different storage paths. + aux_files=[config_file] + ) ) - assembly_payloads.append(payload) # Submit assembly jobs assembly_job_ids = [] @@ -505,6 +512,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "submitting assembly job {} of {}".format(i + 1, num_assemblies) ) + self.log.info(payload) assembly_job_id = self.submit(payload) assembly_job_ids.append(assembly_job_id) @@ -764,8 +772,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): def _format_tiles( - filename, index, tiles_x, tiles_y, - width, height, prefix): + filename, + index, + tiles_x, + tiles_y, + width, + height, + prefix, + reversed_y=False +): """Generate tile entries for Deadline tile job. Returns two dictionaries - one that can be directly used in Deadline @@ -802,6 +817,7 @@ def _format_tiles( width (int): Width resolution of final image. height (int): Height resolution of final image. prefix (str): Image prefix. + reversed_y (bool): Reverses the order of the y tiles. Returns: (dict, dict): Tuple of two dictionaries - first can be used to @@ -824,12 +840,16 @@ def _format_tiles( cfg["TilesCropped"] = "False" tile = 0 + range_y = range(1, tiles_y + 1) + reversed_y_range = list(reversed(range_y)) for tile_x in range(1, tiles_x + 1): - for tile_y in reversed(range(1, tiles_y + 1)): + for i, tile_y in enumerate(range_y): + tile_y_index = tile_y + if reversed_y: + tile_y_index = reversed_y_range[i] + tile_prefix = "_tile_{}x{}_{}x{}_".format( - tile_x, tile_y, - tiles_x, - tiles_y + tile_x, tile_y_index, tiles_x, tiles_y ) new_filename = "{}/{}{}".format( @@ -844,11 +864,14 @@ def _format_tiles( right = (tile_x * w_space) - 1 # Job info - out["JobInfo"]["OutputFilename{}Tile{}".format(index, tile)] = new_filename # noqa: E501 + key = "OutputFilename{}".format(index) + out["JobInfo"][key] = new_filename # Plugin Info - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + key = "RegionPrefix{}".format(str(tile)) + out["PluginInfo"][key] = "/{}".format( + tile_prefix + ).join(prefix.rsplit("/", 1)) out["PluginInfo"]["RegionTop{}".format(tile)] = top out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom out["PluginInfo"]["RegionLeft{}".format(tile)] = left diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index aff34c7e4a..cc069cf51a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -32,7 +32,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, label = "Submit Nuke to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["nuke"] - families = ["render", "prerender.farm"] + families = ["render", "prerender"] optional = True targets = ["local"] @@ -80,6 +80,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, ] def process(self, instance): + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + instance.data["attributeValues"] = self.get_attr_values_from_data( instance.data) @@ -168,10 +172,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, resp.json()["_id"]) # redefinition of families - if "render.farm" in families: + if "render" in instance.data["family"]: instance.data['family'] = 'write' families.insert(0, "render2d") - elif "prerender.farm" in families: + elif "prerender" in instance.data["family"]: instance.data['family'] = 'write' families.insert(0, "prerender") instance.data["families"] = families diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 53c09ad22f..0d0698c21f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -756,6 +756,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instance (pyblish.api.Instance): Instance data. """ + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + data = instance.data.copy() context = instance.context self.context = context diff --git a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py index 78eed17c98..7c8ab62d4d 100644 --- a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -17,10 +17,18 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, label = "Validate Deadline Pools" order = pyblish.api.ValidatorOrder - families = ["rendering", "render.farm", "renderFarm", "renderlayer"] + families = ["rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender"] optional = True def process(self, instance): + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + # get default deadline webservice url from deadline module deadline_url = instance.context.data["defaultDeadline"] self.log.info("deadline_url::{}".format(deadline_url)) diff --git a/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index f34f71d213..ff4be677e7 100644 --- a/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/openpype/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -68,8 +68,15 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): # files to be in the folder that we might not want to use. missing = expected_files - existing_files if missing: - raise RuntimeError("Missing expected files: {}".format( - sorted(missing))) + raise RuntimeError( + "Missing expected files: {}\n" + "Expected files: {}\n" + "Existing files: {}".format( + sorted(missing), + sorted(expected_files), + sorted(existing_files) + ) + ) def _get_frame_list(self, original_job_id): """Returns list of frame ranges from all render job. diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 20a58c9131..15226bb773 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -362,11 +362,11 @@ def inject_openpype_environment(deadlinePlugin): args_str = subprocess.list2cmdline(args) print(">>> Executing: {} {}".format(exe, args_str)) - process = ProcessUtils.SpawnProcess( - exe, args_str, os.path.dirname(exe) + process_exitcode = deadlinePlugin.RunProcess( + exe, args_str, os.path.dirname(exe), -1 ) - ProcessUtils.WaitForExit(process, -1) - if process.ExitCode != 0: + + if process_exitcode != 0: raise RuntimeError( "Failed to run OpenPype process to extract environments." ) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 861f16518c..b51daffbc8 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -16,6 +16,10 @@ from Deadline.Scripting import ( FileUtils, RepositoryUtils, SystemUtils) +version_major = 1 +version_minor = 0 +version_patch = 0 +version_string = "{}.{}.{}".format(version_major, version_minor, version_patch) STRING_TAGS = { "format" } @@ -264,6 +268,7 @@ class OpenPypeTileAssembler(DeadlinePlugin): def initialize_process(self): """Initialization.""" + self.LogInfo("Plugin version: {}".format(version_string)) self.SingleFramesOnly = True self.StdoutHandling = True self.renderer = self.GetPluginInfoEntryWithDefault( @@ -320,12 +325,7 @@ class OpenPypeTileAssembler(DeadlinePlugin): output_file = data["ImageFileName"] output_file = RepositoryUtils.CheckPathMapping(output_file) output_file = self.process_path(output_file) - """ - _, ext = os.path.splitext(output_file) - if "exr" not in ext: - self.FailRender( - "[{}] Only EXR format is supported for now.".format(ext)) - """ + tile_info = [] for tile in range(int(data["TileCount"])): tile_info.append({ @@ -336,11 +336,6 @@ class OpenPypeTileAssembler(DeadlinePlugin): "width": int(data["Tile{}Width".format(tile)]) }) - # FFMpeg doesn't support tile coordinates at the moment. - # arguments = self.tile_completer_ffmpeg_args( - # int(data["ImageWidth"]), int(data["ImageHeight"]), - # tile_info, output_file) - arguments = self.tile_oiio_args( int(data["ImageWidth"]), int(data["ImageHeight"]), tile_info, output_file) @@ -362,20 +357,20 @@ class OpenPypeTileAssembler(DeadlinePlugin): def pre_render_tasks(self): """Load config file and do remapping.""" self.LogInfo("OpenPype Tile Assembler starting...") - scene_filename = self.GetDataFilename() + config_file = self.GetPluginInfoEntry("ConfigFile") temp_scene_directory = self.CreateTempDirectory( "thread" + str(self.GetThreadNumber())) - temp_scene_filename = Path.GetFileName(scene_filename) + temp_scene_filename = Path.GetFileName(config_file) self.config_file = Path.Combine( temp_scene_directory, temp_scene_filename) if SystemUtils.IsRunningOnWindows(): RepositoryUtils.CheckPathMappingInFileAndReplaceSeparator( - scene_filename, self.config_file, "/", "\\") + config_file, self.config_file, "/", "\\") else: RepositoryUtils.CheckPathMappingInFileAndReplaceSeparator( - scene_filename, self.config_file, "\\", "/") + config_file, self.config_file, "\\", "/") os.chmod(self.config_file, os.stat(self.config_file).st_mode) def post_render_tasks(self): @@ -459,75 +454,3 @@ class OpenPypeTileAssembler(DeadlinePlugin): args.append(output_path) return args - - def tile_completer_ffmpeg_args( - self, output_width, output_height, tiles_info, output_path): - """Generate ffmpeg arguments for tile assembly. - - Expected inputs are tiled images. - - Args: - output_width (int): Width of output image. - output_height (int): Height of output image. - tiles_info (list): List of tile items, each item must be - dictionary with `filepath`, `pos_x` and `pos_y` keys - representing path to file and x, y coordinates on output - image where top-left point of tile item should start. - output_path (str): Path to file where should be output stored. - - Returns: - (list): ffmpeg arguments. - - """ - previous_name = "base" - ffmpeg_args = [] - filter_complex_strs = [] - - filter_complex_strs.append("nullsrc=size={}x{}[{}]".format( - output_width, output_height, previous_name - )) - - new_tiles_info = {} - for idx, tile_info in enumerate(tiles_info): - # Add input and store input index - filepath = tile_info["filepath"] - ffmpeg_args.append("-i \"{}\"".format(filepath.replace("\\", "/"))) - - # Prepare initial filter complex arguments - index_name = "input{}".format(idx) - filter_complex_strs.append( - "[{}]setpts=PTS-STARTPTS[{}]".format(idx, index_name) - ) - tile_info["index"] = idx - new_tiles_info[index_name] = tile_info - - # Set frames to 1 - ffmpeg_args.append("-frames 1") - - # Concatenation filter complex arguments - global_index = 1 - total_index = len(new_tiles_info) - for index_name, tile_info in new_tiles_info.items(): - item_str = ( - "[{previous_name}][{index_name}]overlay={pos_x}:{pos_y}" - ).format( - previous_name=previous_name, - index_name=index_name, - pos_x=tile_info["pos_x"], - pos_y=tile_info["pos_y"] - ) - new_previous = "tmp{}".format(global_index) - if global_index != total_index: - item_str += "[{}]".format(new_previous) - filter_complex_strs.append(item_str) - previous_name = new_previous - global_index += 1 - - joined_parts = ";".join(filter_complex_strs) - filter_complex_str = "-filter_complex \"{}\"".format(joined_parts) - - ffmpeg_args.append(filter_complex_str) - ffmpeg_args.append("-y") - ffmpeg_args.append("\"{}\"".format(output_path)) - - return ffmpeg_args diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index ead647b41d..be1d3ff920 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -44,7 +44,7 @@ class AddonSettingsDef(JsonFilesSettingsDef): class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): - """This Addon has defined it's settings and interface. + """This Addon has defined its settings and interface. This example has system settings with an enabled option. And use few other interfaces: diff --git a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py index 1209375f82..a698195c59 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py @@ -9,7 +9,7 @@ from openpype_modules.ftrack.lib import ( class PushHierValuesToNonHier(ServerAction): - """Action push hierarchical custom attribute values to non hierarchical. + """Action push hierarchical custom attribute values to non-hierarchical. Hierarchical value is also pushed to their task entities. @@ -119,17 +119,109 @@ class PushHierValuesToNonHier(ServerAction): self.join_query_keys(object_ids) )).all() - output = {} + attrs_by_obj_id = collections.defaultdict(list) hiearchical = [] for attr in attrs: if attr["is_hierarchical"]: hiearchical.append(attr) continue obj_id = attr["object_type_id"] - if obj_id not in output: - output[obj_id] = [] - output[obj_id].append(attr) - return output, hiearchical + attrs_by_obj_id[obj_id].append(attr) + return attrs_by_obj_id, hiearchical + + def query_attr_value( + self, + session, + hier_attrs, + attrs_by_obj_id, + dst_object_type_ids, + task_entity_ids, + non_task_entity_ids, + parent_id_by_entity_id + ): + all_non_task_ids_with_parents = set() + for entity_id in non_task_entity_ids: + all_non_task_ids_with_parents.add(entity_id) + _entity_id = entity_id + while True: + parent_id = parent_id_by_entity_id.get(_entity_id) + if ( + parent_id is None + or parent_id in all_non_task_ids_with_parents + ): + break + all_non_task_ids_with_parents.add(parent_id) + _entity_id = parent_id + + all_entity_ids = ( + set(all_non_task_ids_with_parents) + | set(task_entity_ids) + ) + attr_ids = {attr["id"] for attr in hier_attrs} + for obj_id in dst_object_type_ids: + attrs = attrs_by_obj_id.get(obj_id) + if attrs is not None: + for attr in attrs: + attr_ids.add(attr["id"]) + + real_values_by_entity_id = { + entity_id: {} + for entity_id in all_entity_ids + } + + attr_values = query_custom_attributes( + session, attr_ids, all_entity_ids, True + ) + for item in attr_values: + entity_id = item["entity_id"] + attr_id = item["configuration_id"] + real_values_by_entity_id[entity_id][attr_id] = item["value"] + + # Fill hierarchical values + hier_attrs_key_by_id = { + hier_attr["id"]: hier_attr + for hier_attr in hier_attrs + } + hier_values_per_entity_id = {} + for entity_id in all_non_task_ids_with_parents: + real_values = real_values_by_entity_id[entity_id] + hier_values_per_entity_id[entity_id] = {} + for attr_id, attr in hier_attrs_key_by_id.items(): + key = attr["key"] + hier_values_per_entity_id[entity_id][key] = ( + real_values.get(attr_id) + ) + + output = {} + for entity_id in non_task_entity_ids: + output[entity_id] = {} + for attr in hier_attrs_key_by_id.values(): + key = attr["key"] + value = hier_values_per_entity_id[entity_id][key] + tried_ids = set() + if value is None: + tried_ids.add(entity_id) + _entity_id = entity_id + while value is None: + parent_id = parent_id_by_entity_id.get(_entity_id) + if not parent_id: + break + value = hier_values_per_entity_id[parent_id][key] + if value is not None: + break + _entity_id = parent_id + tried_ids.add(parent_id) + + if value is None: + value = attr["default"] + + if value is not None: + for ent_id in tried_ids: + hier_values_per_entity_id[ent_id][key] = value + + output[entity_id][key] = value + + return real_values_by_entity_id, output def propagate_values(self, session, event, selected_entities): ftrack_settings = self.get_ftrack_settings( @@ -156,29 +248,24 @@ class PushHierValuesToNonHier(ServerAction): } task_object_type = object_types_by_low_name["task"] - destination_object_types = [task_object_type] + dst_object_type_ids = {task_object_type["id"]} for ent_type in interest_entity_types: obj_type = object_types_by_low_name.get(ent_type) - if obj_type and obj_type not in destination_object_types: - destination_object_types.append(obj_type) - - destination_object_type_ids = set( - obj_type["id"] - for obj_type in destination_object_types - ) + if obj_type: + dst_object_type_ids.add(obj_type["id"]) interest_attributes = action_settings["interest_attributes"] # Find custom attributes definitions attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, destination_object_type_ids, interest_attributes + session, dst_object_type_ids, interest_attributes ) # Filter destination object types if they have any object specific # custom attribute - for obj_id in tuple(destination_object_type_ids): + for obj_id in tuple(dst_object_type_ids): if obj_id not in attrs_by_obj_id: - destination_object_type_ids.remove(obj_id) + dst_object_type_ids.remove(obj_id) - if not destination_object_type_ids: + if not dst_object_type_ids: # TODO report that there are not matching custom attributes return { "success": True, @@ -192,14 +279,14 @@ class PushHierValuesToNonHier(ServerAction): session, selected_ids, project_entity, - destination_object_type_ids + dst_object_type_ids ) self.log.debug("Preparing whole project hierarchy by ids.") entities_by_obj_id = { obj_id: [] - for obj_id in destination_object_type_ids + for obj_id in dst_object_type_ids } self.log.debug("Filtering Task entities.") @@ -223,10 +310,16 @@ class PushHierValuesToNonHier(ServerAction): "message": "Nothing to do in your selection." } - self.log.debug("Getting Hierarchical custom attribute values parents.") - hier_values_by_entity_id = self.get_hier_values( + self.log.debug("Getting Custom attribute values.") + ( + real_values_by_entity_id, + hier_values_by_entity_id + ) = self.query_attr_value( session, hier_attrs, + attrs_by_obj_id, + dst_object_type_ids, + task_entity_ids, non_task_entity_ids, parent_id_by_entity_id ) @@ -237,7 +330,8 @@ class PushHierValuesToNonHier(ServerAction): hier_attrs, task_entity_ids, hier_values_by_entity_id, - parent_id_by_entity_id + parent_id_by_entity_id, + real_values_by_entity_id ) self.log.debug("Setting values to entities themselves.") @@ -245,7 +339,8 @@ class PushHierValuesToNonHier(ServerAction): session, entities_by_obj_id, attrs_by_obj_id, - hier_values_by_entity_id + hier_values_by_entity_id, + real_values_by_entity_id ) return True @@ -322,112 +417,64 @@ class PushHierValuesToNonHier(ServerAction): return parent_id_by_entity_id, filtered_entities - def get_hier_values( - self, - session, - hier_attrs, - focus_entity_ids, - parent_id_by_entity_id - ): - all_ids_with_parents = set() - for entity_id in focus_entity_ids: - all_ids_with_parents.add(entity_id) - _entity_id = entity_id - while True: - parent_id = parent_id_by_entity_id.get(_entity_id) - if ( - not parent_id - or parent_id in all_ids_with_parents - ): - break - all_ids_with_parents.add(parent_id) - _entity_id = parent_id - - hier_attr_ids = tuple(hier_attr["id"] for hier_attr in hier_attrs) - hier_attrs_key_by_id = { - hier_attr["id"]: hier_attr["key"] - for hier_attr in hier_attrs - } - - values_per_entity_id = {} - for entity_id in all_ids_with_parents: - values_per_entity_id[entity_id] = {} - for key in hier_attrs_key_by_id.values(): - values_per_entity_id[entity_id][key] = None - - values = query_custom_attributes( - session, hier_attr_ids, all_ids_with_parents, True - ) - for item in values: - entity_id = item["entity_id"] - key = hier_attrs_key_by_id[item["configuration_id"]] - - values_per_entity_id[entity_id][key] = item["value"] - - output = {} - for entity_id in focus_entity_ids: - output[entity_id] = {} - for key in hier_attrs_key_by_id.values(): - value = values_per_entity_id[entity_id][key] - tried_ids = set() - if value is None: - tried_ids.add(entity_id) - _entity_id = entity_id - while value is None: - parent_id = parent_id_by_entity_id.get(_entity_id) - if not parent_id: - break - value = values_per_entity_id[parent_id][key] - if value is not None: - break - _entity_id = parent_id - tried_ids.add(parent_id) - - if value is not None: - for ent_id in tried_ids: - values_per_entity_id[ent_id][key] = value - - output[entity_id][key] = value - return output - def set_task_attr_values( self, session, hier_attrs, task_entity_ids, hier_values_by_entity_id, - parent_id_by_entity_id + parent_id_by_entity_id, + real_values_by_entity_id ): hier_attr_id_by_key = { attr["key"]: attr["id"] for attr in hier_attrs } + filtered_task_ids = set() for task_id in task_entity_ids: - parent_id = parent_id_by_entity_id.get(task_id) or {} + parent_id = parent_id_by_entity_id.get(task_id) parent_values = hier_values_by_entity_id.get(parent_id) - if not parent_values: - continue + if parent_values: + filtered_task_ids.add(task_id) + if not filtered_task_ids: + return + + for task_id in filtered_task_ids: + parent_id = parent_id_by_entity_id[task_id] + parent_values = hier_values_by_entity_id[parent_id] hier_values_by_entity_id[task_id] = {} + real_task_attr_values = real_values_by_entity_id[task_id] for key, value in parent_values.items(): hier_values_by_entity_id[task_id][key] = value + if value is None: + continue + configuration_id = hier_attr_id_by_key[key] _entity_key = collections.OrderedDict([ ("configuration_id", configuration_id), ("entity_id", task_id) ]) - - session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", + op = None + if configuration_id not in real_task_attr_values: + op = ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + _entity_key, + {"value": value} + ) + elif real_task_attr_values[configuration_id] != value: + op = ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", _entity_key, "value", - ftrack_api.symbol.NOT_SET, + real_task_attr_values[configuration_id], value ) - ) - if len(session.recorded_operations) > 100: - session.commit() + + if op is not None: + session.recorded_operations.push(op) + if len(session.recorded_operations) > 100: + session.commit() session.commit() @@ -436,39 +483,68 @@ class PushHierValuesToNonHier(ServerAction): session, entities_by_obj_id, attrs_by_obj_id, - hier_values_by_entity_id + hier_values_by_entity_id, + real_values_by_entity_id ): + """Push values from hierarchical custom attributes to non-hierarchical. + + Args: + session (ftrack_api.Sessison): Session which queried entities, + values and which is used for change propagation. + entities_by_obj_id (dict[str, list[str]]): TypedContext + ftrack entity ids where the attributes are propagated by their + object ids. + attrs_by_obj_id (dict[str, ftrack_api.Entity]): Objects of + 'CustomAttributeConfiguration' by their ids. + hier_values_by_entity_id (doc[str, dict[str, Any]]): Attribute + values by entity id and by their keys. + real_values_by_entity_id (doc[str, dict[str, Any]]): Real attribute + values of entities. + """ + for object_id, entity_ids in entities_by_obj_id.items(): attrs = attrs_by_obj_id.get(object_id) if not attrs or not entity_ids: continue - for attr in attrs: - for entity_id in entity_ids: - value = ( - hier_values_by_entity_id - .get(entity_id, {}) - .get(attr["key"]) - ) + for entity_id in entity_ids: + real_values = real_values_by_entity_id.get(entity_id) + hier_values = hier_values_by_entity_id.get(entity_id) + if hier_values is None: + continue + + for attr in attrs: + attr_id = attr["id"] + attr_key = attr["key"] + value = hier_values.get(attr_key) if value is None: continue _entity_key = collections.OrderedDict([ - ("configuration_id", attr["id"]), + ("configuration_id", attr_id), ("entity_id", entity_id) ]) - session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", + op = None + if attr_id not in real_values: + op = ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + _entity_key, + {"value": value} + ) + elif real_values[attr_id] != value: + op = ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", _entity_key, "value", - ftrack_api.symbol.NOT_SET, + real_values[attr_id], value ) - ) - if len(session.recorded_operations) > 100: - session.commit() + + if op is not None: + session.recorded_operations.push(op) + if len(session.recorded_operations) > 100: + session.commit() session.commit() diff --git a/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py index dc76920a57..0f10145c06 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py @@ -1,6 +1,6 @@ import collections -import datetime import copy +from typing import Any import ftrack_api from openpype_modules.ftrack.lib import ( @@ -9,13 +9,30 @@ from openpype_modules.ftrack.lib import ( ) -class PushFrameValuesToTaskEvent(BaseEvent): +class PushHierValuesToNonHierEvent(BaseEvent): + """Push value changes between hierarchical and non-hierarchical attributes. + + Changes of non-hierarchical attributes are pushed to hierarchical and back. + The attributes must have same definition of custom attribute. + + Handler does not handle changes of hierarchical parents. So if entity does + not have explicitly set value of hierarchical attribute and any parent + would change it the change would not be propagated. + + The handler also push the value to task entity on task creation + and movement. To push values between hierarchical & non-hierarchical + add 'Task' to entity types in settings. + + Todos: + Task attribute values push on create/move should be possible to + enabled by settings. + """ + # Ignore event handler by default cust_attrs_query = ( "select id, key, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where key in ({}) and" - " (object_type_id in ({}) or is_hierarchical is true)" + " where key in ({})" ) _cached_task_object_id = None @@ -26,35 +43,35 @@ class PushFrameValuesToTaskEvent(BaseEvent): settings_key = "sync_hier_entity_attributes" - def session_user_id(self, session): - if self._cached_user_id is None: - user = session.query( - "User where username is \"{}\"".format(session.api_user) - ).one() - self._cached_user_id = user["id"] - return self._cached_user_id + def filter_entities_info( + self, event: ftrack_api.event.base.Event + ) -> dict[str, list[dict[str, Any]]]: + """Basic entities filter info we care about. - def launch(self, session, event): - filtered_entities_info = self.filter_entities_info(event) - if not filtered_entities_info: - return + This filtering is first of many filters. This does not query anything + from ftrack nor use settings. - for project_id, entities_info in filtered_entities_info.items(): - self.process_by_project(session, event, project_id, entities_info) + Args: + event (ftrack_api.event.base.Event): Ftrack event with update + information. + + Returns: + dict[str, list[dict[str, Any]]]: Filtered entity changes by + project id. + """ - def filter_entities_info(self, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") if not entities_info: return - entities_info_by_project_id = {} + entities_info_by_project_id = collections.defaultdict(list) for entity_info in entities_info: - # Care only about tasks - if entity_info.get("entityType") != "task": + # Ignore removed entities + if entity_info.get("action") == "remove": continue - # Care only about changes of status + # Care only about information with changes of entities changes = entity_info.get("changes") if not changes: continue @@ -69,367 +86,287 @@ class PushFrameValuesToTaskEvent(BaseEvent): if project_id is None: continue - # Skip `Task` entity type if parent didn't change - if entity_info["entity_type"].lower() == "task": - if ( - "parent_id" not in changes - or changes["parent_id"]["new"] is None - ): - continue - - if project_id not in entities_info_by_project_id: - entities_info_by_project_id[project_id] = [] entities_info_by_project_id[project_id].append(entity_info) return entities_info_by_project_id - def process_by_project(self, session, event, project_id, entities_info): - project_name = self.get_project_name_from_event( + def _get_attrs_configurations(self, session, interest_attributes): + """Get custom attribute configurations by name. + + Args: + session (ftrack_api.Session): Ftrack sesson. + interest_attributes (list[str]): Names of custom attributes + that should be synchronized. + + Returns: + tuple[dict[str, list], list]: Attributes by object id and + hierarchical attributes. + """ + + attrs = session.query(self.cust_attrs_query.format( + self.join_query_keys(interest_attributes) + )).all() + + attrs_by_obj_id = collections.defaultdict(list) + hier_attrs = [] + for attr in attrs: + if attr["is_hierarchical"]: + hier_attrs.append(attr) + continue + obj_id = attr["object_type_id"] + attrs_by_obj_id[obj_id].append(attr) + return attrs_by_obj_id, hier_attrs + + def _get_handler_project_settings( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str + ) -> tuple[set[str], set[str]]: + """Get handler settings based on the project. + + Args: + session (ftrack_api.Session): Ftrack session. + event (ftrack_api.event.base.Event): Ftrack event which triggered + the changes. + project_id (str): Project id where the current changes are handled. + + Returns: + tuple[set[str], set[str]]: Attribute names we care about and + entity types we care about. + """ + + project_name: str = self.get_project_name_from_event( session, event, project_id ) # Load settings - project_settings = self.get_project_settings_from_event( - event, project_name + project_settings: dict[str, Any] = ( + self.get_project_settings_from_event(event, project_name) ) # Load status mapping from presets - event_settings = ( + event_settings: dict[str, Any] = ( project_settings ["ftrack"] ["events"] - ["sync_hier_entity_attributes"] + [self.settings_key] ) # Skip if event is not enabled if not event_settings["enabled"]: self.log.debug("Project \"{}\" has disabled {}".format( project_name, self.__class__.__name__ )) - return + return set(), set() - interest_attributes = event_settings["interest_attributes"] + interest_attributes: list[str] = event_settings["interest_attributes"] if not interest_attributes: self.log.info(( "Project \"{}\" does not have filled 'interest_attributes'," " skipping." )) - return - interest_entity_types = event_settings["interest_entity_types"] + + interest_entity_types: list[str] = ( + event_settings["interest_entity_types"]) if not interest_entity_types: self.log.info(( "Project \"{}\" does not have filled 'interest_entity_types'," " skipping." )) - return - interest_attributes = set(interest_attributes) - interest_entity_types = set(interest_entity_types) + # Unify possible issues from settings ('Asset Build' -> 'assetbuild') + interest_entity_types: set[str] = { + entity_type.replace(" ", "").lower() + for entity_type in interest_entity_types + } + return set(interest_attributes), interest_entity_types - # Separate value changes and task parent changes - _entities_info = [] - added_entities = [] - added_entity_ids = set() - task_parent_changes = [] + def _entities_filter_by_settings( + self, + entities_info: list[dict[str, Any]], + interest_attributes: set[str], + interest_entity_types: set[str] + ): + new_entities_info = [] for entity_info in entities_info: - if entity_info["entity_type"].lower() == "task": - task_parent_changes.append(entity_info) - elif entity_info.get("action") == "add": - added_entities.append(entity_info) - added_entity_ids.add(entity_info["entityId"]) - else: - _entities_info.append(entity_info) - entities_info = _entities_info + entity_type_low = entity_info["entity_type"].lower() - # Filter entities info with changes - interesting_data, changed_keys_by_object_id = self.filter_changes( - session, event, entities_info, interest_attributes - ) - self.interesting_data_for_added( - session, - added_entities, - interest_attributes, - interesting_data, - changed_keys_by_object_id - ) - if not interesting_data and not task_parent_changes: - return + changes = entity_info["changes"] + # SPECIAL CASE: Capture changes of task created/moved under + # interested entity type + if ( + entity_type_low == "task" + and "parent_id" in changes + ): + # Direct parent is always second item in 'parents' and 'Task' + # must have at least one parent + parent_info = entity_info["parents"][1] + parent_entity_type = ( + parent_info["entity_type"] + .replace(" ", "") + .lower() + ) + if parent_entity_type in interest_entity_types: + new_entities_info.append(entity_info) + continue - # Prepare object types - object_types = session.query("select id, name from ObjectType").all() - object_types_by_name = {} - for object_type in object_types: - name_low = object_type["name"].lower() - object_types_by_name[name_low] = object_type + # Skip if entity type is not enabled for attr value sync + if entity_type_low not in interest_entity_types: + continue - # NOTE it would be nice to check if `interesting_data` do not contain - # value changs of tasks that were created or moved - # - it is a complex way how to find out - if interesting_data: - self.process_attribute_changes( - session, - object_types_by_name, - interesting_data, - changed_keys_by_object_id, - interest_entity_types, - interest_attributes, - added_entity_ids - ) + valid_attr_change = entity_info.get("action") == "add" + for attr_key in interest_attributes: + if valid_attr_change: + break - if task_parent_changes: - self.process_task_parent_change( - session, object_types_by_name, task_parent_changes, - interest_entity_types, interest_attributes - ) + if attr_key not in changes: + continue - def process_task_parent_change( + if changes[attr_key]["new"] is not None: + valid_attr_change = True + + if not valid_attr_change: + continue + + new_entities_info.append(entity_info) + + return new_entities_info + + def propagate_attribute_changes( self, session, - object_types_by_name, - task_parent_changes, - interest_entity_types, - interest_attributes + interest_attributes, + entities_info, + attrs_by_obj_id, + hier_attrs, + real_values_by_entity_id, + hier_values_by_entity_id, ): - """Push custom attribute values if task parent has changed. + hier_attr_ids_by_key = { + attr["key"]: attr["id"] + for attr in hier_attrs + } + filtered_interest_attributes = { + attr_name + for attr_name in interest_attributes + if attr_name in hier_attr_ids_by_key + } + attrs_keys_by_obj_id = {} + for obj_id, attrs in attrs_by_obj_id.items(): + attrs_keys_by_obj_id[obj_id] = { + attr["key"]: attr["id"] + for attr in attrs + } - Parent is changed if task is created or if is moved under different - entity. We don't care about all task changes only about those that - have it's parent in interest types (from settings). + op_changes = [] + for entity_info in entities_info: + entity_id = entity_info["entityId"] + obj_id = entity_info["objectTypeId"] + # Skip attributes sync if does not have object specific custom + # attribute + if obj_id not in attrs_keys_by_obj_id: + continue + attr_keys = attrs_keys_by_obj_id[obj_id] + real_values = real_values_by_entity_id[entity_id] + hier_values = hier_values_by_entity_id[entity_id] - Tasks hierarchical value should be unset or set based on parents - real hierarchical value and non hierarchical custom attribute value - should be set to hierarchical value. - """ - - # Store task ids which were created or moved under parent with entity - # type defined in settings (interest_entity_types). - task_ids = set() - # Store parent ids of matching task ids - matching_parent_ids = set() - # Store all entity ids of all entities to be able query hierarchical - # values. - whole_hierarchy_ids = set() - # Store parent id of each entity id - parent_id_by_entity_id = {} - for entity_info in task_parent_changes: - # Ignore entities with less parents than 2 - # NOTE entity itself is also part of "parents" value - parents = entity_info.get("parents") or [] - if len(parents) < 2: + changes = copy.deepcopy(entity_info["changes"]) + obj_id_attr_keys = { + attr_key + for attr_key in filtered_interest_attributes + if attr_key in attr_keys + } + if not obj_id_attr_keys: continue - parent_info = parents[1] - # Check if parent has entity type we care about. - if parent_info["entity_type"] not in interest_entity_types: - continue + value_by_key = {} + is_new_entity = entity_info.get("action") == "add" + for attr_key in obj_id_attr_keys: + if ( + attr_key in changes + and changes[attr_key]["new"] is not None + ): + value_by_key[attr_key] = changes[attr_key]["new"] - task_ids.add(entity_info["entityId"]) - matching_parent_ids.add(parent_info["entityId"]) - - # Store whole hierarchi of task entity - prev_id = None - for item in parents: - item_id = item["entityId"] - whole_hierarchy_ids.add(item_id) - - if prev_id is None: - prev_id = item_id + if not is_new_entity: continue - parent_id_by_entity_id[prev_id] = item_id - if item["entityType"] == "show": - break - prev_id = item_id + hier_attr_id = hier_attr_ids_by_key[attr_key] + attr_id = attr_keys[attr_key] + if hier_attr_id in real_values or attr_id in real_values: + continue - # Just skip if nothing is interesting for our settings - if not matching_parent_ids: - return + value_by_key[attr_key] = hier_values[hier_attr_id] - # Query object type ids of parent ids for custom attribute - # definitions query - entities = session.query( - "select object_type_id from TypedContext where id in ({})".format( - self.join_query_keys(matching_parent_ids) - ) - ) + for key, new_value in value_by_key.items(): + if new_value is None: + continue - # Prepare task object id - task_object_id = object_types_by_name["task"]["id"] + hier_id = hier_attr_ids_by_key[key] + std_id = attr_keys[key] + real_hier_value = real_values.get(hier_id) + real_std_value = real_values.get(std_id) + hier_value = hier_values[hier_id] + # Get right type of value for conversion + # - values in event are strings + type_value = real_hier_value + if type_value is None: + type_value = real_std_value + if type_value is None: + type_value = hier_value + # Skip if current values are not set + if type_value is None: + continue - # All object ids for which we're querying custom attribute definitions - object_type_ids = set() - object_type_ids.add(task_object_id) - for entity in entities: - object_type_ids.add(entity["object_type_id"]) + try: + new_value = type(type_value)(new_value) + except Exception: + self.log.warning(( + "Couldn't convert from {} to {}." + " Skipping update values." + ).format(type(new_value), type(type_value))) + continue - attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, object_type_ids, interest_attributes - ) + real_std_value_is_same = new_value == real_std_value + real_hier_value_is_same = new_value == real_hier_value + # New value does not match anything in current entity values + if ( + not is_new_entity + and not real_std_value_is_same + and not real_hier_value_is_same + ): + continue - # Skip if all task attributes are not available - task_attrs = attrs_by_obj_id.get(task_object_id) - if not task_attrs: - return + if not real_std_value_is_same: + op_changes.append(( + std_id, + entity_id, + new_value, + real_values.get(std_id), + std_id in real_values + )) - # Skip attributes that is not in both hierarchical and nonhierarchical - # TODO be able to push values if hierarchical is available - for key in interest_attributes: - if key not in hier_attrs: - task_attrs.pop(key, None) + if not real_hier_value_is_same: + op_changes.append(( + hier_id, + entity_id, + new_value, + real_values.get(hier_id), + hier_id in real_values + )) - elif key not in task_attrs: - hier_attrs.pop(key) + for change in op_changes: + ( + attr_id, + entity_id, + new_value, + old_value, + do_update + ) = change - # Skip if nothing remained - if not task_attrs: - return - - # Do some preparations for custom attribute values query - attr_key_by_id = {} - nonhier_id_by_key = {} - hier_attr_ids = [] - for key, attr_id in hier_attrs.items(): - attr_key_by_id[attr_id] = key - hier_attr_ids.append(attr_id) - - conf_ids = list(hier_attr_ids) - task_conf_ids = [] - for key, attr_id in task_attrs.items(): - attr_key_by_id[attr_id] = key - nonhier_id_by_key[key] = attr_id - conf_ids.append(attr_id) - task_conf_ids.append(attr_id) - - # Query custom attribute values - # - result does not contain values for all entities only result of - # query callback to ftrack server - result = query_custom_attributes( - session, list(hier_attr_ids), whole_hierarchy_ids, True - ) - result.extend( - query_custom_attributes( - session, task_conf_ids, whole_hierarchy_ids, False - ) - ) - - # Prepare variables where result will be stored - # - hierachical values should not contain attribute with value by - # default - hier_values_by_entity_id = { - entity_id: {} - for entity_id in whole_hierarchy_ids - } - # - real values of custom attributes - values_by_entity_id = { - entity_id: { - attr_id: None - for attr_id in conf_ids - } - for entity_id in whole_hierarchy_ids - } - for item in result: - attr_id = item["configuration_id"] - entity_id = item["entity_id"] - value = item["value"] - - values_by_entity_id[entity_id][attr_id] = value - - if attr_id in hier_attr_ids and value is not None: - hier_values_by_entity_id[entity_id][attr_id] = value - - # Prepare values for all task entities - # - going through all parents and storing first value value - # - store None to those that are already known that do not have set - # value at all - for task_id in tuple(task_ids): - for attr_id in hier_attr_ids: - entity_ids = [] - value = None - entity_id = task_id - while value is None: - entity_value = hier_values_by_entity_id[entity_id] - if attr_id in entity_value: - value = entity_value[attr_id] - if value is None: - break - - if value is None: - entity_ids.append(entity_id) - - entity_id = parent_id_by_entity_id.get(entity_id) - if entity_id is None: - break - - for entity_id in entity_ids: - hier_values_by_entity_id[entity_id][attr_id] = value - - # Prepare changes to commit - changes = [] - for task_id in tuple(task_ids): - parent_id = parent_id_by_entity_id[task_id] - for attr_id in hier_attr_ids: - attr_key = attr_key_by_id[attr_id] - nonhier_id = nonhier_id_by_key[attr_key] - - # Real value of hierarchical attribute on parent - # - If is none then should be unset - real_parent_value = values_by_entity_id[parent_id][attr_id] - # Current hierarchical value of a task - # - Will be compared to real parent value - hier_value = hier_values_by_entity_id[task_id][attr_id] - - # Parent value that can be inherited from it's parent entity - parent_value = hier_values_by_entity_id[parent_id][attr_id] - # Task value of nonhierarchical custom attribute - nonhier_value = values_by_entity_id[task_id][nonhier_id] - - if real_parent_value != hier_value: - changes.append({ - "new_value": real_parent_value, - "attr_id": attr_id, - "entity_id": task_id, - "attr_key": attr_key - }) - - if parent_value != nonhier_value: - changes.append({ - "new_value": parent_value, - "attr_id": nonhier_id, - "entity_id": task_id, - "attr_key": attr_key - }) - - self._commit_changes(session, changes) - - def _commit_changes(self, session, changes): - uncommited_changes = False - for idx, item in enumerate(changes): - new_value = item["new_value"] - old_value = item["old_value"] - attr_id = item["attr_id"] - entity_id = item["entity_id"] - attr_key = item["attr_key"] - - entity_key = collections.OrderedDict(( + entity_key = collections.OrderedDict([ ("configuration_id", attr_id), ("entity_id", entity_id) - )) - self._cached_changes.append({ - "attr_key": attr_key, - "entity_id": entity_id, - "value": new_value, - "time": datetime.datetime.now() - }) - old_value_is_set = ( - old_value is not ftrack_api.symbol.NOT_SET - and old_value is not None - ) - if new_value is None: - if not old_value_is_set: - continue - op = ftrack_api.operation.DeleteEntityOperation( - "CustomAttributeValue", - entity_key - ) - - elif old_value_is_set: + ]) + if do_update: op = ftrack_api.operation.UpdateEntityOperation( "CustomAttributeValue", entity_key, @@ -446,449 +383,116 @@ class PushFrameValuesToTaskEvent(BaseEvent): ) session.recorded_operations.push(op) - self.log.info(( - "Changing Custom Attribute \"{}\" to value" - " \"{}\" on entity: {}" - ).format(attr_key, new_value, entity_id)) - - if (idx + 1) % 20 == 0: - uncommited_changes = False - try: - session.commit() - except Exception: - session.rollback() - self.log.warning( - "Changing of values failed.", exc_info=True - ) - else: - uncommited_changes = True - if uncommited_changes: - try: + if len(session.recorded_operations) > 100: session.commit() - except Exception: - session.rollback() - self.log.warning("Changing of values failed.", exc_info=True) + session.commit() - def process_attribute_changes( + def process_by_project( self, - session, - object_types_by_name, - interesting_data, - changed_keys_by_object_id, - interest_entity_types, - interest_attributes, - added_entity_ids + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str, + entities_info: list[dict[str, Any]] ): - # Prepare task object id - task_object_id = object_types_by_name["task"]["id"] + """Proces changes in single project. - # Collect object type ids based on settings - interest_object_ids = [] - for entity_type in interest_entity_types: - _entity_type = entity_type.lower() - object_type = object_types_by_name.get(_entity_type) - if not object_type: - self.log.warning("Couldn't find object type \"{}\"".format( - entity_type - )) + Args: + session (ftrack_api.Session): Ftrack session. + event (ftrack_api.event.base.Event): Event which has all changes + information. + project_id (str): Project id related to changes. + entities_info (list[dict[str, Any]]): Changes of entities. + """ - interest_object_ids.append(object_type["id"]) - - # Query entities by filtered data and object ids - entities = self.get_entities( - session, interesting_data, interest_object_ids - ) - if not entities: + ( + interest_attributes, + interest_entity_types + ) = self._get_handler_project_settings(session, event, project_id) + if not interest_attributes or not interest_entity_types: return - # Pop not found entities from interesting data - entity_ids = set( - entity["id"] - for entity in entities + entities_info: list[dict[str, Any]] = ( + self._entities_filter_by_settings( + entities_info, + interest_attributes, + interest_entity_types + ) ) - for entity_id in tuple(interesting_data.keys()): - if entity_id not in entity_ids: - interesting_data.pop(entity_id) - - # Add task object type to list - attr_obj_ids = list(interest_object_ids) - attr_obj_ids.append(task_object_id) - - attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, attr_obj_ids, interest_attributes - ) - - task_attrs = attrs_by_obj_id.get(task_object_id) - - changed_keys = set() - # Skip keys that are not both in hierachical and type specific - for object_id, keys in changed_keys_by_object_id.items(): - changed_keys |= set(keys) - object_id_attrs = attrs_by_obj_id.get(object_id) - for key in keys: - if key not in hier_attrs: - attrs_by_obj_id[object_id].pop(key) - continue - - if ( - (not object_id_attrs or key not in object_id_attrs) - and (not task_attrs or key not in task_attrs) - ): - hier_attrs.pop(key) - - # Clean up empty values - for key, value in tuple(attrs_by_obj_id.items()): - if not value: - attrs_by_obj_id.pop(key) - - if not attrs_by_obj_id: - self.log.warning(( - "There is not created Custom Attributes {} " - " for entity types: {}" - ).format( - self.join_query_keys(interest_attributes), - self.join_query_keys(interest_entity_types) - )) + if not entities_info: return - # Prepare task entities - task_entities = [] - # If task entity does not contain changed attribute then skip - if task_attrs: - task_entities = self.get_task_entities(session, interesting_data) - - task_entity_ids = set() - parent_id_by_task_id = {} - for task_entity in task_entities: - task_id = task_entity["id"] - task_entity_ids.add(task_id) - parent_id_by_task_id[task_id] = task_entity["parent_id"] - - self.finalize_attribute_changes( - session, - interesting_data, - changed_keys, - attrs_by_obj_id, - hier_attrs, - task_entity_ids, - parent_id_by_task_id, - added_entity_ids - ) - - def finalize_attribute_changes( - self, - session, - interesting_data, - changed_keys, - attrs_by_obj_id, - hier_attrs, - task_entity_ids, - parent_id_by_task_id, - added_entity_ids - ): - attr_id_to_key = {} - for attr_confs in attrs_by_obj_id.values(): - for key in changed_keys: - custom_attr_id = attr_confs.get(key) - if custom_attr_id: - attr_id_to_key[custom_attr_id] = key - - for key in changed_keys: - custom_attr_id = hier_attrs.get(key) - if custom_attr_id: - attr_id_to_key[custom_attr_id] = key - - entity_ids = ( - set(interesting_data.keys()) | task_entity_ids - ) - attr_ids = set(attr_id_to_key.keys()) - - current_values_by_id = self.get_current_values( - session, - attr_ids, - entity_ids, - task_entity_ids, - hier_attrs - ) - - changes = [] - for entity_id, current_values in current_values_by_id.items(): - parent_id = parent_id_by_task_id.get(entity_id) - if not parent_id: - parent_id = entity_id - values = interesting_data[parent_id] - - added_entity = entity_id in added_entity_ids - for attr_id, old_value in current_values.items(): - if added_entity and attr_id in hier_attrs: - continue - - attr_key = attr_id_to_key.get(attr_id) - if not attr_key: - continue - - # Convert new value from string - new_value = values.get(attr_key) - new_value_is_valid = ( - old_value is not ftrack_api.symbol.NOT_SET - and new_value is not None - ) - if added_entity and not new_value_is_valid: - continue - - if new_value is not None and new_value_is_valid: - try: - new_value = type(old_value)(new_value) - except Exception: - self.log.warning(( - "Couldn't convert from {} to {}." - " Skipping update values." - ).format(type(new_value), type(old_value))) - if new_value == old_value: - continue - - changes.append({ - "new_value": new_value, - "attr_id": attr_id, - "old_value": old_value, - "entity_id": entity_id, - "attr_key": attr_key - }) - self._commit_changes(session, changes) - - def filter_changes( - self, session, event, entities_info, interest_attributes - ): - session_user_id = self.session_user_id(session) - user_data = event["data"].get("user") - changed_by_session = False - if user_data and user_data.get("userid") == session_user_id: - changed_by_session = True - - current_time = datetime.datetime.now() - - interesting_data = {} - changed_keys_by_object_id = {} - - for entity_info in entities_info: - # Care only about changes if specific keys - entity_changes = {} - changes = entity_info["changes"] - for key in interest_attributes: - if key in changes: - entity_changes[key] = changes[key]["new"] - - entity_id = entity_info["entityId"] - if changed_by_session: - for key, new_value in tuple(entity_changes.items()): - for cached in tuple(self._cached_changes): - if ( - cached["entity_id"] != entity_id - or cached["attr_key"] != key - ): - continue - - cached_value = cached["value"] - try: - new_value = type(cached_value)(new_value) - except Exception: - pass - - if cached_value == new_value: - self._cached_changes.remove(cached) - entity_changes.pop(key) - break - - delta = (current_time - cached["time"]).seconds - if delta > self._max_delta: - self._cached_changes.remove(cached) - - if not entity_changes: - continue - - entity_id = entity_info["entityId"] - object_id = entity_info["objectTypeId"] - interesting_data[entity_id] = entity_changes - if object_id not in changed_keys_by_object_id: - changed_keys_by_object_id[object_id] = set() - changed_keys_by_object_id[object_id] |= set(entity_changes.keys()) - - return interesting_data, changed_keys_by_object_id - - def interesting_data_for_added( - self, - session, - added_entities, - interest_attributes, - interesting_data, - changed_keys_by_object_id - ): - if not added_entities or not interest_attributes: - return - - object_type_ids = set() - entity_ids = set() - all_entity_ids = set() - object_id_by_entity_id = {} - project_id = None - entity_ids_by_parent_id = collections.defaultdict(set) - for entity_info in added_entities: - object_id = entity_info["objectTypeId"] - entity_id = entity_info["entityId"] - object_type_ids.add(object_id) - entity_ids.add(entity_id) - object_id_by_entity_id[entity_id] = object_id - - for item in entity_info["parents"]: - entity_id = item["entityId"] - all_entity_ids.add(entity_id) - parent_id = item["parentId"] - if not parent_id: - project_id = entity_id - else: - entity_ids_by_parent_id[parent_id].add(entity_id) - - hier_attrs = self.get_hierarchical_configurations( + attrs_by_obj_id, hier_attrs = self._get_attrs_configurations( session, interest_attributes ) - if not hier_attrs: + # Skip if attributes are not available + # - there is nothing to sync + if not attrs_by_obj_id or not hier_attrs: return - hier_attrs_key_by_id = { - attr_conf["id"]: attr_conf["key"] - for attr_conf in hier_attrs - } - default_values_by_key = { - attr_conf["key"]: attr_conf["default"] - for attr_conf in hier_attrs - } + entity_ids_by_parent_id = collections.defaultdict(set) + all_entity_ids = set() + for entity_info in entities_info: + entity_id = None + for item in entity_info["parents"]: + item_id = item["entityId"] + all_entity_ids.add(item_id) + if entity_id is not None: + entity_ids_by_parent_id[item_id].add(entity_id) + entity_id = item_id - values = query_custom_attributes( - session, list(hier_attrs_key_by_id.keys()), all_entity_ids, True + attr_ids = {attr["id"] for attr in hier_attrs} + for attrs in attrs_by_obj_id.values(): + attr_ids |= {attr["id"] for attr in attrs} + + # Query real custom attribute values + # - we have to know what are the real values, if are set and to what + # value + value_items = query_custom_attributes( + session, attr_ids, all_entity_ids, True ) - values_per_entity_id = {} - for entity_id in all_entity_ids: - values_per_entity_id[entity_id] = {} - for attr_name in interest_attributes: - values_per_entity_id[entity_id][attr_name] = None - - for item in values: - entity_id = item["entity_id"] - key = hier_attrs_key_by_id[item["configuration_id"]] - values_per_entity_id[entity_id][key] = item["value"] - - fill_queue = collections.deque() - fill_queue.append((project_id, default_values_by_key)) - while fill_queue: - item = fill_queue.popleft() - entity_id, values_by_key = item - entity_values = values_per_entity_id[entity_id] - new_values_by_key = copy.deepcopy(values_by_key) - for key, value in values_by_key.items(): - current_value = entity_values[key] - if current_value is None: - entity_values[key] = value - else: - new_values_by_key[key] = current_value - - for child_id in entity_ids_by_parent_id[entity_id]: - fill_queue.append((child_id, new_values_by_key)) - - for entity_id in entity_ids: - entity_changes = {} - for key, value in values_per_entity_id[entity_id].items(): - if value is not None: - entity_changes[key] = value - - if not entity_changes: - continue - - interesting_data[entity_id] = entity_changes - object_id = object_id_by_entity_id[entity_id] - if object_id not in changed_keys_by_object_id: - changed_keys_by_object_id[object_id] = set() - changed_keys_by_object_id[object_id] |= set(entity_changes.keys()) - - def get_current_values( - self, - session, - attr_ids, - entity_ids, - task_entity_ids, - hier_attrs - ): - current_values_by_id = {} - if not attr_ids or not entity_ids: - return current_values_by_id - - for entity_id in entity_ids: - current_values_by_id[entity_id] = {} - for attr_id in attr_ids: - current_values_by_id[entity_id][attr_id] = ( - ftrack_api.symbol.NOT_SET - ) - - values = query_custom_attributes( - session, attr_ids, entity_ids, True - ) - - for item in values: + real_values_by_entity_id = collections.defaultdict(dict) + for item in value_items: entity_id = item["entity_id"] attr_id = item["configuration_id"] - if entity_id in task_entity_ids and attr_id in hier_attrs: - continue + real_values_by_entity_id[entity_id][attr_id] = item["value"] - if entity_id not in current_values_by_id: - current_values_by_id[entity_id] = {} - current_values_by_id[entity_id][attr_id] = item["value"] - return current_values_by_id + hier_values_by_entity_id = {} + default_values = { + attr["id"]: attr["default"] + for attr in hier_attrs + } + hier_queue = collections.deque() + hier_queue.append((default_values, [project_id])) + while hier_queue: + parent_values, entity_ids = hier_queue.popleft() + for entity_id in entity_ids: + entity_values = copy.deepcopy(parent_values) + real_values = real_values_by_entity_id[entity_id] + for attr_id, value in real_values.items(): + entity_values[attr_id] = value + hier_values_by_entity_id[entity_id] = entity_values + hier_queue.append( + (entity_values, entity_ids_by_parent_id[entity_id]) + ) - def get_entities(self, session, interesting_data, interest_object_ids): - return session.query(( - "select id from TypedContext" - " where id in ({}) and object_type_id in ({})" - ).format( - self.join_query_keys(interesting_data.keys()), - self.join_query_keys(interest_object_ids) - )).all() - - def get_task_entities(self, session, interesting_data): - return session.query( - "select id, parent_id from Task where parent_id in ({})".format( - self.join_query_keys(interesting_data.keys()) - ) - ).all() - - def attrs_configurations(self, session, object_ids, interest_attributes): - attrs = session.query(self.cust_attrs_query.format( - self.join_query_keys(interest_attributes), - self.join_query_keys(object_ids) - )).all() - - output = {} - hiearchical = {} - for attr in attrs: - if attr["is_hierarchical"]: - hiearchical[attr["key"]] = attr["id"] - continue - obj_id = attr["object_type_id"] - if obj_id not in output: - output[obj_id] = {} - output[obj_id][attr["key"]] = attr["id"] - return output, hiearchical - - def get_hierarchical_configurations(self, session, interest_attributes): - hier_attr_query = ( - "select id, key, object_type_id, is_hierarchical, default" - " from CustomAttributeConfiguration" - " where key in ({}) and is_hierarchical is true" + self.propagate_attribute_changes( + session, + interest_attributes, + entities_info, + attrs_by_obj_id, + hier_attrs, + real_values_by_entity_id, + hier_values_by_entity_id, ) - if not interest_attributes: - return [] - return list(session.query(hier_attr_query.format( - self.join_query_keys(interest_attributes), - )).all()) + + def launch(self, session, event): + filtered_entities_info = self.filter_entities_info(event) + if not filtered_entities_info: + return + + for project_id, entities_info in filtered_entities_info.items(): + self.process_by_project(session, event, project_id, entities_info) def register(session): - PushFrameValuesToTaskEvent(session).register() + PushHierValuesToNonHierEvent(session).register() diff --git a/openpype/modules/ftrack/event_handlers_user/action_applications.py b/openpype/modules/ftrack/event_handlers_user/action_applications.py index 102f04c956..30399b463d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_applications.py +++ b/openpype/modules/ftrack/event_handlers_user/action_applications.py @@ -124,6 +124,11 @@ class AppplicationsAction(BaseAction): if not avalon_project_apps: return False + settings = self.get_project_settings_from_event( + event, avalon_project_doc["name"]) + + only_available = settings["applications"]["only_available"] + items = [] for app_name in avalon_project_apps: app = self.application_manager.applications.get(app_name) @@ -133,6 +138,10 @@ class AppplicationsAction(BaseAction): if app.group.name in CUSTOM_LAUNCH_APP_GROUPS: continue + # Skip applications without valid executables + if only_available and not app.find_executable(): + continue + app_icon = app.icon if app_icon and self.icon_url: try: diff --git a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py index 576a7d36c4..97815f490f 100644 --- a/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py +++ b/openpype/modules/ftrack/plugins/publish/collect_ftrack_family.py @@ -7,23 +7,22 @@ Provides: """ import pyblish.api -from openpype.pipeline import legacy_io from openpype.lib import filter_profiles class CollectFtrackFamily(pyblish.api.InstancePlugin): + """Adds explicitly 'ftrack' to families to upload instance to FTrack. + + Uses selection by combination of hosts/families/tasks names via + profiles resolution. + + Triggered everywhere, checks instance against configured. + + Checks advanced filtering which works on 'families' not on main + 'family', as some variants dynamically resolves addition of ftrack + based on 'families' (editorial drives it by presence of 'review') """ - Adds explicitly 'ftrack' to families to upload instance to FTrack. - Uses selection by combination of hosts/families/tasks names via - profiles resolution. - - Triggered everywhere, checks instance against configured. - - Checks advanced filtering which works on 'families' not on main - 'family', as some variants dynamically resolves addition of ftrack - based on 'families' (editorial drives it by presence of 'review') - """ label = "Collect Ftrack Family" order = pyblish.api.CollectorOrder + 0.4990 @@ -34,68 +33,64 @@ class CollectFtrackFamily(pyblish.api.InstancePlugin): self.log.warning("No profiles present for adding Ftrack family") return - add_ftrack_family = False - task_name = instance.data.get("task", - legacy_io.Session["AVALON_TASK"]) - host_name = legacy_io.Session["AVALON_APP"] + host_name = instance.context.data["hostName"] family = instance.data["family"] + task_name = instance.data.get("task") filtering_criteria = { "hosts": host_name, "families": family, "tasks": task_name } - profile = filter_profiles(self.profiles, filtering_criteria, - logger=self.log) + profile = filter_profiles( + self.profiles, + filtering_criteria, + logger=self.log + ) + + add_ftrack_family = False + families = instance.data.setdefault("families", []) if profile: - families = instance.data.get("families") add_ftrack_family = profile["add_ftrack_family"] - additional_filters = profile.get("advanced_filtering") if additional_filters: - self.log.info("'{}' families used for additional filtering". - format(families)) + families_set = set(families) | {family} + self.log.info( + "'{}' families used for additional filtering".format( + families_set)) add_ftrack_family = self._get_add_ftrack_f_from_addit_filters( additional_filters, - families, + families_set, add_ftrack_family ) - if add_ftrack_family: - self.log.debug("Adding ftrack family for '{}'". - format(instance.data.get("family"))) + result_str = "Not adding" + if add_ftrack_family: + result_str = "Adding" + if "ftrack" not in families: + families.append("ftrack") - if families: - if "ftrack" not in families: - instance.data["families"].append("ftrack") - else: - instance.data["families"] = ["ftrack"] - - result_str = "Adding" - if not add_ftrack_family: - result_str = "Not adding" self.log.info("{} 'ftrack' family for instance with '{}'".format( result_str, family )) - def _get_add_ftrack_f_from_addit_filters(self, - additional_filters, - families, - add_ftrack_family): - """ - Compares additional filters - working on instance's families. + def _get_add_ftrack_f_from_addit_filters( + self, additional_filters, families, add_ftrack_family + ): + """Compares additional filters - working on instance's families. - Triggered for more detailed filtering when main family matches, - but content of 'families' actually matter. - (For example 'review' in 'families' should result in adding to - Ftrack) + Triggered for more detailed filtering when main family matches, + but content of 'families' actually matter. + (For example 'review' in 'families' should result in adding to + Ftrack) - Args: - additional_filters (dict) - from Setting - families (list) - subfamilies - add_ftrack_family (bool) - add ftrack to families if True + Args: + additional_filters (dict) - from Setting + families (set[str]) - subfamilies + add_ftrack_family (bool) - add ftrack to families if True """ + override_filter = None override_filter_value = -1 for additional_filter in additional_filters: diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index a0bd2b305b..4ea8135620 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -29,7 +29,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not zou_asset_data: raise ValueError("Zou asset data not found in OpenPype!") - task_name = instance.data.get("task") + task_name = instance.data.get("task", context.data.get("task")) if not task_name: continue diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 6702cbe7aa..f8e56377bb 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import gazu import pyblish.api +import re class IntegrateKitsuNote(pyblish.api.ContextPlugin): @@ -9,27 +10,98 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" families = ["render", "kitsu"] + + # status settings set_status_note = False note_status_shortname = "wfa" + status_change_conditions = { + "status_conditions": [], + "family_requirements": [], + } + + # comment settings + custom_comment_template = { + "enabled": False, + "comment_template": "{comment}", + } + + def format_publish_comment(self, instance): + """Format the instance's publish comment + + Formats `instance.data` against the custom template. + """ + + def replace_missing_key(match): + """If key is not found in kwargs, set None instead""" + key = match.group(1) + if key not in instance.data: + self.log.warning( + "Key '{}' was not found in instance.data " + "and will be rendered as an empty string " + "in the comment".format(key) + ) + return "" + else: + return str(instance.data[key]) + + template = self.custom_comment_template["comment_template"] + pattern = r"\{([^}]*)\}" + return re.sub(pattern, replace_missing_key, template) def process(self, context): - # Get comment text body - publish_comment = context.data.get("comment") - if not publish_comment: - self.log.info("Comment is not set.") - - self.log.debug("Comment is `{}`".format(publish_comment)) - for instance in context: + # Check if instance is a review by checking its family + # Allow a match to primary family or any of families + families = set([instance.data["family"]] + + instance.data.get("families", [])) + if "review" not in families: + continue + kitsu_task = instance.data.get("kitsu_task") - if kitsu_task is None: + if not kitsu_task: continue # Get note status, by default uses the task status for the note # if it is not specified in the configuration - note_status = kitsu_task["task_status"]["id"] + shortname = kitsu_task["task_status"]["short_name"].upper() + note_status = kitsu_task["task_status_id"] - if self.set_status_note: + # Check if any status condition is not met + allow_status_change = True + for status_cond in self.status_change_conditions[ + "status_conditions" + ]: + condition = status_cond["condition"] == "equal" + match = status_cond["short_name"].upper() == shortname + if match and not condition or condition and not match: + allow_status_change = False + break + + if allow_status_change: + # Get families + families = { + instance.data.get("family") + for instance in context + if instance.data.get("publish") + } + + # Check if any family requirement is met + for family_requirement in self.status_change_conditions[ + "family_requirements" + ]: + condition = family_requirement["condition"] == "equal" + + for family in families: + match = family_requirement["family"].lower() == family + if match and not condition or condition and not match: + allow_status_change = False + break + + if allow_status_change: + break + + # Set note status + if self.set_status_note and allow_status_change: kitsu_status = gazu.task.get_task_status_by_short_name( self.note_status_shortname ) @@ -42,11 +114,22 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): "changed!".format(self.note_status_shortname) ) + # Get comment text body + publish_comment = instance.data.get("comment") + if self.custom_comment_template["enabled"]: + publish_comment = self.format_publish_comment(instance) + + if not publish_comment: + self.log.info("Comment is not set.") + else: + self.log.debug("Comment is `{}`".format(publish_comment)) + # Add comment to kitsu task - task_id = kitsu_task["id"] - self.log.debug("Add new note in taks id {}".format(task_id)) + self.log.debug( + "Add new note in tasks id {}".format(kitsu_task["id"]) + ) kitsu_comment = gazu.task.add_comment( - task_id, note_status, comment=publish_comment + kitsu_task, note_status, comment=publish_comment ) instance.data["kitsu_comment"] = kitsu_comment diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 12482b5657..e05ff05f50 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -12,17 +12,17 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): optional = True def process(self, instance): - task = instance.data["kitsu_task"]["id"] - comment = instance.data["kitsu_comment"]["id"] # Check comment has been created - if not comment: + comment_id = instance.data.get("kitsu_comment", {}).get("id") + if not comment_id: self.log.debug( "Comment not created, review not pushed to preview." ) return # Add review representations as preview of comment + task_id = instance.data["kitsu_task"]["id"] for representation in instance.data.get("representations", []): # Skip if not tagged as review if "kitsureview" not in representation.get("tags", []): @@ -31,6 +31,6 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): self.log.debug("Found review at: {}".format(review_path)) gazu.task.add_preview( - task, comment, review_path, normalize_movie=True + task_id, comment_id, review_path, normalize_movie=True ) self.log.info("Review upload on comment") diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 4fa8cf9fdd..1f38648dfa 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -129,7 +129,7 @@ def update_op_assets( frame_out = frame_in + frames_duration - 1 else: frame_out = project_doc["data"].get("frameEnd", frame_in) - item_data["frameEnd"] = frame_out + item_data["frameEnd"] = int(frame_out) # Fps, fallback to project's value or default value (25.0) try: fps = float(item_data.get("fps")) @@ -147,33 +147,37 @@ def update_op_assets( item_data["resolutionWidth"] = int(match_res.group(1)) item_data["resolutionHeight"] = int(match_res.group(2)) else: - item_data["resolutionWidth"] = project_doc["data"].get( - "resolutionWidth" + item_data["resolutionWidth"] = int( + project_doc["data"].get("resolutionWidth") ) - item_data["resolutionHeight"] = project_doc["data"].get( - "resolutionHeight" + item_data["resolutionHeight"] = int( + project_doc["data"].get("resolutionHeight") ) # Properties that doesn't fully exist in Kitsu. # Guessing those property names below: # Pixel Aspect Ratio - item_data["pixelAspect"] = item_data.get( - "pixel_aspect", project_doc["data"].get("pixelAspect") + item_data["pixelAspect"] = float( + item_data.get( + "pixel_aspect", project_doc["data"].get("pixelAspect") + ) ) # Handle Start - item_data["handleStart"] = item_data.get( - "handle_start", project_doc["data"].get("handleStart") + item_data["handleStart"] = int( + item_data.get( + "handle_start", project_doc["data"].get("handleStart") + ) ) # Handle End - item_data["handleEnd"] = item_data.get( - "handle_end", project_doc["data"].get("handleEnd") + item_data["handleEnd"] = int( + item_data.get("handle_end", project_doc["data"].get("handleEnd")) ) # Clip In - item_data["clipIn"] = item_data.get( - "clip_in", project_doc["data"].get("clipIn") + item_data["clipIn"] = int( + item_data.get("clip_in", project_doc["data"].get("clipIn")) ) # Clip Out - item_data["clipOut"] = item_data.get( - "clip_out", project_doc["data"].get("clipOut") + item_data["clipOut"] = int( + item_data.get("clip_out", project_doc["data"].get("clipOut")) ) # Tasks diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 28863c091a..5a4fa07e98 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1472,13 +1472,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return sync_settings - def get_all_site_configs(self, project_name=None): + def get_all_site_configs(self, project_name=None, + local_editable_only=False): """ Returns (dict) with all sites configured system wide. Args: project_name (str)(optional): if present, check if not disabled - + local_editable_only (bool)(opt): if True return only Local + Setting configurable (for LS UI) Returns: (dict): {'studio': {'provider':'local_drive'...}, 'MY_LOCAL': {'provider':....}} @@ -1499,9 +1501,21 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if site_settings: detail.update(site_settings) system_sites[site] = detail - system_sites.update(self._get_default_site_configs(sync_enabled, project_name)) + if local_editable_only: + local_schema = SyncServerModule.get_local_settings_schema() + editable_keys = {} + for provider_code, editables in local_schema.items(): + editable_keys[provider_code] = ["enabled", "provider"] + for editable_item in editables: + editable_keys[provider_code].append(editable_item["key"]) + + for _, site in system_sites.items(): + provider = site["provider"] + for site_config_key in list(site.keys()): + if site_config_key not in editable_keys[provider]: + site.pop(site_config_key, None) return system_sites diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 0ba68285a4..43286f7da4 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -141,7 +141,9 @@ class TimersManager( signal_handler = SignalHandler(self) idle_manager = IdleManager() widget_user_idle = WidgetUserIdle(self) - widget_user_idle.set_countdown_start(self.time_show_message) + widget_user_idle.set_countdown_start( + self.time_stop_timer - self.time_show_message + ) idle_manager.signal_reset_timer.connect( widget_user_idle.reset_countdown diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index acc2bb054f..22cab28e4b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -22,7 +22,7 @@ from openpype.lib.attribute_definitions import ( deserialize_attr_defs, get_default_values, ) -from openpype.host import IPublishHost +from openpype.host import IPublishHost, IWorkfileHost from openpype.pipeline import legacy_io from openpype.pipeline.plugin_discover import DiscoverResult @@ -1374,6 +1374,7 @@ class CreateContext: self._current_project_name = None self._current_asset_name = None self._current_task_name = None + self._current_workfile_path = None self._host_is_valid = host_is_valid # Currently unused variable @@ -1503,14 +1504,62 @@ class CreateContext: return os.environ["AVALON_APP"] def get_current_project_name(self): + """Project name which was used as current context on context reset. + + Returns: + Union[str, None]: Project name. + """ + return self._current_project_name def get_current_asset_name(self): + """Asset name which was used as current context on context reset. + + Returns: + Union[str, None]: Asset name. + """ + return self._current_asset_name def get_current_task_name(self): + """Task name which was used as current context on context reset. + + Returns: + Union[str, None]: Task name. + """ + return self._current_task_name + def get_current_workfile_path(self): + """Workfile path which was opened on context reset. + + Returns: + Union[str, None]: Workfile path. + """ + + return self._current_workfile_path + + @property + def context_has_changed(self): + """Host context has changed. + + As context is used project, asset, task name and workfile path if + host does support workfiles. + + Returns: + bool: Context changed. + """ + + project_name, asset_name, task_name, workfile_path = ( + self._get_current_host_context() + ) + return ( + self._current_project_name != project_name + or self._current_asset_name != asset_name + or self._current_task_name != task_name + or self._current_workfile_path != workfile_path + ) + project_name = property(get_current_project_name) @property @@ -1575,6 +1624,28 @@ class CreateContext: self._collection_shared_data = None self.refresh_thumbnails() + def _get_current_host_context(self): + project_name = asset_name = task_name = workfile_path = None + if hasattr(self.host, "get_current_context"): + host_context = self.host.get_current_context() + if host_context: + project_name = host_context.get("project_name") + asset_name = host_context.get("asset_name") + task_name = host_context.get("task_name") + + if isinstance(self.host, IWorkfileHost): + workfile_path = self.host.get_current_workfile() + + # --- TODO remove these conditions --- + if not project_name: + project_name = legacy_io.Session.get("AVALON_PROJECT") + if not asset_name: + asset_name = legacy_io.Session.get("AVALON_ASSET") + if not task_name: + task_name = legacy_io.Session.get("AVALON_TASK") + # --- + return project_name, asset_name, task_name, workfile_path + def reset_current_context(self): """Refresh current context. @@ -1593,24 +1664,14 @@ class CreateContext: are stored. We should store the workfile (if is available) too. """ - project_name = asset_name = task_name = None - if hasattr(self.host, "get_current_context"): - host_context = self.host.get_current_context() - if host_context: - project_name = host_context.get("project_name") - asset_name = host_context.get("asset_name") - task_name = host_context.get("task_name") - - if not project_name: - project_name = legacy_io.Session.get("AVALON_PROJECT") - if not asset_name: - asset_name = legacy_io.Session.get("AVALON_ASSET") - if not task_name: - task_name = legacy_io.Session.get("AVALON_TASK") + project_name, asset_name, task_name, workfile_path = ( + self._get_current_host_context() + ) self._current_project_name = project_name self._current_asset_name = asset_name self._current_task_name = task_name + self._current_workfile_path = workfile_path def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/pipeline/publish/contants.py b/openpype/pipeline/publish/contants.py index 169eca2e5c..c5296afe9a 100644 --- a/openpype/pipeline/publish/contants.py +++ b/openpype/pipeline/publish/contants.py @@ -1,2 +1,3 @@ DEFAULT_PUBLISH_TEMPLATE = "publish" DEFAULT_HERO_PUBLISH_TEMPLATE = "hero" +TRANSIENT_DIR_TEMPLATE = "transient" diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 1ec641bac4..81913bcdd5 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -20,13 +20,15 @@ from openpype.settings import ( get_system_settings, ) from openpype.pipeline import ( - tempdir + tempdir, + Anatomy ) from openpype.pipeline.plugin_discover import DiscoverResult from .contants import ( DEFAULT_PUBLISH_TEMPLATE, DEFAULT_HERO_PUBLISH_TEMPLATE, + TRANSIENT_DIR_TEMPLATE ) @@ -690,3 +692,79 @@ def get_publish_repre_path(instance, repre, only_published=False): if os.path.exists(src_path): return src_path return None + + +def get_custom_staging_dir_info(project_name, host_name, family, task_name, + task_type, subset_name, + project_settings=None, + anatomy=None, log=None): + """Checks profiles if context should use special custom dir as staging. + + Args: + project_name (str) + host_name (str) + family (str) + task_name (str) + task_type (str) + subset_name (str) + project_settings(Dict[str, Any]): Prepared project settings. + anatomy (Dict[str, Any]) + log (Logger) (optional) + + Returns: + (tuple) + Raises: + ValueError - if misconfigured template should be used + """ + settings = project_settings or get_project_settings(project_name) + custom_staging_dir_profiles = (settings["global"] + ["tools"] + ["publish"] + ["custom_staging_dir_profiles"]) + if not custom_staging_dir_profiles: + return None, None + + if not log: + log = Logger.get_logger("get_custom_staging_dir_info") + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subsets": subset_name + } + profile = filter_profiles(custom_staging_dir_profiles, + filtering_criteria, + logger=log) + + if not profile or not profile["active"]: + return None, None + + if not anatomy: + anatomy = Anatomy(project_name) + + template_name = profile["template_name"] or TRANSIENT_DIR_TEMPLATE + _validate_transient_template(project_name, template_name, anatomy) + + custom_staging_dir = anatomy.templates[template_name]["folder"] + is_persistent = profile["custom_staging_dir_persistent"] + + return custom_staging_dir, is_persistent + + +def _validate_transient_template(project_name, template_name, anatomy): + """Check that transient template is correctly configured. + + Raises: + ValueError - if misconfigured template + """ + if template_name not in anatomy.templates: + raise ValueError(("Anatomy of project \"{}\" does not have set" + " \"{}\" template key!" + ).format(project_name, template_name)) + + if "folder" not in anatomy.templates[template_name]: + raise ValueError(("There is not set \"folder\" template in \"{}\" anatomy" # noqa + " for project \"{}\"." + ).format(template_name, project_name)) diff --git a/openpype/plugins/publish/cleanup.py b/openpype/plugins/publish/cleanup.py index ef312e391f..b90c88890d 100644 --- a/openpype/plugins/publish/cleanup.py +++ b/openpype/plugins/publish/cleanup.py @@ -93,6 +93,10 @@ class CleanUp(pyblish.api.InstancePlugin): self.log.info("No staging directory found: %s" % staging_dir) return + if instance.data.get("stagingDir_persistent"): + self.log.info("Staging dir: %s should be persistent" % staging_dir) + return + self.log.info("Removing staging directory {}".format(staging_dir)) shutil.rmtree(staging_dir) diff --git a/openpype/plugins/publish/cleanup_farm.py b/openpype/plugins/publish/cleanup_farm.py index b87d4698a2..8052f13734 100644 --- a/openpype/plugins/publish/cleanup_farm.py +++ b/openpype/plugins/publish/cleanup_farm.py @@ -37,7 +37,7 @@ class CleanUpFarm(pyblish.api.ContextPlugin): dirpaths_to_remove = set() for instance in context: staging_dir = instance.data.get("stagingDir") - if staging_dir: + if staging_dir and not instance.data.get("stagingDir_persistent"): dirpaths_to_remove.add(os.path.normpath(staging_dir)) if "representations" in instance.data: diff --git a/openpype/plugins/publish/collect_comment.py b/openpype/plugins/publish/collect_comment.py index 5be04731ac..9f41e37f22 100644 --- a/openpype/plugins/publish/collect_comment.py +++ b/openpype/plugins/publish/collect_comment.py @@ -29,7 +29,7 @@ from openpype.pipeline.publish import OpenPypePyblishPluginMixin class CollectInstanceCommentDef( - pyblish.api.ContextPlugin, + pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin ): label = "Comment per instance" diff --git a/openpype/plugins/publish/collect_custom_staging_dir.py b/openpype/plugins/publish/collect_custom_staging_dir.py new file mode 100644 index 0000000000..72ab0fe34d --- /dev/null +++ b/openpype/plugins/publish/collect_custom_staging_dir.py @@ -0,0 +1,67 @@ +""" +Requires: + anatomy + + +Provides: + instance.data -> stagingDir (folder path) + -> stagingDir_persistent (bool) +""" +import copy +import os.path + +import pyblish.api + +from openpype.pipeline.publish.lib import get_custom_staging_dir_info + + +class CollectCustomStagingDir(pyblish.api.InstancePlugin): + """Looks through profiles if stagingDir should be persistent and in special + location. + + Transient staging dir could be useful in specific use cases where is + desirable to have temporary renders in specific, persistent folders, could + be on disks optimized for speed for example. + + It is studio responsibility to clean up obsolete folders with data. + + Location of the folder is configured in `project_anatomy/templates/others`. + ('transient' key is expected, with 'folder' key) + + Which family/task type/subset is applicable is configured in: + `project_settings/global/tools/publish/custom_staging_dir_profiles` + + """ + label = "Collect Custom Staging Directory" + order = pyblish.api.CollectorOrder + 0.4990 + + template_key = "transient" + + def process(self, instance): + family = instance.data["family"] + subset_name = instance.data["subset"] + host_name = instance.context.data["hostName"] + project_name = instance.context.data["projectName"] + + anatomy = instance.context.data["anatomy"] + anatomy_data = copy.deepcopy(instance.data["anatomyData"]) + task = anatomy_data.get("task", {}) + + transient_tml, is_persistent = get_custom_staging_dir_info( + project_name, host_name, family, task.get("name"), + task.get("type"), subset_name, anatomy=anatomy, log=self.log) + result_str = "Not adding" + if transient_tml: + anatomy_data["root"] = anatomy.roots + scene_name = instance.context.data.get("currentFile") + if scene_name: + anatomy_data["scene_name"] = os.path.basename(scene_name) + transient_dir = transient_tml.format(**anatomy_data) + instance.data["stagingDir"] = transient_dir + + instance.data["stagingDir_persistent"] = is_persistent + result_str = "Adding '{}' as".format(transient_dir) + + self.log.info("{} custom staging dir for instance with '{}'".format( + result_str, family + )) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 621c63a87e..816393f6f9 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -16,9 +16,7 @@ from openpype.lib import ( get_transcode_temp_directory, convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg, - - CREATE_NO_WINDOW + should_convert_for_ffmpeg ) from openpype.lib.profiles_filtering import filter_profiles @@ -255,6 +253,9 @@ class ExtractBurnin(publish.Extractor): # Add context data burnin_data. burnin_data["custom"] = custom_data + # Add data members. + burnin_data.update(instance.data.get("burninDataMembers", {})) + # Add source camera name to burnin data camera_name = repre.get("camera_name") if camera_name: @@ -339,8 +340,6 @@ class ExtractBurnin(publish.Extractor): "logger": self.log, "env": {} } - if platform.system().lower() == "windows": - process_kwargs["creationflags"] = CREATE_NO_WINDOW run_openpype_process(*args, **process_kwargs) # Remove the temporary json @@ -730,7 +729,6 @@ class ExtractBurnin(publish.Extractor): return filtered_burnin_defs families = self.families_from_instance(instance) - low_families = [family.lower() for family in families] for filename_suffix, orig_burnin_def in burnin_defs.items(): burnin_def = copy.deepcopy(orig_burnin_def) @@ -741,7 +739,7 @@ class ExtractBurnin(publish.Extractor): families_filters = def_filter["families"] if not self.families_filter_validation( - low_families, families_filters + families, families_filters ): self.log.debug(( "Skipped burnin definition \"{}\". Family" @@ -778,31 +776,19 @@ class ExtractBurnin(publish.Extractor): return filtered_burnin_defs def families_filter_validation(self, families, output_families_filter): - """Determine if entered families intersect with families filters. + """Determines if entered families intersect with families filters. All family values are lowered to avoid unexpected results. """ - if not output_families_filter: + + families_filter_lower = set(family.lower() for family in + output_families_filter + # Exclude empty filter values + if family) + if not families_filter_lower: return True - - for family_filter in output_families_filter: - if not family_filter: - continue - - if not isinstance(family_filter, (list, tuple)): - if family_filter.lower() not in families: - continue - return True - - valid = True - for family in family_filter: - if family.lower() not in families: - valid = False - break - - if valid: - return True - return False + return any(family.lower() in families_filter_lower + for family in families) def families_from_instance(self, instance): """Return all families of entered instance.""" diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index b117006871..760b1a6b37 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -24,7 +24,10 @@ from openpype.client import ( get_version_by_name, ) from openpype.lib import source_hash -from openpype.lib.file_transaction import FileTransaction +from openpype.lib.file_transaction import ( + FileTransaction, + DuplicateDestinationError +) from openpype.pipeline.publish import ( KnownPublishError, get_publish_template_name, @@ -80,10 +83,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder families = ["workfile", "pointcache", + "pointcloud", "proxyAbc", "camera", "animation", "model", + "maxScene", "mayaAscii", "mayaScene", "setdress", @@ -168,9 +173,18 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ).format(instance.data["family"])) return - file_transactions = FileTransaction(log=self.log) + file_transactions = FileTransaction(log=self.log, + # Enforce unique transfers + allow_queue_replacements=False) try: self.register(instance, file_transactions, filtered_repres) + except DuplicateDestinationError as exc: + # Raise DuplicateDestinationError as KnownPublishError + # and rollback the transactions + file_transactions.rollback() + six.reraise(KnownPublishError, + KnownPublishError(exc), + sys.exc_info()[2]) except Exception: # clean destination # todo: preferably we'd also rollback *any* changes to the database diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index b93abab1d8..1d0177f151 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -76,10 +76,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.00001 families = ["workfile", "pointcache", + "pointcloud", "proxyAbc", "camera", "animation", "model", + "maxScene", "mayaAscii", "mayaScene", "setdress", diff --git a/openpype/plugins/publish/preintegrate_thumbnail_representation.py b/openpype/plugins/publish/preintegrate_thumbnail_representation.py index b88ccee9dc..1c95b82c97 100644 --- a/openpype/plugins/publish/preintegrate_thumbnail_representation.py +++ b/openpype/plugins/publish/preintegrate_thumbnail_representation.py @@ -60,6 +60,8 @@ class PreIntegrateThumbnails(pyblish.api.InstancePlugin): if not found_profile: return + thumbnail_repre.setdefault("tags", []) + if not found_profile["integrate_thumbnail"]: if "delete" not in thumbnail_repre["tags"]: thumbnail_repre["tags"].append("delete") diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 932fdc9be4..dc5b3d63c3 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -270,7 +270,7 @@ class PypeCommands: pass def run_tests(self, folder, mark, pyargs, - test_data_folder, persist, app_variant, timeout): + test_data_folder, persist, app_variant, timeout, setup_only): """ Runs tests from 'folder' @@ -311,6 +311,9 @@ class PypeCommands: if timeout: args.extend(["--timeout", timeout]) + if setup_only: + args.extend(["--setup_only", setup_only]) + print("run_tests args: {}".format(args)) import pytest pytest.main(args) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index cb4646c099..d0a4266941 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -4,8 +4,10 @@ import re import subprocess import platform import json -import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins +import tempfile +from string import Formatter +import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins from openpype.lib import ( get_ffmpeg_tool_path, get_ffmpeg_codec_args, @@ -23,7 +25,7 @@ FFMPEG = ( ).format(ffmpeg_path) DRAWTEXT = ( - "drawtext=fontfile='%(font)s':text=\\'%(text)s\\':" + "drawtext@'%(label)s'=fontfile='%(font)s':text=\\'%(text)s\\':" "x=%(x)s:y=%(y)s:fontcolor=%(color)s@%(opacity).1f:fontsize=%(size)d" ) TIMECODE = ( @@ -39,6 +41,45 @@ TIMECODE_KEY = "{timecode}" SOURCE_TIMECODE_KEY = "{source_timecode}" +def convert_list_to_command(list_to_convert, fps, label=""): + """Convert a list of values to a drawtext command file for ffmpeg `sendcmd` + + The list of values is expected to have a value per frame. If the video + file ends up being longer than the amount of samples per frame than the + last value will be held. + + Args: + list_to_convert (list): List of values per frame. + fps (float or int): The expected frame per seconds of the output file. + label (str): Label for the drawtext, if specific drawtext filter is + required + + Returns: + str: Filepath to the temporary drawtext command file. + + """ + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: + for i, value in enumerate(list_to_convert): + seconds = i / fps + + # Escape special character + value = str(value).replace(":", "\\:") + + filter = "drawtext" + if label: + filter += "@" + label + + line = ( + "{start} {filter} reinit text='{value}';" + "\n".format(start=seconds, filter=filter, value=value) + ) + + f.write(line) + f.flush() + return f.name + + def _get_ffprobe_data(source): """Reimplemented from otio burnins to be able use full path to ffprobe :param str source: source media file @@ -144,7 +185,13 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): self.options_init.update(options_init) def add_text( - self, text, align, frame_start=None, frame_end=None, options=None + self, + text, + align, + frame_start=None, + frame_end=None, + options=None, + cmd="" ): """ Adding static text to a filter. @@ -165,7 +212,13 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if frame_end is not None: options["frame_end"] = frame_end - self._add_burnin(text, align, options, DRAWTEXT) + draw_text = DRAWTEXT + if cmd: + draw_text = "{}, {}".format(cmd, DRAWTEXT) + + options["label"] = align + + self._add_burnin(text, align, options, draw_text) def add_timecode( self, align, frame_start=None, frame_end=None, frame_start_tc=None, @@ -345,12 +398,6 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): "stderr": subprocess.PIPE, "shell": True, } - if platform.system().lower() == "windows": - kwargs["creationflags"] = ( - subprocess.CREATE_NEW_PROCESS_GROUP - | getattr(subprocess, "DETACHED_PROCESS", 0) - | getattr(subprocess, "CREATE_NO_WINDOW", 0) - ) proc = subprocess.Popen(command, **kwargs) _stdout, _stderr = proc.communicate() @@ -414,11 +461,13 @@ def burnins_from_data( True by default. Presets must be set separately. Should be dict with 2 keys: - - "options" - sets look of burnins - colors, opacity,...(more info: ModifiedBurnins doc) + - "options" - sets look of burnins - colors, opacity,... + (more info: ModifiedBurnins doc) - *OPTIONAL* default values are used when not included - "burnins" - contains dictionary with burnins settings - *OPTIONAL* burnins won't be added (easier is not to use this) - - each key of "burnins" represents Alignment, there are 6 possibilities: + - each key of "burnins" represents Alignment, + there are 6 possibilities: TOP_LEFT TOP_CENTERED TOP_RIGHT BOTTOM_LEFT BOTTOM_CENTERED BOTTOM_RIGHT - value must be string with text you want to burn-in @@ -497,13 +546,14 @@ def burnins_from_data( if source_timecode is not None: data[SOURCE_TIMECODE_KEY[1:-1]] = SOURCE_TIMECODE_KEY + clean_up_paths = [] for align_text, value in burnin_values.items(): if not value: continue - if isinstance(value, (dict, list, tuple)): + if isinstance(value, dict): raise TypeError(( - "Expected string or number type." + "Expected string, number or list type." " Got: {} - \"{}\"" " (Make sure you have new burnin presets)." ).format(str(type(value)), str(value))) @@ -539,8 +589,48 @@ def burnins_from_data( print("Source does not have set timecode value.") value = value.replace(SOURCE_TIMECODE_KEY, MISSING_KEY_VALUE) - key_pattern = re.compile(r"(\{.*?[^{0]*\})") + # Convert lists. + cmd = "" + text = None + keys = [i[1] for i in Formatter().parse(value) if i[1] is not None] + list_to_convert = [] + # Warn about nested dictionary support for lists. Ei. we dont support + # it. + if "[" in "".join(keys): + print( + "We dont support converting nested dictionaries to lists," + " so skipping {}".format(value) + ) + else: + for key in keys: + data_value = data[key] + + # Multiple lists are not supported. + if isinstance(data_value, list) and list_to_convert: + raise ValueError( + "Found multiple lists to convert, which is not " + "supported: {}".format(value) + ) + + if isinstance(data_value, list): + print("Found list to convert: {}".format(data_value)) + for v in data_value: + data[key] = v + list_to_convert.append(value.format(**data)) + + if list_to_convert: + value = list_to_convert[0] + path = convert_list_to_command( + list_to_convert, data["fps"], label=align + ) + cmd = "sendcmd=f='{}'".format(path) + cmd = cmd.replace("\\", "/") + cmd = cmd.replace(":", "\\:") + clean_up_paths.append(path) + + # Failsafe for missing keys. + key_pattern = re.compile(r"(\{.*?[^{0]*\})") missing_keys = [] for group in key_pattern.findall(value): try: @@ -574,7 +664,8 @@ def burnins_from_data( continue text = value.format(**data) - burnin.add_text(text, align, frame_start, frame_end) + + burnin.add_text(text, align, frame_start, frame_end, cmd=cmd) ffmpeg_args = [] if codec_data: @@ -605,6 +696,8 @@ def burnins_from_data( burnin.render( output_path, args=ffmpeg_args_str, overwrite=overwrite, **data ) + for path in clean_up_paths: + os.remove(path) if __name__ == "__main__": diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 02c0e35377..e5e535bf19 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -58,12 +58,16 @@ "file": "{originalBasename}.{ext}", "path": "{@folder}/{@file}" }, + "transient": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{family}/{subset}" + }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", "simpleUnrealTexture": "Simple Unreal Texture", "online": "online", - "source": "source" + "source": "source", + "transient": "transient" } } } diff --git a/openpype/settings/defaults/project_settings/applications.json b/openpype/settings/defaults/project_settings/applications.json new file mode 100644 index 0000000000..62f3cdfe1b --- /dev/null +++ b/openpype/settings/defaults/project_settings/applications.json @@ -0,0 +1,3 @@ +{ + "only_available": false +} diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index fe05f94590..20eec0c09d 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -80,6 +80,94 @@ "enabled": true, "optional": true, "active": false + }, + "ExtractThumbnail": { + "enabled": true, + "optional": true, + "active": true, + "presets": { + "model": { + "image_settings": { + "file_format": "JPEG", + "color_mode": "RGB", + "quality": 100 + }, + "display_options": { + "shading": { + "light": "STUDIO", + "studio_light": "Default", + "type": "SOLID", + "color_type": "OBJECT", + "show_xray": false, + "show_shadows": false, + "show_cavity": true + }, + "overlay": { + "show_overlays": false + } + } + }, + "rig": { + "image_settings": { + "file_format": "JPEG", + "color_mode": "RGB", + "quality": 100 + }, + "display_options": { + "shading": { + "light": "STUDIO", + "studio_light": "Default", + "type": "SOLID", + "color_type": "OBJECT", + "show_xray": true, + "show_shadows": false, + "show_cavity": false + }, + "overlay": { + "show_overlays": true, + "show_ortho_grid": false, + "show_floor": false, + "show_axis_x": false, + "show_axis_y": false, + "show_axis_z": false, + "show_text": false, + "show_stats": false, + "show_cursor": false, + "show_annotation": false, + "show_extras": false, + "show_relationship_lines": false, + "show_outline_selected": false, + "show_motion_paths": false, + "show_object_origins": false, + "show_bones": true + } + } + } + } + }, + "ExtractPlayblast": { + "enabled": true, + "optional": true, + "active": true, + "presets": { + "default": { + "image_settings": { + "file_format": "PNG", + "color_mode": "RGB", + "color_depth": "8", + "compression": 15 + }, + "display_options": { + "shading": { + "type": "MATERIAL", + "render_pass": "COMBINED" + }, + "overlay": { + "show_overlays": false + } + } + } + } } } } diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index bdba6d7322..822604fd2f 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -9,6 +9,13 @@ "rules": {} } }, + "workfile": { + "submission_overrides": [ + "render_chunk", + "frame_range", + "resolution" + ] + }, "publish": { "CollectRenderPath": { "output_extension": "png", diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 6b6f2d465b..fdd70f1a44 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -23,7 +23,7 @@ "enabled": true, "optional": false, "active": true, - "tile_assembler_plugin": "OpenPypeTileAssembler", + "tile_assembler_plugin": "DraftTileAssembler", "use_published": true, "import_reference": false, "asset_dependencies": true, @@ -43,10 +43,7 @@ "use_published": true, "priority": 50, "chunk_size": 10, - "group": "none", - "deadline_pool": "", - "deadline_pool_secondary": "", - "framePerTask": 1 + "group": "none" }, "NukeSubmitDeadline": { "enabled": true, diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 954606820a..f974eebaca 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -16,5 +16,10 @@ "linux": [] } } + }, + "copy_fusion_settings": { + "copy_path": "~/.openpype/hosts/fusion/profiles", + "copy_status": false, + "force_sync": false } } diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index aad17d54da..30e56300d1 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -249,6 +249,29 @@ } } } + }, + { + "families": [], + "hosts": [ + "maya" + ], + "task_types": [], + "task_names": [], + "subsets": [], + "burnins": { + "maya_burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_CENTERED": "{focalLength:.2f} mm", + "TOP_RIGHT": "{anatomy[version]}", + "BOTTOM_LEFT": "{username}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "filter": { + "families": [], + "tags": [] + } + } + } } ] }, @@ -591,7 +614,8 @@ "task_names": [], "template_name": "simpleUnrealTextureHero" } - ] + ], + "custom_staging_dir_profiles": [] } }, "project_folder_structure": "{\"__project_root__\": {\"prod\": {}, \"resources\": {\"footage\": {\"plates\": {}, \"offline\": {}}, \"audio\": {}, \"art_dept\": {}}, \"editorial\": {}, \"assets\": {\"characters\": {}, \"locations\": {}}, \"shots\": {}}}", diff --git a/openpype/settings/defaults/project_settings/kitsu.json b/openpype/settings/defaults/project_settings/kitsu.json index 95b3da04ae..59a36d8b97 100644 --- a/openpype/settings/defaults/project_settings/kitsu.json +++ b/openpype/settings/defaults/project_settings/kitsu.json @@ -7,7 +7,15 @@ "publish": { "IntegrateKitsuNote": { "set_status_note": false, - "note_status_shortname": "wfa" + "note_status_shortname": "wfa", + "status_change_conditions": { + "status_conditions": [], + "family_requirements": [] + }, + "custom_comment_template": { + "enabled": false, + "comment_template": "{comment}\n\n| | |\n|--|--|\n| version| `{version}` |\n| family | `{family}` |\n| name | `{name}` |" + } } } } diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 667b42411d..d59cdf8c4a 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -4,5 +4,20 @@ "aov_separator": "underscore", "image_format": "exr", "multipass": true + }, + "PointCloud":{ + "attribute":{ + "Age": "age", + "Radius": "radius", + "Position": "position", + "Rotation": "rotation", + "Scale": "scale", + "Velocity": "velocity", + "Color": "color", + "TextureCoordinate": "texcoord", + "MaterialID": "matid", + "custFloats": "custFloats", + "custVecs": "custVecs" + } } } diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index dca0b95293..e914eb29f9 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -147,6 +147,7 @@ "enabled": true, "write_color_sets": false, "write_face_sets": false, + "include_parent_hierarchy": false, "include_user_defined_attributes": false, "defaults": [ "Main" @@ -330,6 +331,11 @@ "optional": true, "active": true }, + "ValidateMayaColorSpace": { + "enabled": true, + "optional": true, + "active": true + }, "ValidateAttributes": { "enabled": false, "attributes": {} @@ -784,7 +790,7 @@ "ExtractPlayblast": { "capture_preset": { "Codec": { - "compression": "jpg", + "compression": "png", "format": "image", "quality": 95 }, @@ -811,7 +817,8 @@ }, "Generic": { "isolate_view": true, - "off_screen": true + "off_screen": true, + "pan_zoom": false }, "Renderer": { "rendererName": "vp2Renderer" diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 9173a8c3d5..1671748e97 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -42,6 +42,7 @@ "default_variants": [] }, "auto_detect_render": { + "enabled": false, "allow_group_rename": true, "group_name_template": "L{group_index}", "group_idx_offset": 10, diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 5fd9b926fb..eb3a88ce66 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -133,7 +133,7 @@ "linux": [] }, "arguments": { - "windows": [], + "windows": ["-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms"], "darwin": [], "linux": [] }, diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index b4c878fe0f..cff614a4bb 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -350,7 +350,7 @@ How output of the schema could look like on save: - number input, can be used for both integer and float - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) - - key `"maxium"` as maximum allowed number to enter (Default: `99999`) + - key `"maximum"` as maximum allowed number to enter (Default: `99999`) - key `"steps"` will change single step value of UI inputs (using arrows and wheel scroll) - for UI it is possible to show slider to enable this option set `show_slider` to `true` ``` diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index ebe59c7942..8c1d8ccbdd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -82,6 +82,10 @@ "type": "schema", "name": "schema_project_slack" }, + { + "type": "schema", + "name": "schema_project_applications" + }, { "type": "schema", "name": "schema_project_max" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_applications.json b/openpype/settings/entities/schemas/projects_schema/schema_project_applications.json new file mode 100644 index 0000000000..030ed3ee8a --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_applications.json @@ -0,0 +1,14 @@ +{ + "type": "dict", + "key": "applications", + "label": "Applications", + "collapsible": true, + "is_file": true, + "children": [ + { + "type": "boolean", + "key": "only_available", + "label": "Show only available applications" + } + ] +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 2320d9ae26..c5ca3eb9f5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -22,6 +22,31 @@ ] }, + { + "type": "dict", + "collapsible": true, + "key": "workfile", + "label": "Workfile", + "children": [ + { + "key": "submission_overrides", + "label": "Submission workfile overrides", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "render_chunk": "Pass chunk size" + }, + { + "frame_range": "Pass frame range" + }, + { + "resolution": "Pass resolution" + } + ] + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index bb5a65e1b7..d8b5e4dc1f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -121,7 +121,7 @@ "DraftTileAssembler": "Draft Tile Assembler" }, { - "OpenPypeTileAssembler": "Open Image IO" + "OpenPypeTileAssembler": "OpenPype Tile Assembler" } ] }, @@ -239,27 +239,12 @@ { "type": "number", "key": "chunk_size", - "label": "Chunk Size" + "label": "Frame per Task" }, { "type": "text", "key": "group", "label": "Group Name" - }, - { - "type": "text", - "key": "deadline_pool", - "label": "Deadline pool" - }, - { - "type": "text", - "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)" - }, - { - "type": "number", - "key": "framePerTask", - "label": "Frame Per Task" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 8c62d75815..464cf2c06d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -45,6 +45,29 @@ ] } ] + }, + { + "type": "dict", + "key": "copy_fusion_settings", + "collapsible": true, + "label": "Local Fusion profile settings", + "children": [ + { + "key": "copy_path", + "type": "path", + "label": "Local Fusion profile directory" + }, + { + "type": "boolean", + "key": "copy_status", + "label": "Copy profile on first launch" + }, + { + "key":"force_sync", + "type": "boolean", + "label": "Resync profile on each launch" + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json index fb47670e74..8aeed00542 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_kitsu.json @@ -46,12 +46,94 @@ { "type": "boolean", "key": "set_status_note", - "label": "Set status on note" + "label": "Set status with note" }, { "type": "text", "key": "note_status_shortname", "label": "Note shortname" + }, + { + "type": "dict", + "collapsible": true, + "key": "status_change_conditions", + "label": "Status change conditions", + "children": [ + { + "type": "list", + "key": "status_conditions", + "label": "Status conditions", + "object_type": { + "type": "dict", + "key": "condition_dict", + "children": [ + { + "type": "enum", + "key": "condition", + "label": "Condition", + "enum_items": [ + {"equal": "Equal"}, + {"not_equal": "Not equal"} + ] + }, + { + "type": "text", + "key": "short_name", + "label": "Short name" + } + ] + } + }, + { + "type": "list", + "key": "family_requirements", + "label": "Family requirements", + "object_type": { + "type": "dict", + "key": "requirement_dict", + "children": [ + { + "type": "enum", + "key": "condition", + "label": "Condition", + "enum_items": [ + {"equal": "Equal"}, + {"not_equal": "Not equal"} + ] + }, + { + "type": "text", + "key": "family", + "label": "Family" + } + ] + } + } + ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "custom_comment_template", + "label": "Custom Comment Template", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Kitsu supports markdown and here you can create a custom comment template.
You can use data from your publishing instance's data." + }, + { + "key": "comment_template", + "type": "text", + "multiline": true, + "label": "Custom comment" + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 8a283c1acc..4fba9aff0a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -51,6 +51,28 @@ "label": "multipass" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "PointCloud", + "label": "Point Cloud", + "children": [ + { + "type": "label", + "label": "Define the channel attribute names before exporting as PRT" + }, + { + "type": "dict-modifiable", + "collapsible": true, + "key": "attribute", + "label": "Channel Attribute", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + } + ] } ] -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 708b688ba5..1094595851 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -202,7 +202,13 @@ "key": "auto_detect_render", "label": "Auto-Detect Create Render", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "label", "label": "The creator tries to auto-detect Render Layers and Render Passes in scene. For Render Layers is used group name as a variant and for Render Passes is used TVPaint layer name.

Group names can be renamed by their used order in scene. The renaming template where can be used {group_index} formatting key which is filled by \"used position index of group\".
- Template: L{group_index}
- Group offset: 10
- Group padding: 3
Would create group names \"L010\", \"L020\", ..." diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json index 53949f65cb..1037519f57 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json @@ -112,6 +112,66 @@ "label": "Extract Layout as JSON" } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "raw-json", + "key": "presets", + "label": "Presets" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractPlayblast", + "label": "ExtractPlayblast", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "raw-json", + "key": "presets", + "label": "Presets" + } + ] } ] -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index 962008d476..85ec482e73 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -408,6 +408,71 @@ } ] } + }, + { + "type": "list", + "key": "custom_staging_dir_profiles", + "label": "Custom Staging Dir Profiles", + "use_label_wrap": true, + "docstring": "Profiles to specify special location and persistence for staging dir. Could be used in Creators and Publish phase!", + "object_type": { + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "active", + "label": "Is active", + "default": true + }, + { + "type": "separator" + }, + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "custom_staging_dir_persistent", + "label": "Custom Staging Folder Persistent", + "type": "boolean", + "default": false + }, + { + "key": "template_name", + "label": "Template Name", + "type": "text", + "placeholder": "transient" + } + ] + } } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 1f0e4eeffb..416e530db2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -19,12 +19,12 @@ { "type": "text", "key": "compression", - "label": "Compression type" + "label": "Encoding" }, { "type": "text", "key": "format", - "label": "Data format" + "label": "Format" }, { "type": "number", @@ -91,6 +91,11 @@ "type": "boolean", "key": "off_screen", "label": " Off Screen" + }, + { + "type": "boolean", + "key": "pan_zoom", + "label": " 2D Pan/Zoom" } ] }, @@ -156,7 +161,7 @@ { "type": "boolean", "key": "override_viewport_options", - "label": "override_viewport_options" + "label": "Override Viewport Options" }, { "type": "enum", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 1598f90643..d6e6c97b8c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -132,6 +132,11 @@ "key": "write_face_sets", "label": "Write Face Sets" }, + { + "type": "boolean", + "key": "include_parent_hierarchy", + "label": "Include Parent Hierarchy" + }, { "type": "boolean", "key": "include_user_defined_attributes", 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 994e2d0032..5a66f8a513 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 @@ -144,6 +144,10 @@ { "key": "ValidateShadingEngine", "label": "Validate Look Shading Engine Naming" + }, + { + "key": "ValidateMayaColorSpace", + "label": "ValidateMayaColorSpace" } ] }, @@ -365,7 +369,8 @@ "label": "Arnold Render Attributes", "use_label_wrap": true, "object_type": { - "type": "text" + "type": "list", + "object_type": "text" } }, { @@ -375,7 +380,8 @@ "label": "Vray Render Attributes", "use_label_wrap": true, "object_type": { - "type": "text" + "type": "list", + "object_type": "text" } }, { @@ -385,7 +391,8 @@ "label": "Redshift Render Attributes", "use_label_wrap": true, "object_type": { - "type": "text" + "type": "list", + "object_type": "text" } }, { @@ -395,7 +402,8 @@ "label": "Renderman Render Attributes", "use_label_wrap": true, "object_type": { - "type": "text" + "type": "list", + "object_type": "text" } } ] diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 6c763544a9..3aa6c5d8cb 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -19,6 +19,7 @@ from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, ApplicationManager ) +from openpype.settings import get_project_settings from openpype.pipeline import discover_launcher_actions from openpype.tools.utils.lib import ( DynamicQThread, @@ -94,6 +95,8 @@ class ActionModel(QtGui.QStandardItemModel): if not project_doc: return actions + project_settings = get_project_settings(project_name) + only_available = project_settings["applications"]["only_available"] self.application_manager.refresh() for app_def in project_doc["config"]["apps"]: app_name = app_def["name"] @@ -104,6 +107,9 @@ class ActionModel(QtGui.QStandardItemModel): if app.group.name in CUSTOM_LAUNCH_APP_GROUPS: continue + if only_available and not app.find_executable(): + continue + # Get from app definition, if not there from app in project action = type( "app_{}".format(app_name), diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 98ac9c871f..b3aa381d14 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -295,10 +295,10 @@ class SubsetWidget(QtWidgets.QWidget): self.model.set_grouping(state) def _subset_changed(self, text): - if hasattr(self.proxy, "setFilterRegularExpression"): - self.proxy.setFilterRegularExpression(text) - else: + if hasattr(self.proxy, "setFilterRegExp"): self.proxy.setFilterRegExp(text) + else: + self.proxy.setFilterRegularExpression(text) self.view.expandAll() def set_loading_state(self, loading, empty): diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b35491c5b2..2cf11b702d 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -72,8 +72,8 @@ class HierarchyView(QtWidgets.QTreeView): column_delegate_defs = { "name": NameDef(), "type": TypeDef(), - "frameStart": NumberDef(1), - "frameEnd": NumberDef(1), + "frameStart": NumberDef(0), + "frameEnd": NumberDef(0), "fps": NumberDef(1, decimals=3, step=1), "resolutionWidth": NumberDef(0), "resolutionHeight": NumberDef(0), diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index b2bfd7dd5c..5d23886aa8 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -1,4 +1,4 @@ -from qtpy import QtCore +from qtpy import QtCore, QtGui # ID of context item in instance view CONTEXT_ID = "context" @@ -26,6 +26,9 @@ GROUP_ROLE = QtCore.Qt.UserRole + 7 CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8 CREATOR_SORT_ROLE = QtCore.Qt.UserRole + 9 +ResetKeySequence = QtGui.QKeySequence( + QtCore.Qt.ControlModifier | QtCore.Qt.Key_R +) __all__ = ( "CONTEXT_ID", diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 49e7eeb4f7..b62ae7ecc1 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -6,7 +6,7 @@ import collections import uuid import tempfile import shutil -from abc import ABCMeta, abstractmethod, abstractproperty +from abc import ABCMeta, abstractmethod import six import pyblish.api @@ -964,7 +964,8 @@ class AbstractPublisherController(object): access objects directly but by using wrappers that can be serialized. """ - @abstractproperty + @property + @abstractmethod def log(self): """Controller's logger object. @@ -974,13 +975,15 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def event_system(self): """Inner event system for publisher controller.""" pass - @abstractproperty + @property + @abstractmethod def project_name(self): """Current context project name. @@ -990,7 +993,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def current_asset_name(self): """Current context asset name. @@ -1000,7 +1004,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def current_task_name(self): """Current context task name. @@ -1010,7 +1015,21 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod + def host_context_has_changed(self): + """Host context changed after last reset. + + 'CreateContext' has this option available using 'context_has_changed'. + + Returns: + bool: Context has changed. + """ + + pass + + @property + @abstractmethod def host_is_valid(self): """Host is valid for creation part. @@ -1023,7 +1042,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def instances(self): """Collected/created instances. @@ -1134,7 +1154,13 @@ class AbstractPublisherController(object): @abstractmethod def save_changes(self): - """Save changes in create context.""" + """Save changes in create context. + + Save can crash because of unexpected errors. + + Returns: + bool: Save was successful. + """ pass @@ -1145,7 +1171,19 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod + def publish_has_started(self): + """Has publishing finished. + + Returns: + bool: If publishing finished and all plugins were iterated. + """ + + pass + + @property + @abstractmethod def publish_has_finished(self): """Has publishing finished. @@ -1155,7 +1193,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_is_running(self): """Publishing is running right now. @@ -1165,7 +1204,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_validated(self): """Publish validation passed. @@ -1175,7 +1215,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_crashed(self): """Publishing crashed for any reason. @@ -1185,7 +1226,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_validation_errors(self): """During validation happened at least one validation error. @@ -1195,7 +1237,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_max_progress(self): """Get maximum possible progress number. @@ -1205,7 +1248,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_progress(self): """Current progress number. @@ -1215,7 +1259,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_error_msg(self): """Current error message which cause fail of publishing. @@ -1267,7 +1312,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def convertor_items(self): pass @@ -1356,6 +1402,7 @@ class BasePublisherController(AbstractPublisherController): self._publish_has_validation_errors = False self._publish_has_crashed = False # All publish plugins are processed + self._publish_has_started = False self._publish_has_finished = False self._publish_max_progress = 0 self._publish_progress = 0 @@ -1386,7 +1433,8 @@ class BasePublisherController(AbstractPublisherController): "show.card.message" - Show card message request (UI related). "instances.refresh.finished" - Instances are refreshed. "plugins.refresh.finished" - Plugins refreshed. - "publish.reset.finished" - Publish context reset finished. + "publish.reset.finished" - Reset finished. + "controller.reset.started" - Controller reset started. "controller.reset.finished" - Controller reset finished. "publish.process.started" - Publishing started. Can be started from paused state. @@ -1425,7 +1473,16 @@ class BasePublisherController(AbstractPublisherController): def _set_host_is_valid(self, value): if self._host_is_valid != value: self._host_is_valid = value - self._emit_event("publish.host_is_valid.changed", {"value": value}) + self._emit_event( + "publish.host_is_valid.changed", {"value": value} + ) + + def _get_publish_has_started(self): + return self._publish_has_started + + def _set_publish_has_started(self, value): + if value != self._publish_has_started: + self._publish_has_started = value def _get_publish_has_finished(self): return self._publish_has_finished @@ -1449,7 +1506,9 @@ class BasePublisherController(AbstractPublisherController): def _set_publish_has_validated(self, value): if self._publish_has_validated != value: self._publish_has_validated = value - self._emit_event("publish.has_validated.changed", {"value": value}) + self._emit_event( + "publish.has_validated.changed", {"value": value} + ) def _get_publish_has_crashed(self): return self._publish_has_crashed @@ -1497,6 +1556,9 @@ class BasePublisherController(AbstractPublisherController): host_is_valid = property( _get_host_is_valid, _set_host_is_valid ) + publish_has_started = property( + _get_publish_has_started, _set_publish_has_started + ) publish_has_finished = property( _get_publish_has_finished, _set_publish_has_finished ) @@ -1526,6 +1588,7 @@ class BasePublisherController(AbstractPublisherController): """Reset most of attributes that can be reset.""" self.publish_is_running = False + self.publish_has_started = False self.publish_has_validated = False self.publish_has_crashed = False self.publish_has_validation_errors = False @@ -1645,10 +1708,7 @@ class PublisherController(BasePublisherController): str: Project name. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.active_project() - - return self._host.get_current_context()["project_name"] + return self._create_context.get_current_project_name() @property def current_asset_name(self): @@ -1658,10 +1718,7 @@ class PublisherController(BasePublisherController): Union[str, None]: Asset name or None if asset is not set. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.Session["AVALON_ASSET"] - - return self._host.get_current_context()["asset_name"] + return self._create_context.get_current_asset_name() @property def current_task_name(self): @@ -1671,10 +1728,11 @@ class PublisherController(BasePublisherController): Union[str, None]: Task name or None if task is not set. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.Session["AVALON_TASK"] + return self._create_context.get_current_task_name() - return self._host.get_current_context()["task_name"] + @property + def host_context_has_changed(self): + return self._create_context.context_has_changed @property def instances(self): @@ -1751,6 +1809,8 @@ class PublisherController(BasePublisherController): """Reset everything related to creation and publishing.""" self.stop_publish() + self._emit_event("controller.reset.started") + self.host_is_valid = self._create_context.host_is_valid self._create_context.reset_preparation() @@ -1992,7 +2052,15 @@ class PublisherController(BasePublisherController): ) def trigger_convertor_items(self, convertor_identifiers): - self.save_changes() + """Trigger legacy item convertors. + + This functionality requires to save and reset CreateContext. The reset + is needed so Creators can collect converted items. + + Args: + convertor_identifiers (list[str]): Identifiers of convertor + plugins. + """ success = True try: @@ -2039,13 +2107,33 @@ class PublisherController(BasePublisherController): self._on_create_instance_change() return success - def save_changes(self): - """Save changes happened during creation.""" + def save_changes(self, show_message=True): + """Save changes happened during creation. + + Trigger save of changes using host api. This functionality does not + validate anything. It is required to do checks before this method is + called to be able to give user actionable response e.g. check of + context using 'host_context_has_changed'. + + Args: + show_message (bool): Show message that changes were + saved successfully. + + Returns: + bool: Save of changes was successful. + """ + if not self._create_context.host_is_valid: - return + # TODO remove + # Fake success save when host is not valid for CreateContext + # this is for testing as experimental feature + return True try: self._create_context.save_changes() + if show_message: + self.emit_card_message("Saved changes..") + return True except CreatorsOperationFailed as exc: self._emit_event( @@ -2056,16 +2144,17 @@ class PublisherController(BasePublisherController): } ) + return False + def remove_instances(self, instance_ids): """Remove instances based on instance ids. Args: instance_ids (List[str]): List of instance ids to remove. """ - # QUESTION Expect that instances are really removed? In that case save - # reset is not required and save changes too. - self.save_changes() + # QUESTION Expect that instances are really removed? In that case reset + # is not required. self._remove_instances_from_context(instance_ids) self._on_create_instance_change() @@ -2136,12 +2225,22 @@ class PublisherController(BasePublisherController): self._publish_comment_is_set = True def publish(self): - """Run publishing.""" + """Run publishing. + + Make sure all changes are saved before method is called (Call + 'save_changes' and check output). + """ + self._publish_up_validation = False self._start_publish() def validate(self): - """Run publishing and stop after Validation.""" + """Run publishing and stop after Validation. + + Make sure all changes are saved before method is called (Call + 'save_changes' and check output). + """ + if self.publish_has_validated: return self._publish_up_validation = True @@ -2152,10 +2251,8 @@ class PublisherController(BasePublisherController): if self.publish_is_running: return - # Make sure changes are saved - self.save_changes() - self.publish_is_running = True + self.publish_has_started = True self._emit_event("publish.process.started") diff --git a/openpype/tools/publisher/widgets/__init__.py b/openpype/tools/publisher/widgets/__init__.py index 042985b007..f18e6cc61e 100644 --- a/openpype/tools/publisher/widgets/__init__.py +++ b/openpype/tools/publisher/widgets/__init__.py @@ -4,8 +4,9 @@ from .icons import ( get_icon ) from .widgets import ( - StopBtn, + SaveBtn, ResetBtn, + StopBtn, ValidateBtn, PublishBtn, CreateNextPageOverlay, @@ -25,8 +26,9 @@ __all__ = ( "get_pixmap", "get_icon", - "StopBtn", + "SaveBtn", "ResetBtn", + "StopBtn", "ValidateBtn", "PublishBtn", "CreateNextPageOverlay", diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 3fd5243ce9..0734e1bc27 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -164,6 +164,11 @@ class BaseGroupWidget(QtWidgets.QWidget): def _on_widget_selection(self, instance_id, group_id, selection_type): self.selected.emit(instance_id, group_id, selection_type) + def set_active_toggle_enabled(self, enabled): + for widget in self._widgets_by_id.values(): + if isinstance(widget, InstanceCardWidget): + widget.set_active_toggle_enabled(enabled) + class ConvertorItemsGroupWidget(BaseGroupWidget): def update_items(self, items_by_id): @@ -437,6 +442,9 @@ class InstanceCardWidget(CardWidget): self.update_instance_values() + def set_active_toggle_enabled(self, enabled): + self._active_checkbox.setEnabled(enabled) + def set_active(self, new_value): """Set instance as active.""" checkbox_value = self._active_checkbox.isChecked() @@ -551,6 +559,7 @@ class InstanceCardView(AbstractInstanceView): self._context_widget = None self._convertor_items_group = None + self._active_toggle_enabled = True self._widgets_by_group = {} self._ordered_groups = [] @@ -667,6 +676,9 @@ class InstanceCardView(AbstractInstanceView): group_widget.update_instances( instances_by_group[group_name] ) + group_widget.set_active_toggle_enabled( + self._active_toggle_enabled + ) self._update_ordered_group_names() @@ -1091,3 +1103,10 @@ class InstanceCardView(AbstractInstanceView): self._explicitly_selected_groups = selected_groups self._explicitly_selected_instance_ids = selected_instances + + def set_active_toggle_enabled(self, enabled): + if self._active_toggle_enabled is enabled: + return + self._active_toggle_enabled = enabled + for group_widget in self._widgets_by_group.values(): + group_widget.set_active_toggle_enabled(enabled) diff --git a/openpype/tools/publisher/widgets/images/save.png b/openpype/tools/publisher/widgets/images/save.png new file mode 100644 index 0000000000000000000000000000000000000000..0db48d74ae2660e8766fc2bcd30ed794fd067068 GIT binary patch literal 3961 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 0: dialog_x -= diff @@ -549,6 +635,14 @@ class PublisherWindow(QtWidgets.QDialog): def _on_create_request(self): self._go_to_create_tab() + def _on_convert_requested(self): + if not self._save_changes(False): + return + convertor_identifiers = ( + self._overview_widget.get_selected_legacy_convertors() + ) + self._controller.trigger_convertor_items(convertor_identifiers) + def _set_current_tab(self, identifier): self._tabs_widget.set_current_tab(identifier) @@ -599,8 +693,10 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_frame.setVisible(visible) self._update_publish_frame_rect() + def _on_save_clicked(self): + self._save_changes(True) + def _on_reset_clicked(self): - self.save_changes() self.reset() def _on_stop_clicked(self): @@ -610,14 +706,17 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): - self._set_publish_comment() - self._controller.validate() + if self._save_changes(False): + self._set_publish_comment() + self._controller.validate() def _on_publish_clicked(self): - self._set_publish_comment() - self._controller.publish() + if self._save_changes(False): + self._set_publish_comment() + self._controller.publish() def _set_footer_enabled(self, enabled): + self._save_btn.setEnabled(True) self._reset_btn.setEnabled(True) if enabled: self._stop_btn.setEnabled(False) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 3398743aec..63d2945145 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -327,7 +327,7 @@ class InventoryModel(TreeModel): project_name, repre_id ) if not representation: - not_found["representation"].append(group_items) + not_found["representation"].extend(group_items) not_found_ids.append(repre_id) continue @@ -335,7 +335,7 @@ class InventoryModel(TreeModel): project_name, representation["parent"] ) if not version: - not_found["version"].append(group_items) + not_found["version"].extend(group_items) not_found_ids.append(repre_id) continue @@ -348,13 +348,13 @@ class InventoryModel(TreeModel): subset = get_subset_by_id(project_name, version["parent"]) if not subset: - not_found["subset"].append(group_items) + not_found["subset"].extend(group_items) not_found_ids.append(repre_id) continue asset = get_asset_by_id(project_name, subset["parent"]) if not asset: - not_found["asset"].append(group_items) + not_found["asset"].extend(group_items) not_found_ids.append(repre_id) continue @@ -380,11 +380,11 @@ class InventoryModel(TreeModel): self.add_child(group_node, parent=parent) - for _group_items in group_items: + for item in group_items: item_node = Item() - item_node["Name"] = ", ".join( - [item["objectName"] for item in _group_items] - ) + item_node.update(item) + item_node["Name"] = item.get("objectName", "NO NAME") + item_node["isNotFound"] = True self.add_child(item_node, parent=group_node) for repre_id, group_dict in sorted(grouped.items()): @@ -482,10 +482,10 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel): return True # Filter by regex - if hasattr(self, "filterRegularExpression"): - regex = self.filterRegularExpression() - else: + if hasattr(self, "filterRegExp"): regex = self.filterRegExp() + else: + regex = self.filterRegularExpression() pattern = regex.pattern() if pattern: pattern = re.escape(pattern) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index a04171e429..3279be6094 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -80,9 +80,16 @@ class SceneInventoryView(QtWidgets.QTreeView): self.setStyleSheet("QTreeView {}") def _build_item_menu_for_selection(self, items, menu): + + # Exclude items that are "NOT FOUND" since setting versions, updating + # and removal won't work for those items. + items = [item for item in items if not item.get("isNotFound")] + if not items: return + # An item might not have a representation, for example when an item + # is listed as "NOT FOUND" repre_ids = { item["representation"] for item in items diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 8a6e43f796..89424fd746 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -160,10 +160,10 @@ class SceneInventoryWindow(QtWidgets.QDialog): self._model.set_hierarchy_view(enabled) def _on_text_filter_change(self, text_filter): - if hasattr(self._proxy, "setFilterRegularExpression"): - self._proxy.setFilterRegularExpression(text_filter) - else: + if hasattr(self._proxy, "setFilterRegExp"): self._proxy.setFilterRegExp(text_filter) + else: + self._proxy.setFilterRegularExpression(text_filter) def _on_outdated_state_change(self): self._proxy.set_filter_outdated( diff --git a/openpype/tools/settings/local_settings/projects_widget.py b/openpype/tools/settings/local_settings/projects_widget.py index bdf291524c..4a4148d7cd 100644 --- a/openpype/tools/settings/local_settings/projects_widget.py +++ b/openpype/tools/settings/local_settings/projects_widget.py @@ -272,7 +272,7 @@ class SitesWidget(QtWidgets.QWidget): ) site_configs = sync_server_module.get_all_site_configs( - self._project_name) + self._project_name, local_editable_only=True) roots_entity = ( self.project_settings[PROJECT_ANATOMY_KEY][LOCAL_ROOTS_KEY] diff --git a/openpype/tools/settings/settings/search_dialog.py b/openpype/tools/settings/settings/search_dialog.py index 33a4d16e98..59750c02e1 100644 --- a/openpype/tools/settings/settings/search_dialog.py +++ b/openpype/tools/settings/settings/search_dialog.py @@ -27,10 +27,10 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): if not parent.isValid(): return False - if hasattr(self, "filterRegularExpression"): - regex = self.filterRegularExpression() - else: + if hasattr(self, "filterRegExp"): regex = self.filterRegExp() + else: + regex = self.filterRegularExpression() pattern = regex.pattern() if pattern and regex.isValid(): @@ -111,10 +111,10 @@ class SearchEntitiesDialog(QtWidgets.QDialog): def _on_filter_timer(self): text = self._filter_edit.text() - if hasattr(self._proxy, "setFilterRegularExpression"): - self._proxy.setFilterRegularExpression(text) - else: + if hasattr(self._proxy, "setFilterRegExp"): self._proxy.setFilterRegExp(text) + else: + self._proxy.setFilterRegularExpression(text) # WARNING This expanding and resizing is relatively slow. self._view.expandAll() diff --git a/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py index 5c72e2049b..602faaa489 100644 --- a/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py +++ b/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py @@ -5,10 +5,10 @@ from qtpy import QtCore class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): """Filters to the regex if any of the children matches allow parent""" def filterAcceptsRow(self, row, parent): - if hasattr(self, "filterRegularExpression"): - regex = self.filterRegularExpression() - else: + if hasattr(self, "filterRegExp"): regex = self.filterRegExp() + else: + regex = self.filterRegularExpression() pattern = regex.pattern() if pattern: model = self.sourceModel() diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 3007fa66a5..3ac1b4c4ad 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -247,7 +247,7 @@ class TrayPublishWindow(PublisherWindow): def _on_project_select(self, project_name): # TODO register project specific plugin paths - self._controller.save_changes() + self._controller.save_changes(False) self._controller.reset_project_data_cache() self.reset() diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index d76284afb1..fa69113ef1 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -30,9 +30,11 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): def displayText(self, value, locale): if isinstance(value, HeroVersionType): return lib.format_version(value, True) - assert isinstance(value, numbers.Integral), ( - "Version is not integer. \"{}\" {}".format(value, str(type(value))) - ) + if not isinstance(value, numbers.Integral): + # For cases where no version is resolved like NOT FOUND cases + # where a representation might not exist in current database + return + return lib.format_version(value) def paint(self, painter, option, index): diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index e8593a8ae2..ac242d24d2 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -38,7 +38,6 @@ class HostToolsHelper: self._subset_manager_tool = None self._scene_inventory_tool = None self._library_loader_tool = None - self._look_assigner_tool = None self._experimental_tools_dialog = None @property @@ -219,27 +218,6 @@ class HostToolsHelper: raise ImportError("No Pyblish GUI found") - def get_look_assigner_tool(self, parent): - """Create, cache and return look assigner tool window.""" - if self._look_assigner_tool is None: - from openpype.tools.mayalookassigner import MayaLookAssignerWindow - - mayalookassigner_window = MayaLookAssignerWindow(parent) - 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.""" - - with qt_app_context(): - look_assigner_tool = self.get_look_assigner_tool(parent) - look_assigner_tool.show() - - # Pull window to the front. - look_assigner_tool.raise_() - look_assigner_tool.activateWindow() - look_assigner_tool.showNormal() - def get_experimental_tools_dialog(self, parent=None): """Dialog of experimental tools. @@ -315,9 +293,6 @@ class HostToolsHelper: 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.") @@ -356,9 +331,6 @@ class HostToolsHelper: elif tool_name == "sceneinventory": self.show_scene_inventory(parent, *args, **kwargs) - elif tool_name == "lookassigner": - self.show_look_assigner(parent, *args, **kwargs) - elif tool_name == "publish": self.show_publish(parent, *args, **kwargs) @@ -436,10 +408,6 @@ def show_scene_inventory(parent=None): _SingletonPoint.show_tool_by_name("sceneinventory", parent) -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) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 8d38f03b8d..950c782727 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -862,11 +862,11 @@ class WrappedCallbackItem: return self._result def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ if self.done: self.log.warning("- item is already processed") diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 270e00b2ef..94645af110 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -202,11 +202,20 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): Use case: Filtering by string - parent won't be filtered if does not match the filter string but first checks if any children does. """ + + def __init__(self, *args, **kwargs): + super(RecursiveSortFilterProxyModel, self).__init__(*args, **kwargs) + recursive_enabled = False + if hasattr(self, "setRecursiveFilteringEnabled"): + self.setRecursiveFilteringEnabled(True) + recursive_enabled = True + self._recursive_enabled = recursive_enabled + def filterAcceptsRow(self, row, parent_index): - if hasattr(self, "filterRegularExpression"): - regex = self.filterRegularExpression() - else: + if hasattr(self, "filterRegExp"): regex = self.filterRegExp() + else: + regex = self.filterRegularExpression() pattern = regex.pattern() if pattern: @@ -219,8 +228,9 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): # Check current index itself value = model.data(source_index, self.filterRole()) - if re.search(pattern, value, re.IGNORECASE): - return True + matched = bool(re.search(pattern, value, re.IGNORECASE)) + if matched or self._recursive_enabled: + return matched rows = model.rowCount(source_index) for idx in range(rows): diff --git a/openpype/version.py b/openpype/version.py index e8124f1466..4d6ee5590e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.2-nightly.6" +__version__ = "3.15.4-nightly.1" diff --git a/openpype/widgets/splash_screen.py b/openpype/widgets/splash_screen.py index fffe143ea5..7c1ff72ecd 100644 --- a/openpype/widgets/splash_screen.py +++ b/openpype/widgets/splash_screen.py @@ -49,9 +49,7 @@ class SplashScreen(QtWidgets.QDialog): self.init_ui() def was_proc_successful(self) -> bool: - if self.thread_return_code == 0: - return True - return False + return self.thread_return_code == 0 def start_thread(self, q_thread: QtCore.QThread): """Saves the reference to this thread and starts it. @@ -80,8 +78,14 @@ class SplashScreen(QtWidgets.QDialog): """ self.thread_return_code = 0 self.q_thread.quit() + + if not self.q_thread.wait(5000): + raise RuntimeError("Failed to quit the QThread! " + "The deadline has been reached! The thread " + "has not finished it's execution!.") self.close() + @QtCore.Slot() def toggle_log(self): if self.is_log_visible: @@ -256,3 +260,4 @@ class SplashScreen(QtWidgets.QDialog): self.close_btn.show() self.thread_return_code = return_code self.q_thread.exit(return_code) + self.q_thread.wait() diff --git a/pyproject.toml b/pyproject.toml index 2fc4f6fe39..42ce5aa32c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1" # OpenPype +version = "3.15.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/tests/conftest.py b/tests/conftest.py index 7b58b0314d..4f7c17244b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,11 @@ def pytest_addoption(parser): help="Overwrite default timeout" ) + parser.addoption( + "--setup_only", action="store", default=None, + help="True - only setup test, do not run any tests" + ) + @pytest.fixture(scope="module") def test_data_folder(request): @@ -45,6 +50,11 @@ def timeout(request): return request.config.getoption("--timeout") +@pytest.fixture(scope="module") +def setup_only(request): + return request.config.getoption("--setup_only") + + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): # execute all other hooks to obtain the report object diff --git a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py index cd9cbb94f8..a4026f195b 100644 --- a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py +++ b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke.py @@ -71,12 +71,30 @@ class TestDeadlinePublishInNuke(NukeDeadlinePublishTestClass): failures.append( DBAssert.count_of_types(dbcon, "representation", 4)) + additional_args = {"context.subset": "workfileTest_task", + "context.ext": "nk"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + additional_args = {"context.subset": "renderTest_taskMain", "context.ext": "exr"} failures.append( DBAssert.count_of_types(dbcon, "representation", 1, additional_args=additional_args)) + additional_args = {"context.subset": "renderTest_taskMain", + "name": "thumbnail"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + + additional_args = {"context.subset": "renderTest_taskMain", + "name": "h264_mov"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + assert not any(failures) diff --git a/tests/integration/hosts/nuke/test_publish_in_nuke.py b/tests/integration/hosts/nuke/test_publish_in_nuke.py index f84f13fa20..bfd84e4fd5 100644 --- a/tests/integration/hosts/nuke/test_publish_in_nuke.py +++ b/tests/integration/hosts/nuke/test_publish_in_nuke.py @@ -15,7 +15,7 @@ class TestPublishInNuke(NukeLocalPublishTestClass): !!! It expects modified path in WriteNode, use '[python {nuke.script_directory()}]' instead of regular root - dir (eg. instead of `c:/projects/test_project/test_asset/test_task`). + dir (eg. instead of `c:/projects`). Access file path by selecting WriteNode group, CTRL+Enter, update file input !!! @@ -70,12 +70,30 @@ class TestPublishInNuke(NukeLocalPublishTestClass): failures.append( DBAssert.count_of_types(dbcon, "representation", 4)) + additional_args = {"context.subset": "workfileTest_task", + "context.ext": "nk"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + additional_args = {"context.subset": "renderTest_taskMain", "context.ext": "exr"} failures.append( DBAssert.count_of_types(dbcon, "representation", 1, additional_args=additional_args)) + additional_args = {"context.subset": "renderTest_taskMain", + "name": "thumbnail"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + + additional_args = {"context.subset": "renderTest_taskMain", + "name": "h264_mov"} + failures.append( + DBAssert.count_of_types(dbcon, "representation", 1, + additional_args=additional_args)) + assert not any(failures) diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py index 2bafa16971..300024dc98 100644 --- a/tests/lib/testing_classes.py +++ b/tests/lib/testing_classes.py @@ -243,6 +243,8 @@ class PublishTest(ModuleUnitTest): PERSIST = True # True - keep test_db, test_openpype, outputted test files TEST_DATA_FOLDER = None # use specific folder of unzipped test file + SETUP_ONLY = False + @pytest.fixture(scope="module") def app_name(self, app_variant): """Returns calculated value for ApplicationManager. Eg.(nuke/12-2)""" @@ -286,8 +288,13 @@ class PublishTest(ModuleUnitTest): @pytest.fixture(scope="module") def launched_app(self, dbcon, download_test_data, last_workfile_path, - startup_scripts, app_args, app_name, output_folder_url): + startup_scripts, app_args, app_name, output_folder_url, + setup_only): """Launch host app""" + if setup_only or self.SETUP_ONLY: + print("Creating only setup for test, not launching app") + yield + return # set schema - for integrate_new from openpype import PACKAGE_DIR # Path to OpenPype's schema @@ -316,8 +323,12 @@ class PublishTest(ModuleUnitTest): @pytest.fixture(scope="module") def publish_finished(self, dbcon, launched_app, download_test_data, - timeout): + timeout, setup_only): """Dummy fixture waiting for publish to finish""" + if setup_only or self.SETUP_ONLY: + print("Creating only setup for test, not launching app") + yield False + return import time time_start = time.time() timeout = timeout or self.TIMEOUT @@ -334,11 +345,16 @@ class PublishTest(ModuleUnitTest): def test_folder_structure_same(self, dbcon, publish_finished, download_test_data, output_folder_url, - skip_compare_folders): + skip_compare_folders, + setup_only): """Check if expected and published subfolders contain same files. Compares only presence, not size nor content! """ + if setup_only or self.SETUP_ONLY: + print("Creating only setup for test, not launching app") + return + published_dir_base = output_folder_url expected_dir_base = os.path.join(download_test_data, "expected") diff --git a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py index 5c2d7b367f..88e0095e34 100644 --- a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py +++ b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py @@ -135,7 +135,7 @@ class TestPipelinePublishPlugins(TestPipeline): } # load plugin function for testing - plugin = publish_plugins.ExtractorColormanaged() + plugin = publish_plugins.ColormanagedPyblishPluginMixin() plugin.log = log config_data, file_rules = plugin.get_colorspace_settings(context) @@ -175,14 +175,14 @@ class TestPipelinePublishPlugins(TestPipeline): } # load plugin function for testing - plugin = publish_plugins.ExtractorColormanaged() + plugin = publish_plugins.ColormanagedPyblishPluginMixin() plugin.log = log plugin.set_representation_colorspace( representation_nuke, context, colorspace_settings=(config_data_nuke, file_rules_nuke) ) # load plugin function for testing - plugin = publish_plugins.ExtractorColormanaged() + plugin = publish_plugins.ColormanagedPyblishPluginMixin() plugin.log = log plugin.set_representation_colorspace( representation_hiero, context, diff --git a/website/docs/admin_environment.md b/website/docs/admin_environment.md index 1eb755b90b..29b70a6c47 100644 --- a/website/docs/admin_environment.md +++ b/website/docs/admin_environment.md @@ -27,4 +27,4 @@ import TabItem from '@theme/TabItem'; - for more details on how to use it go [here](admin_use#check-for-mongodb-database-connection) ## OPENPYPE_USERNAME -- if set it overides system created username +- if set it overrides system created username diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md index 0e77f29fc2..68b402c5e0 100644 --- a/website/docs/admin_hosts_maya.md +++ b/website/docs/admin_hosts_maya.md @@ -6,13 +6,13 @@ sidebar_label: Maya ## Publish Plugins -### Render Settings Validator +### Render Settings Validator `ValidateRenderSettings` Render Settings Validator is here to make sure artists will submit renders -we correct settings. Some of these settings are needed by OpenPype but some -can be defined by TD using [OpenPype Settings UI](admin_settings.md). +with the correct settings. Some of these settings are needed by OpenPype but some +can be defined by the admin using [OpenPype Settings UI](admin_settings.md). OpenPype enforced settings include: @@ -36,10 +36,9 @@ For **Renderman**: For **Arnold**: - there shouldn't be `` token when merge AOVs option is turned on - Additional check can be added via Settings - **Project Settings > Maya > Publish plugin > ValidateRenderSettings**. You can add as many options as you want for every supported renderer. In first field put node type and attribute -and in the second required value. +and in the second required value. You can create multiple values for an attribute, but when repairing it'll be the first value in the list that get selected. ![Settings example](assets/maya-admin_render_settings_validator.png) @@ -51,7 +50,11 @@ just one instance of this node type but if that is not so, validator will go thr instances and check the value there. Node type for **VRay** settings is `VRaySettingsNode`, for **Renderman** it is `rmanGlobals`, for **Redshift** it is `RedshiftOptions`. -### Model Name Validator +:::info getting attribute values +If you do not know what an attributes value is supposed to be, for example for dropdown menu (enum), try changing the attribute and look in the script editor where it should log what the attribute was set to. +::: + +### Model Name Validator `ValidateRenderSettings` @@ -95,7 +98,7 @@ You can set various aspects of scene submission to farm with per-project setting - **Optional** will mark sumission plugin optional - **Active** will enable/disable plugin - - **Tile Assembler Plugin** will set what should be used to assemble tiles on Deadline. Either **Open Image IO** will be used + - **Tile Assembler Plugin** will set what should be used to assemble tiles on Deadline. Either **Open Image IO** will be used or Deadlines **Draft Tile Assembler**. - **Use Published scene** enable to render from published scene instead of scene in work area. Rendering from published files is much safer. - **Use Asset dependencies** will mark job pending on farm until asset dependencies are fulfilled - for example Deadline will wait for scene file to be synced to cloud, etc. @@ -107,6 +110,41 @@ or Deadlines **Draft Tile Assembler**. This is useful to fix some specific renderer glitches and advanced hacking of Maya Scene files. `Patch name` is label for patch for easier orientation. `Patch regex` is regex used to find line in file, after `Patch line` string is inserted. Note that you need to add line ending. +### Extract Playblast Settings (review) +These settings provide granular control over how the playblasts or reviews are produced in Maya. + +Some of these settings are also available on the instance itself, in which case these settings will become the default value when creating the review instance. + +![Extract Playblast Settings](assets/maya-admin_extract_playblast_settings.png) + +- **Compression type** which file encoding to use. +- **Data format** what format is the file encoding. +- **Quality** lets you control the compression value for the output. Results can vary depending on the compression you selected. Quality values can range from 0 to 100, with a default value of 95. +- **Background Color** the viewports background color. +- **Background Bottom** the viewports background bottom color. +- **Background Top** the viewports background top color. +- **Override display options** override the viewports display options to use what is set in the settings. +- **Isolate view** isolates the view to what is in the review instance. If only a camera is present in the review instance, all nodes are displayed in view. +- **Off Screen** records the playblast hidden from the user. +- **2D Pan/Zoom** enables the 2D Pan/Zoom functionality of the camera. +- **Renderer name** which renderer to use for playblasting. +- **Width** width of the output resolution. If this value is `0`, the asset's width is used. +- **Height** height of the output resolution. If this value is `0`, the asset's height is used. + +#### Viewport Options + +Most settings to override in the viewport are self explanatory and can be found in Maya. + +![Extract Playblast Settings](assets/maya-admin_extract_playblast_settings_viewport_options.png) + +- **Override Viewport Options** enable to use the settings below for the viewport when publishing the review. + +#### Camera Options + +These options are set on the camera shape when publishing the review. They correspond to attributes on the Maya camera shape node. + +![Extract Playblast Settings](assets/maya-admin_extract_playblast_settings_camera_options.png) + ## Custom Menu You can add your custom tools menu into Maya by extending definitions in **Maya -> Scripts Menu Definition**. ![Custom menu definition](assets/maya-admin_scriptsmenu.png) @@ -142,7 +180,7 @@ Fill in the necessary fields (the optional fields are regex filters) ![new place holder](assets/maya-placeholder_new.png) - - Builder type: Wether the the placeholder should load current asset representations or linked assets representations + - Builder type: Whether the the placeholder should load current asset representations or linked assets representations - Representation: Representation that will be loaded (ex: ma, abc, png, etc...) @@ -169,5 +207,3 @@ Fill in the necessary fields (the optional fields are regex filters) - Build your workfile ![maya build template](assets/maya-build_workfile_from_template.png) - - diff --git a/website/docs/admin_settings.md b/website/docs/admin_settings.md index 8626ef16ba..1128cc00c6 100644 --- a/website/docs/admin_settings.md +++ b/website/docs/admin_settings.md @@ -7,12 +7,15 @@ sidebar_label: Working with settings import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -OpenPype stores all of it's settings and configuration in the mongo database. To make the configuration as easy as possible we provide a robust GUI where you can access and change everything that is configurable +OpenPype stores all of its settings and configuration in the mongo database. To make the configuration as easy as possible we provide a robust GUI where you can access and change everything that is configurable **Settings** GUI can be started from the tray menu *Admin -> Studio Settings*. -Please keep in mind that these settings are set-up for the full studio and not per-individual. If you're looking for individual artist settings, you can head to -[Local Settings](admin_settings_local.md) section in the artist documentation. +:::important Studio Settings versus Local Settings +Please keep in mind that these settings are set up for the full studio and not per-individual. If you're looking for individual artist settings, you can head to +[Local Settings](admin_settings_local.md) section in the documentation. +::: + ## Categories @@ -76,7 +79,7 @@ You can also reset any settings to OpenPype default by doing `right click` and ` Many settings are useful to be adjusted on a per-project basis. To identify project overrides, they are marked with **orange edge** and **orange labels** in the settings GUI. -The process of settting project overrides is similar to setting the Studio defaults. The key difference is to select a particular project you want to be configure. Those projects can be found on the left hand side of the Project Settings tab. +The process of setting project overrides is similar to setting the Studio defaults. The key difference is to select a particular project you want to be configure. Those projects can be found on the left hand side of the Project Settings tab. In the image below you can see all three overrides at the same time. 1. Deadline has **no changes to the OpenPype defaults** at all — **grey** colour of left bar. diff --git a/website/docs/admin_use.md b/website/docs/admin_use.md index c92ac3a77a..c1d1de0e8c 100644 --- a/website/docs/admin_use.md +++ b/website/docs/admin_use.md @@ -68,7 +68,7 @@ Add `--headless` to run OpenPype without graphical UI (useful on server or on au `--verbose` `` - change log verbose level of OpenPype loggers. -Level value can be integer in range `0-50` or one of enum strings `"notset" (0)`, `"debug" (10)`, `"info" (20)`, `"warning" (30)`, `"error" (40)`, `"ciritcal" (50)`. Value is stored to `OPENPYPE_LOG_LEVEL` environment variable for next processes. +Level value can be integer in range `0-50` or one of enum strings `"notset" (0)`, `"debug" (10)`, `"info" (20)`, `"warning" (30)`, `"error" (40)`, `"critical" (50)`. Value is stored to `OPENPYPE_LOG_LEVEL` environment variable for next processes. ```shell openpype_console --verbose debug diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index 71ba8785dc..12c1f40181 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -47,7 +47,7 @@ This is the core functional area for you as a user. Most of your actions will ta ![Menu OpenPype](assets/3dsmax_menu_first_OP.png) :::note OpenPype Menu -User should use this menu exclusively for **Opening/Saving** when dealing with work files not standard ```File Menu``` even though user still being able perform file operations via this menu but prefferably just performing quick saves during work session not saving actual workfile versions. +User should use this menu exclusively for **Opening/Saving** when dealing with work files not standard ```File Menu``` even though user still being able perform file operations via this menu but preferably just performing quick saves during work session not saving actual workfile versions. ::: ## Working With Scene Files @@ -73,7 +73,7 @@ OpenPype correctly names it and add version to the workfile. This basically happ etc. -Basically meaning user is free of guessing what is the correct naming and other neccessities to keep everthing in order and managed. +Basically meaning user is free of guessing what is the correct naming and other necessities to keep everything in order and managed. > Note: user still has also other options for naming like ```Subversion```, ```Artist's Note``` but we won't dive into those now. diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index a9c9ca49fa..939ef4034c 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -34,7 +34,7 @@ a correct name. You should use it instead of standard file saving dialog. In AfterEffects you'll find the tools in the `OpenPype` extension: -![Extension](assets/photoshop_extension.PNG) +![Extension](assets/photoshop_extension.png) You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`. @@ -104,7 +104,7 @@ There are currently 2 options of `render` item: When you want to load existing published work, you can use the `Loader` tool. You can reach it in the extension's panel. -![Loader](assets/photoshop_loader.PNG) +![Loader](assets/photoshop_loader.png) The supported families for loading into AfterEffects are: @@ -128,7 +128,7 @@ Now that we have some content loaded, you can manage which version is loaded. Th Loaded images have to stay as smart layers in order to be updated. If you rasterize the layer, you can no longer update it to a different version using OpenPype tools. ::: -![Loader](assets/photoshop_manage.PNG) +![Loader](assets/photoshop_manage.png) You can switch to a previous version of the image or update to the latest. diff --git a/website/docs/artist_hosts_harmony.md b/website/docs/artist_hosts_harmony.md index aa9355e221..eaa6edc42a 100644 --- a/website/docs/artist_hosts_harmony.md +++ b/website/docs/artist_hosts_harmony.md @@ -44,7 +44,7 @@ Because the saving to the network location happens in the background, be careful `OpenPype > Create` -![Creator](assets/harmony_creator.PNG) +![Creator](assets/harmony_creator.png) These are the families supported in Harmony: diff --git a/website/docs/artist_hosts_hiero.md b/website/docs/artist_hosts_hiero.md index 136ed6ea9c..977c7a7baf 100644 --- a/website/docs/artist_hosts_hiero.md +++ b/website/docs/artist_hosts_hiero.md @@ -231,14 +231,14 @@ All published instances that will replace the place holder must contain unique i ![Create menu](assets/nuke_publishedinstance.png) -The informations about these objects are given by the user by filling the extra attributes of the Place Holder +The information about these objects are given by the user by filling the extra attributes of the Place Holder ![Create menu](assets/nuke_fillingExtraAttributes.png) ### Update Place Holder -This tool alows the user to change the information provided in the extra attributes of the selected Place Holder. +This tool allows the user to change the information provided in the extra attributes of the selected Place Holder. ![Create menu](assets/nuke_updatePlaceHolder.png) @@ -250,7 +250,7 @@ This tool imports the template used and replaces the existed PlaceHolders with t ![Create menu](assets/nuke_buildWorfileFromTemplate.png) #### Result -- Replace `PLACEHOLDER` node in the template with the published instance corresponding to the informations provided in extra attributes of the Place Holder +- Replace `PLACEHOLDER` node in the template with the published instance corresponding to the information provided in extra attributes of the Place Holder ![Create menu](assets/nuke_buildworkfile.png) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 07495fbc96..0a551f0213 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -230,8 +230,8 @@ Maya settings concerning framerate, resolution and frame range are handled by OpenPype. If set correctly in Ftrack, Maya will validate you have correct fps on scene save and publishing offering way to fix it for you. -For resolution and frame range, use **OpenPype → Reset Frame Range** and -**OpenPype → Reset Resolution** +For resolution and frame range, use **OpenPype → Set Frame Range** and +**OpenPype → Set Resolution** ## Creating rigs with OpenPype @@ -386,7 +386,7 @@ Lets start with empty scene. First I'll pull in my favorite Buddha model. there just click on **Reference (abc)**. Next, I want to be sure that I have same frame range as is set on shot I am working -on. To do this just **OpenPype → Reset Frame Range**. This should set Maya timeline to same +on. To do this just **OpenPype → Set Frame Range**. This should set Maya timeline to same values as they are set on shot in *Ftrack* for example. I have my time set, so lets create some animation. We'll turn Buddha model around for @@ -500,7 +500,7 @@ and for vray: maya// ``` -Doing **OpenPype → Reset Resolution** will set correct resolution on camera. +Doing **OpenPype → Set Resolution** will set correct resolution on camera. Scene is now ready for submission and should publish without errors. @@ -516,6 +516,22 @@ In the scene from where you want to publish your model create *Render subset*. P model subset (Maya set node) under corresponding `LAYER_` set under *Render instance*. During publish, it will submit this render to farm and after it is rendered, it will be attached to your model subset. +### Tile Rendering +:::note Deadline +This feature is only supported when using Deadline. See [here](module_deadline#openpypetileassembler-plugin) for setup. +::: +On the render instance objectset you'll find: + +* `Tile Rendering` - for enabling tile rendering. +* `Tile X` - number of tiles in the X axis. +* `Tile Y` - number of tiles in the Y axis. + +When submittig to Deadline, you'll get: + +- for each frame a tile rendering job, to render each from Maya. +- for each frame a tile assembly job, to assemble the rendered tiles. +- job to publish the assembled frames. + ## Render Setups ### Publishing Render Setups diff --git a/website/docs/artist_hosts_maya_arnold.md b/website/docs/artist_hosts_maya_arnold.md index b3c02a0894..da16ba66c0 100644 --- a/website/docs/artist_hosts_maya_arnold.md +++ b/website/docs/artist_hosts_maya_arnold.md @@ -6,7 +6,7 @@ sidebar_label: Arnold ## Arnold Scene Source (.ass) Arnold Scene Source can be published as a single file or a sequence of files, determined by the frame range. -When creating the instance, two objectsets are created; `content` and `proxy`. Meshes in the `proxy` objectset will be the viewport representation when loading as `standin`. Proxy representations are stored as `resources` of the subset. +When creating the instance, two objectsets are created; `content` and `proxy`. Meshes in the `proxy` objectset will be the viewport representation when loading as `standin`. ### Arnold Scene Source Proxy Workflow In order to utilize operators and proxies, the content and proxy nodes need to share the same names (including the shape names). This is done by parenting the content and proxy nodes into separate groups. For example: diff --git a/website/docs/artist_hosts_maya_yeti.md b/website/docs/artist_hosts_maya_yeti.md index f5a6a4d2c9..aa783cc8b6 100644 --- a/website/docs/artist_hosts_maya_yeti.md +++ b/website/docs/artist_hosts_maya_yeti.md @@ -9,7 +9,9 @@ sidebar_label: Yeti OpenPype can work with [Yeti](https://peregrinelabs.com/yeti/) in two data modes. It can handle Yeti caches and Yeti rigs. -### Creating and publishing Yeti caches +## Yeti Caches + +### Creating and publishing Let start by creating simple Yeti setup, just one object and Yeti node. Open new empty scene in Maya and create sphere. Then select sphere and go **Yeti → Create Yeti Node on Mesh** @@ -44,7 +46,15 @@ You can now publish Yeti cache as any other types. **OpenPype → Publish**. It create sequence of `.fur` files and `.fursettings` metadata file with Yeti node setting. -### Loading Yeti caches +:::note Collect Yeti Cache failure +If you encounter **Collect Yeti Cache** failure during collecting phase, and the error is like +```fix +No object matches name: pgYetiMaya1Shape.cbId +``` +then it is probably caused by scene not being saved before publishing. +::: + +### Loading You can load Yeti cache by **OpenPype → Load ...**. Select your cache, right+click on it and select **Load Yeti cache**. This will create Yeti node in scene and set its @@ -52,26 +62,39 @@ cache path to point to your published cache files. Note that this Yeti node will be named with same name as the one you've used to publish cache. Also notice that when you open graph on this Yeti node, all nodes are as they were in publishing node. -### Creating and publishing Yeti Rig +## Yeti Rigs -Yeti Rigs are working in similar way as caches, but are more complex and they deal with -other data used by Yeti, like geometry and textures. +### Creating and publishing -Let's start by [loading](artist_hosts_maya.md#loading-model) into new scene some model. -I've loaded my Buddha model. +Yeti Rigs are designed to connect to published models or animation rig. The workflow gives the Yeti Rig full control on that geometry to do additional things on top of whatever input comes in, e.g. deleting faces, pushing faces in/out, subdividing, etc. -Create select model mesh, create Yeti node - **Yeti → Create Yeti Node on Mesh** and -setup similar Yeti graph as in cache example above. +Let's start with a [model](artist_hosts_maya.md#loading-model) or [rig](artist_hosts_maya.md#loading-rigs) loaded into the scene. Here we are using a simple rig. -Then select this Yeti node (mine is called with default name `pgYetiMaya1`) and -create *Yeti Rig instance* - **OpenPype → Create...** and select **Yeti Cache**. +![Maya - Yeti Simple Rig](assets/maya-yeti_simple_rig.png) + +We'll need to prepare the scene a bit. We want some Yeti hair on the ball geometry, so duplicating the geometry, adding the Yeti hair and grouping it together. + +![Maya - Yeti Hair Setup](assets/maya-yeti_hair_setup.png) + +:::note yeti nodes and types +You can use any number of Yeti nodes and types, but they have to have unique names. +::: + +Now we need to connect the Yeti Rig with the animation rig. Yeti Rigs work by publishing the attribute connections from its input nodes and reconnect them later in the pipeline. This means we can only use attribute connections to from outside of the Yeti Rig hierarchy. Internal to the Yeti Rig hierarchy, we can use any complexity of node connections. We'll connnect the Yeti Rig geometry to the animation rig, with the transform and mesh attributes. + +![Maya - Yeti Rig Setup](assets/maya-yeti_rig_setup.png) + +Now we are ready for publishing. Select the Yeti Rig group (`rig_GRP`) and +create *Yeti Rig instance* - **OpenPype → Create...** and select **Yeti Rig**. Leave `Use selection` checked. -Last step is to add our model geometry to rig instance, so middle+drag its -geometry to `input_SET` under `yetiRigDefault` set representing rig instance. +Last step is to add our geometry to the rig instance, so middle+drag its +geometry to `input_SET` under the `yetiRigMain` set representing rig instance. Note that its name can differ and is based on your subset name. -![Maya - Yeti Rig Setup](assets/maya-yeti_rig.jpg) +![Maya - Yeti Publish Setup](assets/maya-yeti_publish_setup.png) + +You can have any number of nodes in the Yeti Rig, but only nodes with incoming attribute connections from outside of the Yeti Rig hierarchy is needed in the `input_SET`. Save your scene and ready for publishing our new simple Yeti Rig! @@ -81,28 +104,14 @@ the beginning of your timeline. It will also collect all textures used in Yeti node, copy them to publish folder `resource` directory and set *Image search path* of published node to this location. -:::note Collect Yeti Cache failure -If you encounter **Collect Yeti Cache** failure during collecting phase, and the error is like -```fix -No object matches name: pgYetiMaya1Shape.cbId -``` -then it is probably caused by scene not being saved before publishing. -::: +### Loading -### Loading Yeti Rig - -You can load published Yeti Rigs as any other thing in OpenPype - **OpenPype → Load ...**, +You can load published Yeti Rigs in OpenPype with **OpenPype → Load ...**, select you Yeti rig and right+click on it. In context menu you should see -**Load Yeti Cache** and **Load Yeti Rig** items (among others). First one will -load that one frame cache. The other one will load whole rig. +**Load Yeti Rig** item (among others). -Notice that although we put only geometry into `input_SET`, whole hierarchy was -pulled inside also. This allows you to store complex scene element along Yeti -node. +To connect the Yeti Rig with published animation, we'll load in the animation and use the Inventory to establish the connections. -:::tip auto-connecting rig mesh to existing one -If you select some objects before loading rig it will try to find shapes -under selected hierarchies and match them with shapes loaded with rig (published -under `input_SET`). This mechanism uses *cbId* attribute on those shapes. -If match is found shapes are connected using their `outMesh` and `outMesh`. Thus you can easily connect existing animation to loaded rig. -::: +![Maya - Yeti Publish Setup](assets/maya-yeti_load_connections.png) + +The Yeti Rig should now be following the animation. :tada: diff --git a/website/docs/artist_hosts_photoshop.md b/website/docs/artist_hosts_photoshop.md index 88bfb1484d..12203a5c6e 100644 --- a/website/docs/artist_hosts_photoshop.md +++ b/website/docs/artist_hosts_photoshop.md @@ -75,7 +75,7 @@ enabled instances, you could see more information after clicking on `Details` ta ![Image instances creates](assets/photoshop_publish_validations.png) -In this dialog you could see publishable instances in left colummn, triggered plugins in the middle and logs in the right column. +In this dialog you could see publishable instances in left column, triggered plugins in the middle and logs in the right column. In left column you could see that `review` instance was created automatically. This instance flattens all publishable instances or all visible layers if no publishable instances were created into single image which could serve as a single reviewable element (for example in Ftrack). diff --git a/website/docs/artist_tools_sync_queu.md b/website/docs/artist_tools_sync_queu.md index 770c2f77ad..7dac8638f9 100644 --- a/website/docs/artist_tools_sync_queu.md +++ b/website/docs/artist_tools_sync_queu.md @@ -2,7 +2,7 @@ id: artist_tools_sync_queue title: Sync Queue sidebar_label: Sync Queue -description: Track sites syncronization progress. +description: Track sites synchronization progress. --- # Sync Queue diff --git a/website/docs/assets/harmony_creator.PNG b/website/docs/assets/harmony_creator.png similarity index 100% rename from website/docs/assets/harmony_creator.PNG rename to website/docs/assets/harmony_creator.png diff --git a/website/docs/assets/integrate_kitsu_note_settings.png b/website/docs/assets/integrate_kitsu_note_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..9c59f0bf5cc9a748de1a8b66b248f539ee8014b5 GIT binary patch literal 48874 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

KZ;uX*}2 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

_?B@ssfQYlBAHF=3EyzE&_=Z2gg)O{M_nh8~ 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�lF z3v}%-a*KhmUwz_Wgu1>Q(BJ-w9?PAou;fxSU`r9$p#_)iA<@r11Tvsk)OhOOfdI0Z z#t8BoE41vKMk^dL(+B61@;Ps0K^LtwC%Qnxh*D{7FuMRXaWNnAMsmyxutmZlJ?DF{ zPu7pMq^VgMYZBGW(;p9{az9d}pm4*0Aq7Byh9l)m%gVkQ-987;Wdh;0fYE2WaMJp` z(f4nxvwf2s7oY~pTS?W!Z&BwY>uPF2QF{6~AN;tE*>QPVuub`$JlNd>^$z$@zm9KP zFRIywgDN(ypGGW{g-M3x=aFB_ORy&BY`V`RLJt<>OL+T*Y5r6cPv8^^ezTTIf^+=m zyQ>CkvfZu%Xr6(!?UgF(uW~_`dK%vLbl;I_2_Bfayd1Ms>kv>xDTz4xk7CF4R`k}vS<3$p9{;7_0sHH(bpB4e|G%Rc fw&7b=Cmv8OJ)uU~wvf`no{Qp&`t?g-pX>iGR($zU literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-admin_extract_playblast_settings_camera_options.png b/website/docs/assets/maya-admin_extract_playblast_settings_camera_options.png new file mode 100644 index 0000000000000000000000000000000000000000..1a5561328e056fe03278f78c56ea6b837a384e56 GIT binary patch literal 16732 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-admin_extract_playblast_settings_viewport_options.png b/website/docs/assets/maya-admin_extract_playblast_settings_viewport_options.png new file mode 100644 index 0000000000000000000000000000000000000000..5b801775d32338ea9dcb051d2b01a008697c7bd8 GIT binary patch literal 1064191 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%0AJpOqGmRpk29oF=8t0UWz?Ldih)A1?YQ79f+ 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 literal 11220 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 MZWh2!_Zu@zC-RKjZe8Y@R=?oKtf2P(R@GSfdH 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

6UB=BTN2JGQDhT}p%k(-!~F@xZG> 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≦Kv8 z&cL9RwuB3sZ=1O>z`4qPZEP| zPBEIyV@pUawAS58Wh8`=kJ6hdJZ?2Yq0DQh+=F$ZL40ZOSD4g!*ooI*n`0#!h0DYfkU& zc4h{@30HZ`f8eUHu>ukgmSt#g4VWsefALspF3*^4&61>OnujsTRgzK7bI#tI zbA)iRj%y)ghH}Ez5dtNoxSm}8p6OjsaW;aR^h%2!gq4t^H_P)qbp@OEXgV4{dttFKn@a|RPtgol8%KH@73K**0#1G zwKU?a0K-~wQR$KElPLdo!hU*i_gu5&V&gB%>VR-rCT>3&R(2<)gJi=Hh%6Y15jh=~ zgZ6E)7*U>9awS9oP9*?@aFL#Aaog!VaiY?J%@ z<=$O=Cf`fjA>M0aNSEvpT-Egw#MvKw>gwzuq2e8+aWqrdm4q?Ai}KxXz|T(dBx`AV z%TCY8rEV9}uqI9;_1<7)H5(%h(z}=rRurpzcGhZ&y)LZib^yfSZPzJwS!W+#IJ8Dw zZ#cB`H5`JxSkFBFhQ3G&IfimMg_k7gYJDv5;Riz7Uk7T!%h0V7AM{_g;7aPhAiA1P z2qL;~gRqvknqF|a-i70VGzRIx5@)dQ`Q5xhOq3RO=~ZFN^}SFn5;_Tg`&VyivKC^8 z?GvnZm*tI7)*+mn)MVyylY7?{=z&RZr0{_(ep=7Obq@=_@{IBeU5i3NWG3q%AgKSGHoLt@Ph^VP`zS_?)GHF`#mhDy%3d7}Z6{pp zoOSOarcfWGjS;}7(E%?<<&MrM8a~qzLLf>z?B@dgdEWcF`!5&vs1x$HJ4LkQGGt`e z_r42T!or%_)5aGqONln$zgwBEr~)emp1k=PzCC%57O^Rum1ZXed61`iB%yG@aYQ~9 zJN=tQtZtQS<55dxW7hd{-Rs`l-2H=s#_5p3nWmhd_WN*U6YDn70Q1?*upz7l6%B44(dg=K-pA#f@rvgoo`uKcIxE#hyR9&mk4 zD?omD(S@sag_!u(h9Z}R(k zzMN#C8GL-MyxP4o>p9bt4{HZne@BodEq9emvhUf3nYAiI{gc!z@I`-op<=toQbfg zJ*zatdsc@ldnEIr<$6_w)fzZAe?0F4aBB&0&-De$Hy#}~tWKppc4>a7X{55j{qw1x z!;p=pQ<(=TH#}tXRIWzW=V|ftuExsQQGVszWHgdzpqFcq20=|dyyCMoIwS$wah`pL z%Q8+j%mgt_zYX@jdWQM^{9K^>VW!kKmb)hT>m8;D%(lREl@G%YhsUq2s=oPVH0*+E z6arTl!vwaD3rx$I>$Pmy#|&Scb`nzc{XbHIp&oBM$|0?GcF8;)6NvL5J;EZ^;)f-R%N2?;K}Ahx05omico3RPvCw-8 zs+a4im5{zQ8qA$0r@5s1ArwNjacn{WcwLCFxcqlZ1^&H}Bu$bs(81o)pO^%W+0K;Vu&w$t`@4{r@39Yn7eIFDxt{>>r!qfw255{Kk6`KH6{1T zvV%C7#ozKjJ2`M6V=)i(y9m5()vg63co>Pjh1J8px=o?GFC>*5MC1tqZnKzQ@-kNQ z>k?B;kzL78#)n^Z06zoq-N_d~^?8q$Xu~dT%7XFO^YLN*ZO6};*U)h(&o7f}GiUW7 zcj=I;&JiwSwYce#PkVeN!?WILP1q}yIQ5Q$dVWANbHFql@UNxHh*^GY$ z1Bd^d7{GBVTNS_65n5qFn$o-ffMVtA$3X$97U5|=^q{;$)fXGKgMl+#+M^sn^k* zh<$?-92Yc0LQ|&uO{-?DK#ks8uI~#G!6H#5hB)txOewNmL zsTW&}QortbW|~j&^~33N|2W9O0}kJNqZv_!vX5&ND$MdT3&Hk)WX2m>9d9@6q#)|; z9(Yklu(2cF&_#bKh%5Q)is2X{`-i)jM?$ril=IBsw9ArFPCJcnMVYgE1McUeV;@oz z*{>xp@0N`CIjlaPEB2HFB@Zv$fqJ0GdWKamUvWKC(fN~B5VE2eL5B1a+_Zb2V?4{H zk&@VxDlTYL<6>43aJan$(7$ir-g1Sl=lFWeegZX1t>5}Qcp?%(p;$Y+dANh+7&ocp zQ8BJ6tc>ji0cnXKD?9w(#j<8jdm?`)l7_!EiniIUt#ab{Ie;O{qG@1d^ceP=p{q(| ztw`QCnpF`^WQz*)aXePRPlxN@Erzo0uC!bqp&0JVma6@T2nseWl0P&)0$mdJR*5;itsj3i}F?Ui%=BF=%BJl@p|AF zo=fB`T3D%T`i9=enmS)%s9k;Sq59QaS!DSqQ<{c80{9gXgIoGgb!7~T&Yc=ms9x_|c3vhE}|SiQC^J0cw-Q#u5cMInHy*sst>5CE_PnP;J@*8QDT|o!Q)+WnAS_q`ws)A+QxN#2*(guvhprxd zV^sN+Q?N1qlg}GByt_@QSLrfaL{b9v6}?QJ`_mOUw;{tT$PMoAPi8=R?qkzuiTKm+ z<~ZDqXe(p{OF>xa&>5W*FyyIo5^RYrdVJT(KuKQL4 z=ycV$ zF*fCFci#}5(3_;hbkuKG#wlnJ?b<0kd5Vgy#GtAiw3_&S@(F{YL-yvixDNWMF<* z&oQxSc?=mN#0td?(12uxN{g9H+kvfgpT|3P`!jEBQxA(=|8LHwge`~eT3`?~OL@Sz z`zyX~bf>w%P3*UicP;YQmeLSnJF<(_lCGYPlYN(&<6bj8AcRC+&78WlH$d zyl@AyGTv7uTD*n~-f{B-?t4S~S_U(rL_=ER+zCKprmcA)a?5nL&Q8;rb;&lvuPcv| zts?m1{A}C#Y!UTZnyZ#!wsE|FmY9GvZB=6Qo!r$`sX;t-$MF61I}g>_BYd-@0Fy@% z)n2Cfl7@&^@k%vQOfsut`{g^7T!E*X`r9Ti5tIktG9i>UzPIR|!KcTqh=PI(5H%HM zlT!`ZMgRC#s($z|9YVm$CZM<4K9lO5d-}$$O=25FbUe{eQ;y#MmaQB;H70v7^3X+- z-{L6h{`4K^?;Q-MJqug8&u;Qft%4{l1B@5d7URTq?+1z~cC^mp z09toV>f#n<_Q$hedERoR9@Y!~@A7<+*SY>nSi2mv7hX||A> z2%*Fx1x$|!JVwH^g@6;AB1gUZNCH$l1r93w?@AO<_7d{dhQlG+Rm%D)+#QfeTDXa& z($Dl(Br)w+N}}162~V!x$A+OKU1>Q^Qx-USqM8rjmG>(^0ymd&Jj?-T{aj9A43jHe z<_Da~>^-T#nb)Ii7I=cO_c?uAql*`Y$(0#S({YA`S7^&Z+WdO3-=_ql{TJ%;AC@Z; z)*mny*yDLT)hyhi0%exo>)B&7l~3wyd`Mc%YT~ES$Y#}gr35D#ZVzkn3|7HA(p)J3 zd}KJQ@@v_q{~s3cA2SuUufi75H|j%SOc0yoA~;ngu{Fvj{F=0Z@kE&G<@Cx~_Kk+g zx;ZL`*OiG2?QoObXv;W{ zo6KgfpYUtB8 z4dLiYTpj88N@g-pdo?V>kvy|_G6v-`xYCpl6&sa3kwjkRGB0}`q21;}oKE$3jODQS zhaS6aspSqFpTHEOKkf~!1EU2U#=||?=jAsszYkPjAMeY|Pfqs{W(HmRz4}8z-O5Vt zp1V?9FjDZ{up%oz_w9C{G5w}UbNn_%!X{_w=!UNKKg9?4Q9iYQ9tNi{0LV{3I+M_~ zcrg))Rg}3n-G`lo)*Rdu53ii0-BoFAwd$KZrjAl+Rf|>!=R6%TT_9I4HBT-8SRTc5 zTI2Qz;zi}`)f=$bAytE-4$>C`@}6NdOo|HxV`435rz$^v%=-I?B56K$Q=V{?!`mY= zl(NA)!`CH)==2V7Vd&SgEa2ls*_TM4B(aY4vFy_KQK4`GvZ3& z;AAjdd2*TC!C2y0B+~9yh)NoSCR#&mTejF>i7-&Dr1@!~6HWEVW4R|w;GHv@VB*!@ zUsYxM^Z>v}N9`!0<|1q}Ju@kJ%zcr@tsm(QzpUH8aL;U7PSh#$SRA6fUliv<={Pwc z9Ac5%@HkevROjx?CuF3Cju%Uff$93a{@h;yQy zmRvvX;s=*c?V`VkECHE00WO?L|7Y3my&QgT$$Xf-Z|$p@J2l*$uj9Y^C{h737Y(4O z+Kb8gr7I?_^jwn4S?)zZ*wC9NsQ9I|PScnj`N#y1s8L|)+5tffKgDI$mT=OKC1Ero z{KRA+y_q?j=2qYfd~)WaDi1tQvuQ1IqfmMFw$OlIRA5Jq+^2f?i?nV7=y+H56FBZwNvCy|1_Km0}&Y%~1b(rtT&1yjy)1*0tKuJJhmgUC27H1AoP3?)$ zxvTYek3wIC@PS4H^9SbKAy7CJJt=id*AVf12!pv+=vJO{I!^LMKnW$#i;Mbm#BGf;a}z|kMesZUl#urs_0yMMUi;j)Av3Tir+OEi}qE@=!P`x08a!L6_?)5 zXCj^m?wQ!I)Dx&gdtoLEURcd<3L&?TR3snqXDDwYetkSAoCK4A2a~F-*dYkGqqwZs zW2|_EJd%a=E0l)(CP4e8i23xM%RXJbJbUICx=my{d$8QJPtz6(RB~_#y>nY7aHUm& zc>wIK5^_HhnpORYTj7v%IqA00JGz|o;H}<>4UufW(oRTmn^MU}@ky}Ux3oqG5-55M2nbf*u5`Yt(d1yBl6l`4vS|1-koC#U!r?n1{YejV zC|H+N>6;{uc@F(fT5lPQoU7YwVo{dcJy1UDmwD#G+h(a<;f>Cl=J%1VY9TpjYc#FY z3}P>h-X75QW}~ldOf;(Q?KpqLUTc^R(D>}#zfVVF-Wc!p-+q!}6H=c4|7O&)&cC9H zVJ(n+5-lTe{=1QwUrM>!ad1^d2c2PXdOT+{IliB=d;na^#o+%>hQ3L~-~FxBd1zk3&eD%v)64;Kmr9H#icZ*n_PV6WyN=N;go8vcDG^^X@tpP99Px2S%zAp zZZwx&V#QOmCYAS@Dx1XL`u+)_TSX`auVqOg z)IqEfft`lY)`Kh%YQGN8-ar6!+V@ksv`;pL+(5*TOL5Udt7Kw(Ox?c ze+~IXS#Bz%(TDwJIBUM8E8_G^hbPb{6c zyn5c&g3aY@{}8vBW(=W@)^ya)rJz$6RZf&6^NsRbj7Lej<_O&pGt!~^SOB?uGfeF= z$&G9_{=oMt%18JFYZ-IQO8*~OZ{Zfz7q)HF3=AuQz$4NY_zN0Dam3DXz0eh z3I3Zfn{}*g648>q^89+F=w%vLix8dDROK!WP_VFYxt$|Wc_2N=(WJ#Hdc)LXuAwOA z-EeA-Bqe>wD^|ewLwWBRGm9dGO8 zE~1vE*PO z>ntAa@vew}kDW9)01&PQ>467!gLTB&TLJd}qrxQvK0Gcj#n%Xmv4V%RQ&1@%;TqhL!7+qR;+p#|#6f z(?GqN+3y%ONsyCv6D$YlznU7CScV^N>xF(#P3sG^m zozQ9}Pmisv2ZJ1h6XY@0xN$y9VlU=fyv3Sy_(pislr^-;!?k2D>|&t;tb6784WGD1 zG-zwzKc^~=n-J+p|KLb%z(75XSFpNJ$aEH8Ead2uphI6eBA{B&Gck|QHOI$v{0X0F z8@uhfQ>F^KCl2lS^n+F^X5{>hthNfq5W@)*2aP_Zat*+Jk4Sm`_|uT;c|YLT}BkWMfgQu zcZaH@eH6b{EuGWHCDXLL1emxA<4o6HucfK}ehD1@o9cNg0-WYX8|r6%W(UL)ZS{fg zJgk#rL{zIi;KsiI>RB4aVoh`i`T(Sp`luER=0lL#yMqKBR9ft?;;C8B-@~D zbVHZ3x>(A+JE+Vt@ze(nWf{_kR=;i|Kyf~m4Bm-$ElEHI^%58qm|TIgPsGO46=HAm zRo_b>Q=f(H%`YlhJa?!}DNXQ4pAu|4_FLVDfQY&;Iu zqcw0`v_Iwn>7h|G*C_`+T(P8s+#W7eQ3#PtSVdZ#^a0B3eA{8y+13yNy0ad{C$ zNug)!1WEWUIV{u5-tPpE4L$=;qdhpEotC6zg@v<`Ix37Q{AZRge^ zTTdOws*e;RW|btgbatds9@b+3fWFq@OY3ZV>53A|$65Q1Hb$7#9{^VO83>%u`^384 zTrJ%M{wkHd`rIXQNpiJuqswCEMzS$Y%^&n2D8N@#dR5EfDPV`SNu$D(IJ?C^9ZQWgF)2&cRD*+(pT~}#aIb#_K~N;q zpZ~0p+dCj+uSkB6ST*{bBzwqRUK(7=Ez=B~e^6KiQ53rrp5h1#Km^@tONA*kC8H(n zi^Zr_0@@Hn^yfhocwR6JC93-fztEKxfsg-sJ zh-J1tcTqDK%D=I-VsfcZgU3XXj z{rT^rnL(Rx04b1ZZC}Ml-|B_61zEut$|^9a<%qufU*^6~N*!Wx2#8<%ffx3GoK_;J z`!r?2o-3#B%evTjZ+(l;iCe;fMI=!R(;ur(t%6BMGke&&_%r)reJA}gB*$9&*_uIz zi@1QqFZ4Syo(yS`?6Tb*39dki+Q&l#Xq7CNKana~A{;Q8G34bu!aiZe;Kq7m;NZ?4 z+T-GbV#ugw(G&(2k%azC3v@EvGguV%-!HZ!GTsp@fnUQ3$HQo;(FFdMqDm$f=5S+* zlMthb?+F?jodh|EidXtL8W?!G63TZT>kfbtpp{9)6%)7 zDXk3U2Yno;8DZrHiB7=3(6C^0)7O-cLwzV!<#O547;i7R&-p{M|)| zY90M0MOS{;jlyO}^5}p&E$I81?a7Oo&m$W4wS9(qG&Bf@A&&E_lYT$Mc3A7p>8=OU zqV~q*n=yHzY8^n5&@-9JsAZP@NB{0^-ynN}z6L&+^97&Fb?7pgN^MST-LEB2l_Yyb zZ1i^K<)EqkF!bh#Sy!8fwp0R2CpIfS5|q6!LKxn`FShND<|Yikk)1^lWnWE&9Bf4P z5)~3c2q@BlU>=0z{g?$V2t+`xkitL(eD}4LA}Jlh%Uok?^Fcl=Fy|AOGG>Fnf(ftRIfsq$mt!qm)nv;3#drmuj~ z-GhUWSNzfsPfRcW1U-(0xTh#>`oVWogk?Y4)dd!N9QT~Mw+^7{%ala6>P;`~FBO0e zxPn0yKtm5Z5{p(%IPHt8Zl>fL)?-uXCM014h=cDPMc}dj0!5ntrojtkeu9h_b!BnI zLe{~&?Mh`D#a?(_FR$l;oy!Kn+`!j3TQ*72H{(=XghXn)o>ot+e`t90Vs-2RVak%5 zRr>iCWsruX#RnYERxQLMN8V0Ip9fse2m+)KUzn-ZCuXOdudQr1rzHm+OKRZ0Qx4a> zXF60M=GMMMz3epk)`g@Hc~e{t-87gN6supwHTONm)EgP>5ufY>GC>6$8nIu_6j8FW z81fhpe3ONQk46}=zH-`fLRD5#2TBBSv#=sFE~^4g5ZyYHhyqjdOYwTS5`lw#*A;~53tn)6sM!k z?*z_{l$~n4EJKTiRfk)iLr>qkUrx?bZ?lZMj z1=24$6*^zoVlOmgoQ9Kx}MDUGHl$se;`v50slHkh@DVFC|oj3C#fB6NvQDN{@$@bV@6YY9Dqn(aNsx z(kth=gLUaVv9287!&7^XsAZx7cM|h?*^HrF>A4u1D)&|8vz1w3&LM`0ts;qA;@+H; zm}nS7gQP&~>xvy*H68v;s?b-mf~i)S+n2bF7z--G*df)HxWosMD4s^r2qai&5b-NV{Whyt-1?H2H#nJZNS1FwlLgB@r`)0i>_KuH};gXeb3#>{ZrqJhZn8 zmjN)<=i!Ws;SPOxCM zQ#2kaPi(e}L{R-LQcP(4ig87^b@6t6{j()$0y#sA`Gq>+E4y#O4pBXcHIuG-4d3;P|vxw>pjW3LXwp+M&;1` z8LlobF1yNCCF%t&-xSueQIf$ZREM;4h&X7{s^H=Laz;8m)Cf<0|8^wdSex8gsr0Er zYkm@tw#W^)i?cz3cfvZu?c}o%PVC=rx$`4$(60rT+2nP-1!aKGi1)M|gtF%`HRR5Q znc}sc*YRt&vS|@3@;v0DO)+itr!(EJTwWhP379<$%P{wy{pu}Gq<2Mq3qE){rH&h!-&rzN(WfmDnz%1RN<- zamD{wq>cXwuxmQ4A+3CT_>?Vn0K7S6F`47?@~E4`Q!}ND;Qa<+Nij2RPrIp%vs#_6 z`*!D>A<+nGE*`g32qha2AVm&$_GciVwAkmYUg$EpCnB?F3=o?1>;C&$bIi^=`Zr2Iqiqhgi6+qz=)& zYR;*VHShYs75w92Q`)h=s#o!+NR47b1E%ySE6&Z^A!MoK}xJ& zq%2r(mF=APq*d61SMYI(YFtlV2(``ThdV1=MgtGZv1%9c-6iuZ2LHOa)vu56hTkap z=eis>0E?vB`FmkMC?nwJ>g==Z^{Q-h9}Sq>;&mYXWe?N!GL-=UcKiGALpJ{liLqA# zA+d>WaaTJ_6W36B;ICfT2TO_uDD2`B6v7Y_IVY{H1|xGnE?oNJfK=$cB>{ZO-}U9s z%IJWERI=?$;N3Gt&IzXvG!DjS#~JUc4*?o>0LJRr2*3`v;k|l(RQ!#o z=;}y$nx?mUi&&|?=E7{vZ{o8p4G1V-ns&icJ$3#!1YjUceVpch%3z=I|Mg1|W<>bj znDeowt2D=2l1?rz#AZ8&YC18@j7_Y1!3>0t%~IW)H;yZf|;7_M=JjvbW_5 z4hUzkWWQW9Vqt%~JV&!L^LnLbuO*Kw0R{2jtI+uJBK$|1y^M*T)xdH2)(KA!mlO*c|2(y6&6^D1pYBK2DU=Ge^_+Ee`AcbN39|9 zCLyO-yw3(&&lCr?hKAPB=-blnUFR+N7)dcpqY^<8pH5YqM!~q*o@qYJb zkw4cs`3V3Qc~|Ofuow}o5f1zP_LWY#bW58#kv>qX&61(th6~sZG|v)=?mNSqRp=Z@W9I-=FFj7dl8G1T9p` zE_;OhK~vuSJKLC6>+*a(QfMVig0ncvYVPCN$#xmRnCtRwC35JQ{4S6*o=`Y_vj7l2 zxxVSV6*ewj3`2L>0)Bv6BN5?gflv*_%edI}`WR;fg0~eUhWo7wYN%$)UO+#_Uc}_ zzI=xk)S(km($t9P-C8g>xu>9AX6)L6O|r(#;bT}=9fs=k8-9KI9LzKzItRiH;J64| zElyW+)QmSACZ#-DC%(Bo82s7=D}f5rBG!iET#MoBk|Sry*s?fQ5fk^B@i10U*P8do zFGf4)bsyOWfq|Xs=Zgblwp;y5Db^lDwEEv$<^m*bcx?-m<9)=tEtmDR}Y(!$e`_c z_jjOV@?-;^BK$BGIeux5Lpz)bTw@M?Yk?t;f^^%YmH*%`^Om@=&#MwYxAHZH%CBKIsYXcAVXbGP^lOKK`kzgk|`Mto))253Qw2FOU z)%U@4?oRw$>kj)E;d_D1re8+)bwrhq8Nrz)b*WL zRV#BcqY>+aFaf{`=K8qKo<96~vQiLbEvtO59`%1O`Txa#yN|8ZzEW}!MXinn(`mglV z^=|iE_g9anjZekCbM8H$h|FOsj_PH%y4%b`ST>0-m79ZmtM#PyIp%!Ai+S_JG#%~e(kNk;@zcun$pYLO=_XR z^!rD8AFZW^vHEW2U178%b*xwV!T9AH^T}+b-@1gtOc9B1dgE|)S|MoJ z+KwgQp&%inO7z9UJ5F;b9r&@AlCPqP+~3!fi{kyi2-n&tdz_z{a1^ybJlLQGG~9DU zx=OPK4W{3rKdjz`uZBxq{`}%45HPp5RL^zf{H)E{<>&@XsnzZYF!{HTwDOikTnY~Y zuQ@rmssaIEJirG9kpYZu(@|gns&?shFzs}ebq*fc7Xt}u*F4SKU~gRmcU!q)G5_CQ z851Z({OEqJy8`}J5~@2t3OiWKZF+ycX;OT@{O|y9nT}Lz>0>EGI9S1~V0bV>$Rn7b zVi?|Ea9CMrmD~-RNC*ySPOB@Z7c2wS-j1PAI5Uu~D#D>af-f{~pw_-3F;ssR^c@hf z?ni&fc!Al52D$KdAunPuK|0hO#uarQVif8Yv5ADbaM+kg?&u~zl8@Ps9fHNmAE}q| zrhseS$ePc%7V$dmQdD_FHVv7unNl1rFk;WT0{uDhMr$4{`{4<3Hk*gN;KF;VMK*EM zV28oqTeAvAmV9?blnboe*tG(;gnRz%?2Zxp_X&Q&&tb$jZ zyPh=KEt=$=>u#?NV;zR+Yl83#R%K8mVlBH;1?+g7F}{j<6dFL4@&00SqpVlvlFaw) z#Y#>8cCygWd)cea>|uCbchF{cgPYYUm5wnk%fsbqSqll@qq=U$-49r-ATc4RP*g}y zo*ix>70OHw0y}|PMNtZcfbh-7mi<`mKm=q|r+QAWwJK8Cv@yB~HrwAm4Lwl2f#^^? z1?13E6$eE;W%dO(OzO2pz&6JqtIU4#jd;ubS`6Eb@r8;Y+!UfI4(39vL%_MgSUJUB zurNLGX95lRu{xocKI=Ym@xy+Q9qb$fw+i6C)Y`8=Q6VvAnhHgV6DI=N-8^5%<;vMr zIe)zld6?!85QEb%nOB@23(L;vX!`#MAA`;^*~c@4o2?|>RFOOD197!Qt$&Ue&7+vPv2@l=0N@T$fj8IzbpydtP{p(QCG$@QA4~Uw#Fc-1#EO!ayX8a z8~b6$RN@COo9fdq&9!|(MvqkL4VAPviBEXk;7N?LFS0qI5HN^U`#+$ za%1X?Q@eE%le>Ikr6+-5FO0p+3@_lY+zNb1l4--~bKxQwc; zl-S6yH)IZOl;Kba#O-+XfTQgNk|=|*90y7alMg>dF)`~Xj&6eK+Qn|Y)O)Fp=PMC7 zgs`^?DU@4#u~!vCq~?(uU>Y zVOJyRN}M<$q-v|7zVgzPaIQw#73@|IHcEc8=hyZeGS$5Daw&H}E)*L!dHK8BY~^wm z8(BHru~;9=lBLQ=N7C1Knpi4Z{5sxve$c}gF~}ihQ64j{YkbVZe3hk9YV)Yg*U&Av z*sm(sE|{a*nn8kbYgmjTGH5s>X#SqA`ny%gz=tEEHs7w5)V`UXXwuY<>yygF;EU4W zw>)UzP4KrMzs2|O0{{-R5{~=cQ>`hPXHo~!|4NkA;t7(xl%A0OxCmNm^Lw-|hA_}q z{hcYzqH(pRqTOn>gd#oo1=ivyB`xj_O_J53(CHE>4@H}aU9xeI9N5SJ<+kbgqCerU zf&9`FJS)68u`YAp)6cQt^zz}CHA(v@s@Sm*YyWSO9Dl(4t;@P_sAULBUYe*E-$8nL z%J%gqoC&UR^B9_9$774Xx)h)yq>&r&>$6%ht}0?8 z0Cqx_A}A;C3nUG^x{T)e3zAj8UzH15N#|Jnnyy zk|%$Xt<7EE8GE}te(X+jx!O7RW9`OPk~N^rMF|mB$W@Xw@cF7dH8X4|z-E|y+jx0- z`t0Wqg`wvmJ+n67tL*pmN1%JBg~C?KoVwl z5TlrjAc7OEkL3KEP=h9fvgA$O2SLTo&pY67JSbQSo8R;%+&6yju%5#} zIxdgNMQq4+Sxvzaro8KyETNLD(Pu24$o}YlnCJUP67C6>wwX?4w!^DO>rJjbQ4G;y z>rNyw>RSp|9^o6C31e)DAU4!SD}XxRyUiU#QxhH71h5`A+ z|(zTk==%HDgPdcB$JmF}3lXE(Se)Et5!iq##> z68<8782tR~eH>C-jT1?9eF{86by=8+Fh*4zID4_+_fRg&u4GGW2pAts2%(Gpgw8zk zHz2T|cdsb&crO=fgmp1DARP(X6zq7L2>NvGPXAmH$T4u6ptL2s zR*0Z0fU52O_1Vo;un!z}tI%k(dVe(dx&|;Hai&joT>tD~XA1t~F4u{OBL>XjJdBIH zOkd*N$~-ie(Llgqv1#j?iD~hJ#K!j?mCs2@jI2ekc6WsT)1?LcLuJOS^!=te?K=Y^ z|1G~g`hY2AP5jv()j+r23KNspBT;GMQy5j3LR5|!Z^{b|pA>f$EQUm2Fadn!BjmSI zzDQ^5u9O51<0XT5zdde#33))@W+6Lrc=>HoNVitLS8k|tYuJawEL&X(qN{#LAodZ; z=hpm=<{TLK0r?ti5dCFC~+5<-!$gTo0*-kRP`LVh1A#|c$!SYfeP}2oNxQS}q*WsbADt$yj+s8= zHLC^8m$2_EuGj0XZ{a-4H-BVrM%Mu3cY1>%+9q#l?_0-p9iZRjAyb99*%$UlgpT7U zWrql`YYIt}UZ3pAOx2+ZwIh_DCsK-c?;`)ZuY>=)uPONR^ML!hY85WYHp@&W`4HFu z4Je9M+$ksJ^w7ud>^4FSPyLGgP9b*BJU2U==8nJK0*BB;lv8retHQZiS9gWJ&An>T zL-o0bS|uyEeXpTxsMf}34yAL^*7VHSzY|fRsaa{rfB~IEh=N-AYQK0wash~#FqPcC zz`9rrE=@V&T_r4t&`!XR6Y8(EpX3^+L2|(eVG!V3U|0@`mqLu9L<(a#vpT_mTlsei zEtv>}9nImcpwu0M1*|0{qLJ_0#?6O8Sm%woAKpbz6Noo2JvVeSg-!jX7}RF5RVZ>A zU;}5%DFP@$aPGsx$OIX*VV2}96`IC>G)q%|B(ti|S0~zPX_}JnZPd!occTu6Bs4;5Z;D^FGlO*T zd-$J_0yeA48KBPLO|P)T(pFSf&(tdpD6`Wzvg#^l`>Hp202XIffx%%|pD|$EY41~E z3eh`yia}qTRPW&OQ+Ln7jLRM z(C5iJ&Tjqhp5)l-lZIP68Jios(WKpt-qj|g#{pfwY%UXh$VvixZpUOFTFaZQcj0+q zC=C!}Y(_I*v}*81e8K0&&D;mZ)7h&oEq`9hj<}SxWnKKy+naoojuioh3!#GocMI3X zDr%Ln>Cq`~#AX}3O5&7(Wy64C*~-|b<@)c5Q|AZ(;05a8&Ne7c8MVB=V%D3j2U`-Q zChl(aO}P=o*EU;9lGl}6V7d_L8I=X1#7PeB2b(vgCP_cBRtQpHQ&n;g&H;IDB^Ck>K)(^KiX>a z<&X3Ww)nc7dsF>YoP(?uI>pA_1lD~V&gaK{Km%y17J~uXn8h0SfZ&_Ol6Rhbd(IcO zadz^U-C-=(%vHKnB~0l0-N|^w^=QD`&S6(Ue52o0AR+`IX@7yc^pL*<1ab#`STjtT@_+X~0e~mUCo$#cd~x z`Fm#&wS8NRQ*{?1#V{kdjv(;t`~O-{B})E$WAJ0%m#c+Meo-@;*8$ZLvp%nb7JSg1 zoIfS=A_+M<>~w;5%+xy(Apj)ooG!=}J75$2P_ur_aiH0oWL($Vyli&uM0^ zq8on<6v{Y7nBNc zA%2o5&4r|x6*c2UExj?5l)wCse!28N%qYQnL}J|Pp&(EF(MFusX0};sMi9|mv0Oz? z*G^^Q-$x2dS63%;5HW3qr5eKL<`V7FO##nTen^ryIH$&fbi!&M1eDs~YPvf-C~L=s zM`h2eMMsVSk2_(W-z1k2>>@eZ9 zD4To%F$E0!4Am}V7Q2Hgj1h7b(ivJ-?R#iNjVV7&G$G0?026{qz+{Zv!?j>n6e1fE zhMCI&IAj%l!c8J)a_?bm6ka?s&yOOwTX!#X!ZtDmEEUp?x;)7eL!Ht7mo~G==PDfoif2h4@N1FB zC9h9ot;b~)In6*w0z&s-;H*w~1vj##N2!^&-0dLQYKBiK6$&-!a!GCH)VG+EEmcMx zv_CY}!-@#jH2nXqQY?>|oMfC4Z_p~d@rdZH^p&_ZLER{}-`-q){*;;wm)QG;fvfZG zxh;_wg-{UIspMAPj!)=4T72*3oFGDgwGpbdhPwn6bc-? z=f>yguXV8IuNf*1!c0uKkD+X<$onhE2`h*=M|qC|@d2|WCHfm6b@DOA`T5g4vn5vujl&@ps!XmIV0Kw5 zHU8cRzTQwH86Ok3#)$}zs`b$Po9*iPSFwJxxq>V7xOG;^#bnbvL%zCF))w&Lt);+E z>QL{EMK~IR-J|pj=UQJSvOK<|3q@FTkk43j^Z@v`RAAO?K#!(Qba^L{h_H+#h@rVC zrotm}nNTh#=8P&!vg{*|AS!wFSg7DLs7SnQp8Ce9n->E8GE?}d zWCOTeCbZ92aL1|M*111>V$J4d@X{j6sDPGZs%zO}|EK*FF-!psVw3cV8I)VD8GXO; zau`0mI_DvKNWnMmJ+wX-ff*%Xux=_E+ zkgj?hrhX+ac2+wKC@tB&kP9j~Out?1Ug#ymIv&owP7yVGcAY5k>UZ>hiVlJ@-(E(_ znB&OLYZu4Xg^$Ed4b1X*21t4SY)(1dJoepu?jAp9o#~u0V9T_AXcvVrqk-_!su=$_ z&Jy`I&XNaY@p03QzQg!RG}yr-Mhd%eLogXV#!p%I)clINzsl4$coA=h!XUG*ktyTh z<>a)O;rsQQALTN&a|n>}#&1yf6z~$AtDwv%EJLyC6G4lO{*sDdu}Ub9x0A`rgr2i; z9#tg!b~jbNa!5X2__hEBXFbXe78|<1%`&Rsi4Di&EJWGKEz9}B>Ly$!`Ef`pw&F-T zuj({RiDkB^^Te*tpGZw9>)^vK5zPvPef~e}$a15#z=7#SjI04pO9F6BduR}2 z1Zl}HGW0j)ky`^UTn%E0J%rhA&42MuMpC|r=Sh>k-oU_iK8Af^z#$x|Du7++%4#-) zB45YLQ+rin^&{wl6G;eHI$oH?b(&LEtK20YfSbqrNUivKzqI^l3(YkMq<<-B|MGt$ zrwe1Lz1?Z4%gE1Lw*XzM)_^|If%Zv8h)YZdfEd-aSTS! zCcs)PT028c$Y&Pha8dM)nK*6lo9T(n7hLx*QCQ#g>eSP))W=zy6C)qP>XlK2gb2F| z|KHYHA3!a`N9fgi=2_X(DTp~dW=x&1$7BJ`s;@t~^>M5!)JBhFltrqBS`=M{7%8f8 zw(v*fxKcS4)mkA9%8aBHsPZm|-#^X!=48nyU+m^^V++N4s-*R z{zM-G<1U=SrPx1^{oi`|Kgcv-TT%dzJqM3B$_-Qd(U3|459nR|9LvDH!->Jx>T#+9 z_`q|Pz}YlgS~D#u5wnxL^~9gDv5yJ#5D@v>xMe^dH_eC$+;$EsVb*nk!%8SLeWdZb zadl`V+MO31(<$zQr~=+rU_(ifs#PC66+n>VzV@x8)P?E3{1Lo7lpu_bj1@ zPlG&#srW;oxkPl310AcGb~LsirtP&gIY*D$VR@L4&mZ`L$S}oGw5P%8Z|=!lpV2~3V-Nw-$f%0C8?1n9m5{>}J1AEyx09TS z$#Lqe3Um{ieo2D~U87GunEFAP99)K#s^r5AF(xSE0K$@#Le2OE(rNGMpdKV+Ua-*s zymIO_lizE}p3Z!<$td6mBa;x}`JV@?>fXPDoiziX1F~)dNc`FbhFPv?$R%BGgA)mM z(f#qZi!j16MD>0du97Y=o_w@_L5A~j`NP@jrHEK*9TH4wR1bTAxQmG|##xM5+#wW8 z+VduHt~1cb;Y>xnv3Lz^7_hiaFs}5bLL7J?#GM?4p$^uFrx4C`-=igoFYZBeAiRqE zX*im@YQ;TtD)T3*lSpEcDkx`rP4M;+Fz}XODTF~nRSA@6xlXxhBT%?GELFBo9}K3D zkQG^%E>tk?Ik*K13BSN695`iE~D1ggDm{Cub z$q&QYCljqjKG39y2f)n^hD1J~tJ{7uTrhSkyGH;cxeGW{!9yj5>aDW^1wo#b_oMj( zaGvpdX~hBj1h~|`K!S=+)e(E5)(z*j7}2l!J8QO%|IH@IV%8G>0q}Bx4hGjiO%?sL zl6~&hX(rllmE3_TrXv-SlO+m}oJIUL6Ha%Zmhu7qKmmobbXYR3rW5_B*oSkM23{0- zY*{jPw$|eE0CQ!UVtyH@kYw$NO1%7#b{Zbyxy8cACP2tqZh`sp;gClPKGL-8Ku8z{ zf$pVGa^9cn+9DPS3;ns`#4#ves7yQT_9>9Vi;~252CD!1?`i1Sec2GJ85DIz=PB?) ze9S=?3S0ZsU|qP0=@e$yC3020RWQZ4*e+yPqp*^=t?;RQKinhF_F+W$ge~R@G_S%& ziGcnpp7`pUAP&<-1ua{>GVTf$jQ)VfuvO0F*39rwKt+E- z@M{8$l01c*0#gO2;3jrQd$x+D`0OaECb>(e9CjVA?#tB>@1nz5X`iGA{sm*8uX<|cz(p;7+f5@=KdfmFqi1d(f^Z4{JXi2 zbz8@IikSUDP^5T`O?dOFL?zYeY$k_+uY{GUL19P5HrX~2i9AV zjU9eGmJKO*COg^UaiXTA_g5r1&kCT6Y{Q*V*`q=pg;}r6Wwxi5jA<3 zS!-amrx^j97Q&C9(TwFh;nzpc`>e4X2(UMdl&r3dGU*!4oKlE2Y}L^yO7hi@HAZ@> zgN(@tX|NfJ%7pWV7z*uPj^urJQk39UdYmjmlu0h3Rfq+1yWg76P{)TJ6^mF;c>4Lz z#Y$xVi!z4S6@=rTuJKS5A925bPtPzW=kH7m?JrQJXxcxa>9rl?{(zQ9ynp?v$CZsV z@Dq8sOVa;-mAk}JKL(vXmlyjKHwvE|Y()E!!#y>Ny`s8aw&hBSMW2W?-MkccE##)u zlin<)#Q7*zW$l$-Gu!^1AHsq1&F2Rg1_ciF<&o}pwU;k{eP%G|Ca4okb5TnsRGU*v zKw7%&9j!-RX|h(;As|qFf6TN!RUJskLD$B(^iR_?+>z(N7BlpdJTC|i^9?uG;pE#j z$oIwt9EN~mFxkmSd?|*yxD-n%0(}iyN=fRp-XI+`@bzc7*E@?dtj%7jlFgCGIZ(Juq5)t>}76 z@ra?Io9aD2w;{~w!8<5_Km6X~zC`s+-&~+qI`E}R%C_>(D1EH;3#|)5K6kz;TkTyy zQ1Gv|{r}_;3gC7rowbkveQe~+#$A-e$ABz$#R!j#K=4#vwEDy51|{N;7D!;6PvTd??jaPX4C`xNO>m+P5l5iF_KS0#J#^t(Olh zAc+VjImMBT{@YY}K))fm@w4T|gkqgtn9{D*Ao|IZpM!7fsdDYlyI-YuPsb)RlRXLA z%G~LzQN#G2okN$=?A&kOH>*-#a#qdxiXx{-NE2!0=B%sBz$49H_TlMFSgq_yGDbm@ z3yX}CClo$ZOYK;c&>zmg0jk^L@Y{XJPcyx-CzO+|*dxVs^q^rt2|yVvNrl_g{O{l3 zVJx+u**x)Jm`8Gzwbh0u$lt8Eg($4;NW$m+k)cw>YfSn4@tS4CL*~t7>+FVKY??5$ zPoK3e*l~#H(d`}=-PO({N_rUGxL(}H3@P$+Q+dm&ShK|*x>KQfg9Mn}RPUzgE<~PT zZb)k$gH;;Db*xu`q2md~w>8OeJizSgZJ##HqroEkQP&)^8^64uifzQdP*e`e9OQm9 zMz64Z%h5D|;|`o;gIH(Hp~=3dWFQF?heQxe(E@8b_Pvpi%|j=*cAWB;p4nQt22d9I zA`(1r>8e1lAAYCp?cIU1Wq2Hf>Duz%JMz7ey1MS|vxE^8F`Or3!juLDwe|-eU)==h zD{~Men09>9Gr!QN?YZe_^Y_bGFWZw&a^aug_Ef>*iC|4`By=C3Qt1xCMa5M+Sz14` zh$lT2L}#}%`Q+_wxRd^&c8g-nCSbgKjAqO#U^?q14bN>9(j)OsYP zn=r+*I_fN5+I6kLImVymym%irl7w+d0!6QW`s-jTp6!NssoiibQ60Tpk}zMD$M5+1 zsVX?ZHIJ>b?Uy5UoLn639f^=i+>%|v981fF59bAuWp!rZBVyV`tfp>{HE>M0VF3)3FwHdlHv)>1`eaZJmNU;Ei=qedpQQY?T}DmtQ`{SD3hmRU6!M4-r2y;ky><&qviYy)yfZE6MC) z?T69!P{N#R%kJROg-lBgx66X&5{oH+&orpI#xx2XS~?}B?ZMnek+E4f+V0q~-;7L~ zmOuY=nTrlIztntTmiS=*>1sCL%MW$DQ}b2I;l;yoY&-E_NBv@r)P_ac;1=hf*=gQf z?cyI!#6MWP4=~<<`q58LH9`iwro={s_%Sf##LSAiizMExg)Km}dn-p{pO3n=9Lrdt zs4^7KCna(slNXs1Lq`;l&xUSF+eZ z9lv1KD+L!3%!viN&qXEp6I*K?VPRlItO|2Uy|sSqY6ij$d}Mzpv*zroDV?P%+qLA5O($;Y3+kXs_`(qffg*tg7ke`2&`QP-E%jMI`ySX?J$$Tmq@ zxH$6I1rkZFD%jddo$XZaj8=&S=g?Al8{5*MuKPN8#dw<|UM$pqF^QS(^IcvPHJ|@B z!^j`>E3oC?)l<~KYAan-%&458W!ewg_+k5Y=0)ydGVOZG^aqHr@DnjSWk&3^_%)>e& z4~q(u{c!cRL|fvemTzceMt`3Rc_~fdK-fAi1_o%rQQud8V63&1-muueb(i#2v-EV0 zjnl**W{%^^9j2ydAwjCv$M2Z@Mk_)KF1Nb8i-HsK7_%F`sBBp@jbPDS$N8JSP{rT& zGEN}>P@S(F#V44ai~H;QnM}K+V)veLs-kCoD4%h0_`zehS;hu+M9k^u>4)0Z|A(tL zk7s*({>R&QZ|mNxrP?ACsj9Y0kgBbS+p20&s#;5IC0Bz8wMz)Mi+xF3idwr^+Dnik z#M)LQ_Pw#h(k2A8q=eY{z54z=9^X&A|Kjy{ye2be&di*d=b3X}%=!#XOT!y{%2v z{Sn81^r*v5OXr22uNCpe(deC@V)AtnR$j z1G7CGKrZpu`!lQO=kryLxT?4V&v@M#c};gcaT_b)(jk}yea9(z@LW%mCbv3XhPrK& zbh-{Q%CaE4WXV=~_MeFGqQ{P1NtYimXe+uD2=%;$%k{dfF8REj@4GnXt^EC?$M2}Q!(iSv$^Jl zi}-GP%DwS)-HB)Fg8n~8g+7^J*U$7!F@}Vzm3o1%6q7^j$8RD*??1dLJGd;eDRg*FN_eovRW$i%QEB zd)tiMuOOvM&BXIGs&kXa1|-jUJeV$u`~C{h4|5)GuY3vJ@t-XBXUczVcl`a`<6ZvK z(fUoQbWOvCXV%L%ZO}R{ebw6UujTs7-8t+R(q{%Xgn2Ln`lYI=TJ5*$`uEf5w zt|2kGjFTv-t#6EyIZ{QN{WjZsF5}43Fah_GlcC&>M%ldYaoP0fIs$y{&m?T7DAs>o z7_+lw`lq@<@1ZB1p%!XicsKp6Zek@A>T=TmXg!|w*_SxFZY%Bq8bWM@a@wp&YdkCC$deET|&b4E17{VIPv-?;f6AA$HB?U zUK4|vm5uuGtyV$tTgS5U-bQwTcREWr3x(g`a%P&0kgn4uoiDn_H@zGkVJ0W6vu-Ue zA+s3{hc4jkXXA5^C^*Rcz_kpWxEx~G@!RCh>Z)pWm9gE0wx;^8RX;mNBxXNX+M}%w zbBvPMea|ZONTPbmdQ;{l<2B{>EaOlnTup;#%cs9cwxLr|^hz+J1kxJzDd;?`XX*Vx zyk6+g2~h8={g+h<4ESGm;ZcZ^wyQ&VZ;E1w;d9$H2k4EFPty+t+T9XRS1Wq+JlkCs zz6BT9UV#OYPeB&Y0;}jZ?sc7FIi!K~(V-JljL|A?qMg?`PFx+{8(PicF#m~0S;N$z@WeCC-wee^$U?5vEL=mSlGa%Ll(NZgI1Km zXDX?nxJl2$Ch>BMF{qBJzS@2J_I(dd_!IvlZ7f~<&f6RDt+4|JwF91zA7Rd2%;S1^ zkAjLnYH5Z7uX92I8>m;R=~pC$r9t6~$2ZxA8`l1xHXNR)tM@k5ebus1YZQ7mtMq$_ z+1#-!)w<6e>Tf1I-sIE~YY2wB(&W|R+|=55bigwIsVt&UWAIu61`;ZJq+P-h)(=;G`Vc{sCyvpui0 z-QQhJO?e?KlrHWO9gI+o3`&WZ{OdP&GfY=OFPi3wUonh6vhD*ysEwv0u7|p`iRKt^ z_(PbRV>#_lYkVc{QpFq+@6nc*E^W7FgAyCeOXxM(C((4yp^vFHm3p=p@iyFjw>U`G zf3bFO1N4>(oc-qw{(N02T~41Yn7d!mc`>+cJukDoxg3p(yCa;3x*DxKpKAi1Q96V> zr+#6s{g1jzI{k+D2P?}f;qyhux)gIpHHFLPbLw}RM$r!Jo8bW|l$x=f?uxSG6e<SRa-7nbfy4)DIMr171WZiU0eR#(}HsSL*{jKFDKlp1q zf4hYS45CswhH&ex8r1)RSv4ubB~7C`9%gjw#dj7$_uC~EQVKhHbxXm-3BN>7JfaOf zf(RH+L0k)U$vBx~S@MoQS%IFn;&WfXwfN4h?(y$Z zy{={c63PT?=hUcPmdC>z)$d8v(|T&O^y`5Ns_H8J$rjmz8t@DX%5nORgX4h$jmAv) zmodeukkcjVE(Tkv&RfNCm(Ehok%)^DHPRh|`}Q489C-ipec~I8#W_VQAcIo(n5pUi z+Y?aA$3%xPqtxF&stffVd}Ud^{H*DQ0A*!00uL+GIqJRJ>(~)jATzG13QNlTu=t74 zWu?~GIaYc-w$7~MPvZR7^3t-y{)etq3q^E>{q$|^gdeRrlf;{vby{0nSTgdsJNW9j zQ`pSa#Nv)5J$H{Z4ye89Y8 zYz7z?Yq<4imNGQG6Y54u`El6;^6%!BtS_6ex+O1oxCP%zkPODWw47O``I1=%?OcQs zcgo#tBE(ZH$2mTqn!uN}FAvu~Q!pH-7u^6tcjem^j(F2Z=xNyGNNVP&>hgq+3@l_L z6n6jxJ3Q1dVjW)|T;?_Vtx9Zo=e}?0#tdzh^Umb2A;jva-@wZUkw9255gD>Q88klV z#J$){?9`E?Rh@)1})rqyK&PtK-!r2i7m&@J2q4 zmK4kh+tJgR+03nfSpOhO{cPA;R>_>$FnwNS4C_sJ;yk{qXRce#FzYWO4%F^ss4OU# zxvnNa+=_91Vfo06t?!(<2JS;WA@!@vtCOEkvn1uzolhKakoE@R`^LgE1cD$4pJJ0um&i1o&D`?f3PRiKfRmA3ZARk_ z-i~hHAH+R(z)o+QJ2{*)z!#*--+6Zm{a_?yb#lKiVBHsucxjSi@NbMZ1)V3S>aM+{ zm;Iza38vX`7=XvA{qDHYzZq2W6<4?FPzf_fJ{}2VxE{YEuz{BT-Ml(6DqT2yG0LgV z#>fB}wbjB2Uu51fd28};=5+&5+VHBElXk);aOQJ^f#J&?T^!y`0P{%~p+8$&K&6iB z=~-U8b#(hyhSc@c_K(obyT=5G0mi+wxr-JqDA#CZdDplPv+YvqPp-{I3~I=Dk0#%! zt%ch?#eH(OWEz+b3^WE0>_~@1TO{;Wwa19?IpGW$Bm4YMf5q#7AJ$E8Z%*C0m=kRw zF(d!z&gJIaHH7e-m;TP+&Ydd;T{&RSfuUn>K|_=8%JZ*P6L#`;e>!azbi^+!30fFX zlmn94FC~Sr?K{TRW)aF=xeXr1HkNWL(@!*m1XcrL z;E^757W!>|Bw8g$&pX>~(d(x8Mf1@x2D+v}BaycjH?^r>d&vq*G6Y(@rgv&o(!~R> zx9D`L$OptrYPkC;#C{Lt8-&OlLZxLYH0zOQ##W-yaTDfeOyt1ZOyTCs?pGo6-VPG_stAT zRJZq+zIvkuo+G7N!|!5OSNp(KTGRCm-qyGJX*GXLe)*f{y_4zwhn^_oashnAz8hB; zC8avCC{I$l}RW)Qf2n^y@7yAuc)HHmdWRZAPkHPk$-CD&7*Ck=E3E)fC=UNN;cg8Y#B>d3(~wruuJO8I{km zCiqG{W$1O5=POH!n>743{7C%hyg-ResAgA|rV5IN^-3IG9j!$Lv7_i!-8l6_fk$3)D|9H5hu=mTY{*c> zK8Z1?%GR!_$&$+P#q*vF;fB@4ICiwq&)JKXq)~7vE3a(#S*UkO&+D^^dfv#;+)F~0 zS>g1by|)`dd2>anI-J2AqHLpKf53x8<%X5!1Dk5lM52i)riJ+)W2kA9Rn)3f=Tt>4o^1AV@ zgAVz|34cm=58%)x>bjoN!rc0TaS|d&J-4Z4o`gmS#xY>i$4tX_vP$fS9;}5`o2Do? zE*F&KXsUFooLl)q%O0A4*GUCwfUn_W2xZW}bCR+@oTl4oUrP0Az<~t`FP-s6ROa&n zl>N`PJz1?=Z)M43#rRDxA@Xv`lX^~ZR`o`xNT}@ffcLUybyF{JyVJcttRXH{ndc=cX14Gjr|(q{TrzLrIG2whq5eQM?O-P&f9!j zcIwQimB3;ShS@VZ@7#FBKQJ25)%=Tlf%%lI$}s~$41?=&Ets=HVSq;*j1|$*({o2r z!?}W)1^vy7G6RptH!_{&kik~5O{a0D#O|O=;Vb zsRRMHGkd-{IqlS6_{e#6+_!6T@^SlU(EP4^y%<7llvJVYU&mCFj z9oKnZLW5z#{u=sVZNf2 zBz?Y;*2kDZG^$DYUw=HhGvQ~RGb$x@t#T>|Wu+Su;OvPy^xpQ3iP22Ga$W9&*u!1> zdLCi30$y`;MjDoVZLntk`Hj091r2qm*g*t#M?ai1E1nu5S>GjGzuh`y2d>5N>b%2t zT3K-MYqtcmnw;ZeGOQccK9r4P*DbAOj@Nj8{i#O1ojDf}Xcez6-COpzzisKJ_n1}< zRO=+`sCrp%q?J&VZ(+d9>cV_Yi2@CD{2jJP`kXI9hKkOFQ2CdK?%Jv!x1sJ4Y`{Kl zn-wC)TpTMT3xA*UwSZSTuVo${_@I?+cR55BXNq*2hK`yEf1D)+b&u*ae)bBVdt&aW z6J4w(Hb;^?e7OxkQIG$HjB`s6937C9;$WDBn(f|@`p~g`N2O~An4{G=R=*r*0}p=;-x338VO-eorh^3uU!n=eP5P$%-rok*os>6b1iT}1|N z@ls)RD96K)^9_{aSt&+y@HpQ!P8pH;)O4|GLfN&s#jf390q^y0FWeCV+8G}=rn9~EVHuv~N{mB( zG;8{)=M8{3FUz3D#(wP~_0}_0sv2B^dL-q&ki4&R!qyKFR<*dZl-vJp3?A-?GqJLl zVFH_4|EeqNu|@sfXMT6-h*~m1$xU-ghn_3biw0`BM1f-66dBMeu;xKx4H&w+_2-J~ zu6M*P*H0@6s$Yg}>}>PQcUl{3OpHE!c+*t3GFYOE-CZaFKF{g2(d>1_LoX#PQu0Xk z?lKEMH^_afCco2`v)$Q5!nSu7Kigg3Y^Vq^t=!!bZun^T$*wZM_5y?I)=&i0`=*gy zA#-(8-6(?Wn>Cxzy48MLaTC~VvVnHwET|Ae0;w_Y;`4|f2aa==u+mdE#xwS2I;CHF zS!jH)M~o=>o_UeS#AK?FV}U?vh1U~6Q(x6&etnPb&w`hTumMtt@X zp79h!7aocovqlE!a*MY&PQp_^kF|9^?fx>sOUwOl+^*Gna&39*T&IfdN~(VFJQEt? zxRa`nSz$sUV`zK`66)44M@myp=Z~yovxRzMD@88+(RJS;c56OY2ZMrRNdX zGJinTttdbi6OW^CZt^YESd^p9l^F@I)G;G_3&cyd*m^llYeM~OL+^n>_58$ z#Mv1?gT@GILGM(^=KZcCe%}UpmRYUPcqF1F*ikgVH2R*NSyQYHwZ}v0bU6Q-+h@T2 z5_=P0S*N|C%Zxr&NF;w%qds``L0d!jAGDx(0e(EF%c17_KcnJ|P8c#+Rkw~{qANXo zJvHnjZJvxwQWJ}uQxYP$E&`=tb-*q$PF=ek-?;jb;j}$M3!L*o+!ADT0^#EwCv2YS z!=T6G+Ih1lI7JB`YP-O&Y&PZ1JS{yP2~v?}WYa41q0`!C6%y>b5N-N9D;LYg)z^IB z21G-pD0(@V0lpXz;)yB@?&v7VhFbM0wLqmQX2O4uhjS9*X!B6=6!q)D51hKqkFT6_ zGU~0dn~OA=szD8$JpgBJ+ltN1mJ3Db>F1U~$~(3-z30ZP0aVe45a7(osn3}%ijU%q z=jgd~wH#Po$WWzEK_%l6purHO7HZ4d<+ zJ6|}kSiGXM3Mxz&DZ%zj6~cM^CSTrLTl!^yh1RF{4j>#*6vYn65&}kDsLIq%k!dEW zDMboYM#Tls?qs!`{z?L4v+C$OKaQCL*Ovxk^>L;si`=dPe^Lag4nWtD-XUv{n$63C z21pefxl_1?Ws-7C9=eaVpzUR{DMZz`qrqA8s}w63L=FUjOE z`-erNc;TL76YXlIsfppUP`61P-qFJJBP@g39~PEH9&L(z3$P^$u#oHc%58h{xUlQX zSnCg4Kcrchuj!cl10yq!I5jV>M@)a({OM~%a+*P{SRXlXv+~w>NU#&i;&6^8UZ13Y z@@ib{tZA$hW_hPfV4;o}((Z_;jGEKyzZ<+9>cc7D&(#m*%vy^Lr7Q2MZpDaFz7d^mI+W@eb@Voi;zv`lkBrOWQ2+B1gm=XSLrggF^PzT+JyYc}y&nx{QCm%T6Gz-pm8AGj;py;FUgGl*SU&hCQQhRqN5 zw<00(Y$u~c{c*}q*nkvP8K>I;sX?!bm`LUfBl^Dtf!?KZB);}9o^uQ!1{+4(&Xf7E z%+s2{=g|Y28TH8(`oe7;2}4%e^qMiMj&XLL0QqYX2o^N`t%|3EGZR>hN=06r3FKvy zK*1YnqXR=?fljH#y>PJ?UQ?rHLm@-lnep$Z8Brw7-4zufO%-1a1>G@ZIJMLpgc z)$TFMk-~0|+1XwmqZGpInu=c9^Dnc{)5Wb$0n5z;h+Q}=|K#zqOAXPBBsh3C8k7HI zB=EIP-TWw6GE6+n=b>9j$jFWd^ryewC}|TYsY1<=PuvoOZdMWpa!YP;7#d^SfFK2= z)ZGp7hA9}Vw#dqCY%0%8k249+9clCt(^pCSRRp4(rCM9FP!nD_daNyqe1$K*lDr&k ze5Myk<98^yFV0a+QEeJ_0B-8hr7nF6?Dxz*{HvQ*1;H)jWOaEjWQuTuLZ!DZ8{F} z*?L(ZVC!FgVW%Rv-G^CCMJZAS+uWi&dmaDoup8qmJO&kVo3PqPYHYhJVHYB|J;&%e zDK~>4C9G`*8^oTXTkzECNf+8SL@d27+K!vO&kn zHYQ?iXNd)T(G7J;rRWq8-HfbQu+@Lry0Z>wC`x;PD)TyLX>=t!YHUEpyps4UmqkZ0 zj#NTd$7_jpzmJ53Rn7lg+(mVN(kjQfla~lhp=jIUD)Wl3Q>4eljI&>YMankX6Kqw8 z`Fzaef!Y2YnC*PA3Ql;N(4+*k&~mv7L#Dqa3QSjw4idXbzZh1cb!SL3h#9{bjhXEb zPQ4#3WGrMmlV+mu+?z1vjZzXYp7_!&nbUKex>OatPL+D!HJG*{X2++}q(Y3d;cFVB z{=t9!wL{Its=&?XWQMoi#{=sK8r#O;Q+6368^K`s@oa$1of-?4SZESOCi9bT<9nz{zjk?pb;os#(Ohm`KPp8Mh1#PFtekfz3Nvy z+ihk(?S9Dr2km#Gx^C1Qyt-ggZBw)TW z&tP7RA;ipl{~Ox8HfIr)*$BHN5 zr8U-D_id?Rxu| z=7Pi@(5W;eBjY%5oe{sNZjMTg!Y|$DjIKTf3sQ>B{8(c09IQs*T-2@oqpP`$!N5jZ zqF#M}+A3OhDot~Rm?lzl`KzSnQUY}FY9h39)lEHcFNowz5UVC%+rIMG$PaamKnuST zq@Rm($Db&%+)7&7$N3XcW0hZP5i(8eNrw%a4Xq!yO-7naW{p- zwFFqL8G%o_SeF%g+%fP+O0lC@4IGdNMg8|K_w7LK?4pj5+PT%<0aJ`LGy16J5>kfh zNhbJ~L}c;@ic2a>(~CwiqFl&{6+LQ6AYsC>%OgP=Q%M6AsTeH`g&b|Tj26}LeV|Q! zn0=mQ<+sIWYCu^Lie0P%% z!Ho^)#D)4&6LoFCh_DY-;(O;DK9>iEvE{L~-z6gG$iZOuy6|1XM5{j2x{|<5RounW z);f;M?w+1Ikn-xJz6;o(3+nd0#%M=qk7*c&Rh$^N`g`DG1Ik&KZh1i<#(9(;>-1r3 zRvK~Hv7GpEJBH+x_P}#4+Rs$4DX)nmxu~;u9$l|ND1_IE)!`PZu*ssAPbj5cdCW?y zok!ZiH;!oAG{Trwh($S4fSb0N-o*V}&J8}NbaPA=$+M~WqJ3w-gCJEghv54RM28@^ zNX`1H0+tn%ujjqdn+b)zYa(rzD^66(_^Eyg;t$}LR9c7ryE`yN< zK1H$u!~x%UY;)tX4rxmdrEyk+QWDmKxRIDp1~ehA4>8g_CqpR>>v6eZ)?ta|kC?#xFQuG#XVspf~E1?CK zr^U4wZUkd)NHK2PNkw4>bxsOXU2$YuCWz!5r}aW}eKJvQj}s6REf2mI{h%#)Z(_h8 z6x(dbwv{%Ul=bm6IO3!GGOn&{(K)kKQIAR~J%XYSCR}cGl^2fGhmXY=KmOht<~S~H`)c?uIW}# zebVsv&AjA;l-Z#wo~Sr2_|7u}OfIV>%}g8(d=N&5O?kdXQ&T^?SAk(xx3Pwk$ybjP zzaDEttVionAL!-zJ+~pf@y|}HfE8G4)W>tRdT23#3O%z8yNu%T{QE&~FvLvJM1){7 zl<``~0--GrCemzPmIcC&wFM?ePY$mS*7(;-_&MD|7)UXE&ZkhSUc5ogA#(a&rzExbB&Sr+kzqyO3RB%pc*$qZ=JN5Lcua&crkMD>^3(_E@n?ZDs^6 zarLjmcGQbVGZ#G3{_LG!?AtpOL!`+^jW|&E`OQ_SHmh9in@SATc=lV_11I07=jit{tiq@cCMy5PRYLc~f^5Ohm3-m(KqL&JlWQ9nTsPARX&d__h%+i${_AC!)O=#f=gkLrqT+Inqk+tUbVK5Z1xKOf>sBV(?`dv zK~uE9>{6&|#sF)rxr!G_?F7=%E|QHd3a@~{#P~$Z89BaSti0vR9_xTq^;@D%FxTKU z#F8bUu0k9@*~2?c*zmgA+B4bnH&}L-_NbAm3RPvs1?Uc?48|vO{v?pC&Gp22hW%DP zb=MTta%*noSV8vE3$k^pCFZ6s!(}m1W(!RuB#?JP6zKp zFEVXWUM?kHt5z~o+nWaS+3aMrh?C^5n@<9e|K1;Ih3ja(5)9Y_Apnv;EG7r%|MWIb zPo-2Ifiyj@9YGvs+NNhv${{JjB1}nF;A(s=cI8UivA#jRmD4n#Ls0RVono@W2BX^} z%%y~#_97KVMTC(3lF~@>9e9XK4Ulsu`Zy>LgO2s3@smOT(nveyXa(MEfRcU;D#Rmp zhB!izry@_v`z6(S#7-p_Je1VO>6} z=_AUj5NhkFpk`D7%yW(rA?ssW@lbF9$>(%2wEO>TEbRus{9%ZSzrqqL{r{(DUe?t2 ziCI)cwh`s?=ZT@MqKr&V#Cr6T5}uE2F@t}q?KD-2((+!{QEVtL6pdm~8Kgv1@fPJ? zJQ8NHl!WP(iJN&bsEmQM$|{Et5)Sx`9^&(mp|=?42*svZ=lBki@#J|Qbl|&ORZ{o25UVBUB2L|G#9_{@kr*p(koR8KPs2lW;DOk~*GwGGrftLdbB8=Ykm{}0V z-J`{VIV{swXQ>_h14HSvYsaqF*EI#0@LSnk?w3FB{Mok-s$3#91Fsh!BaWUn@)1RZ z+Y_|mEEwyO3b>ATJBN-KL?!Yz>IV4t;d}wtSj*kl@x(_-?rQciM&M*L`l(dYJqokh z$~SqCMDs0s2|N;-l66NBQmy=TG|;zd<#xw2<#QEV1Zp0G&R|p3T@slWT>89#A0p*q zJZk4}mxwWqy*5Cp{FzyMCNR%0Pozvjsa(S@OBY750x==;T~+ab&C0Wa_NEOCw47Eu zb-O{5Wvraos5QwCBTxam4$h4pIfQ^YqA|vvi~uE4#iT;<5Bb14w!8Ou}>)n)zPCl`HZp@O`;PB9PW(+V0uYY|sV%#lxG&S%CymyCpYV{JIw3P=T&Q z2kiOo`KTLs{p)M{tx`XbjYAn?di9oEQERBzEL_cczKp(QT2$!o*^+^V2I2ol=3WnH zFa67F-#-g~lT>QC$7GPA7ad-dRK*gNyxX9puCvtnR%xdtc`@eq|Do(Nw(!zdU3(5V zu83u4s5})NcXT{+J$4o}PDo#I9NI4b+r57&!K1$M-!Xj`O?v0TS<_EKBkil`FpRG;_zEq*|Q z-frzOQeUy;M#yZs7r&`n5uY)SglxuPsFYNx_jdS2lLi$&Hv}4|N}3I;wr@<&M_u)b zAIFkff6?E8j$J_dNc(^a=$RGUXFJhcrpXAS*Fd-Kf3n%4Z{Y;=n=WTFTcB~~veB-e>^g92WavR7~hy{S^P)sAD-24d8{Wh?o3jlTdH zwcFYoWfpt*=+5bgOljmT0-8iAPz&k>_KIBziS<(C*(t1{KG~>P8+0C<;@yfNPAXw6 zaq%T$dn+Qp!s@XioBy;my6PaBb zQ4*EL%dX^Yhk&Do+hfF`3kJ(tT|;|!Jl`H6$9gIb%8AeG$tl;VDs`V^gF&vPscqw{d3Q zNYgzHjd+V+=g{Iek2=FtwnO)hS=#bXJ4H&RG8Jmxbn#he20TQ z5sfpYl55h((kZWk5ewuHP$6R!PK5#RG*}|7! zTN%a3w#BoZ+vi1Rk8w=)R8!@&u=2$N|KcFjw5=7WOhEH_vo%i-+vk#DZ45xvBgTAhTT1Lc54F7<-@9ZZidJpu_~| zr%^uLRW3%<1BAj0B_*u_(iOoq52?OQ` z{)0Bz%8PGZ1`2=x;@h>m#2PDTJjf$jCxW{=oTaEX#i-dW`LXTuFHv?=s>Nk#M>Blj zpmQ#xsm`^PvHE*_L?lVH(&Wz(NG!rr@lLznglZzfvn?*Uky4{VW=bnWeJ=xvoO6mp z7}Rcl45-~(d1w`tcG!-1*`7z>>}WODgjibC%MHyKQ{dGzV-|iOSe&>|&A%(eQ}lV0 zDSvFptvyk=iBuE~x!3GvaSCB)N2&c=h;@ky!^4--f>&KV9O*A;KW(?LcxQ9OU$u;Y z^TjAy<{o3Q$q_9=i4ss^v}osqw2~~ERy?TP8YOd_yCW5cAz%(WNp_9746}kX^ukx^ z@d=ag#l6Kd-Pv#sZuq0yDklz|9J@-##5YE_T!KtMp`0#NHt<)dY>jsm;k$l}b-P3x zQ+s`3Z|OE^1BF+gl9u}dyYHZlkdepxNI@@mE>-3EoMvykL-9T~k{mI_*pF3stj~w8 z-r$$<+IuwrJXJJ1FSbkm^uqC0_%UuX8Q?z4%(0STMELmvA1Rio^$hT1+r-S_(>yzzG7q%7+SYT)3vv{2}+QgH8Se ziC8yB8(C{VDEv>vodT&}#G>jv6FE<0k#9UaU(lml7re6j|4|Hr3lFmo+xJO=?5B(0pO_I2ou~9&|Oa(?L@! z*V@s{@k;wed>TV5AvlZf>!^m@bS0h>*!b!@*D|ZePe%ZDI?hJ0H9)Jr9?ieVgjdo5T*k3oN$$x4k*+#N zzl^m?&TTISD=OOv?19%`58-cXANm&%Rldnpm-06`9e9;>SExU898@s1_F_E#N*k5( zm1!=tOrWkMmBIKVEqlOy3^oWj1>=?{k5BB2)b73$hRDozxfQkv!Uvjmcr(I&%yy^1 zndaFtMtl=#{HqwueO)BDC|NMMbylF;7a*e}%)@f1YyD{VM8<5(lX}CB{l0!ueB3O5 zRe@)k{;rE2^{JcC#B`r)<2Wu!(JImoOzrlc{?mgz)fC2Xi^KXJEr0*7H@3g$~dWWzwg7#d>qSiG0shL2mj89R@7FW zxWb^awJ`sr``tH9L`YGg`4zq+FeIVNz#cWVPg|28*?t9_KmYQaNfg$jzLIK4B)WBF zN}Ur{J?Blj%h;hm_DMh#+6+#;5U`OGXfAW*OT2&`oP+weH|QoKEsHErhK5py-_pKg z+QLe-maj`7*8H_*A4b&gn~%{iTlf1a3iHWo>GIjDJZ}oVJgt#g6On?1tgwqAEWfyQ?%)%s^PA4nqrok+U0V9LArVd__2 zC29M5n5Ex8nF>*SaR4~gYoosNx)gkM3)C977>|t8Jtush-W~z*m1c=#gnCGuWW1v? z4sC;y1SW3oJrH(~`tM{^#P$tl)NSo}^kEU-Ec<^HUI~i)qAYNQw*+j;5JJI9tjYak zB5*k9|Mi8c3xN(~1Dt&v)m< zn76HNkPHAp)6(&>%I*wd=t;9D#AR+ZDdM?aEM2*DZL6bek^-01v zq}!sbd}UZGQr=73FA@$SdPxdIdGS4110qBG>Xn$gA40#;OtPYfLLqG3^?&ZB3#V`< zZZu%y^wz`D5{@AHQ{)%Vh%fCeM^&vm0YNWJ*o;Px2kTAzqMKb$bjLOe_=f48RHPPq zX(MhZPwY*V@5jh%JL^AeE(|={)`xAw`2NSeGf5ca0X^57NekBSig(H~ro`Jf^Jn&^ zzhvGQf!?nFqQG@^bKLQ!HvZT8mrpY9-h zzl8Ze)5o3a44K%Hr@jFrma@r@-VFOa4()7Pdc&)A8yzOfg4uiX^3wh{nWbRRglRZ7 zQ5zEuLi84sG3I9X-;*-al2$=o)KqAq>ulOH;XeN5=0k_zoBNb!lTWwPWR1G*gr2Py zKw#8z)^0`M+Yy0<`&uTkk%op%9n+n;Wu_SR& zYZiihZL~yYa8PZB5q)`Kg5tg>3p&XD2o_8co;aurVH>8erX>;!ty@OUseFrhAujI+ zX4y(b)36>AsHN|!eAc2?fRQ~Bjr#~zjbfTR-hclxQ|sksTBJ`=8=P$J_^Hg*u?Tpk zGOv2Je((ux`GY^ey}>-PoQwLDmX;*`vp-WD;Ro)67B8Zo?*cN|O~{f#8BNhPlh&KT zFOFOD#fvmDpqlfZjFB%nwf_{5YSS`y(;}gXG~e*H!Dr$>i=L<2O~LZZ(nKmJ;U|rJ zUG_TRU#(%~N-+@3BZIe7*AusB{_#PFdwg@q(FVp72iL7MEOOMcCl2Xaa8C#AATCqGb$YltBmbPUcD&XVZi-kulh0bYXr(uGkZ&ezduxw| zN%a^0En@EY?b&&<^SXI4^@8;`I2A-{j0YcACYW?lu^~?lUx=R<~%woYwV@rE4I&jMookr1OM?OJO zY?_tdKJL+iE%>Vgzz<0zorJXSH$_0t@j~Jo@72ZggA}&IwMS#W1s8$F0gpH&WGkn6 zx_y7U!N`)E&o8DEx9cmlgjEwJ|0={%h`gEO%d@4Qx3@y#3K4T$;a|%Ygefj3=k$A=NA_m87VDx@&*Ck!itUKfT*khTW5#1swA-1gPURfmxNV?7xf4Om`__q9%;f8 z0z$+QH>eSO%goNro&Sm(0F-~HJNWdCOm+^;U~d+_b+czUjJcx+LoKM%zYVVSPOeU!dH~llcT`>XX}$}ijn1# zLVEgY-JuHnUgQcWZ(AS<3hR$7Yo*+Ml<#Mc`Tc`=D9P%JBKtkkeu_}i_h=RI#X3|Z z?(M=Ci4vti@N(LZEtQPmIf7{gw9Na;rj0fgiUanij2=c$+7#O>#^{cjq4!)E0)WO( zuVSku!%j_J4f_Vl5S;iUab~6=wLJm@sw5?+2~PZ;NX(2&J7=+4sHxuch;OXjwS$qO zwR(x-+K=CS|HZ?&P5WY|a9R|w@zc!*0o+@d7ieA}Uac75=!>%Y>!rAlbU%YCCT=Eb zKUxQE2EXn2gMA*5SmC8&-kA7o+%Xfrum_}(z(qNuR>{02oF8IjI61`Z**TD$Z}vp? z@ie>^+`ff{>EqdZ!pko(JV+e$;-X5k zXr>Dpc6N&9`GiD}2*cT%;JH-y zCRe`hNUre||BlT}9gsP@EN{>4hepdU{bFJjV;`fdS$f#VA-`Zz4q&>yE4XwJk=!cE zCu|=A*llTy-lXfed?ci^4*|pUoiv8{9cVksn!#qBocPm>NPfOVI&g=~eadI|$zF~( zo?guY9|39+#`^%TXDT3+SMlJ;sez5FwKQih)k8lq7-#%*6_6aVn189JXNYgzcrUyN zkLpMmre%bFwz2fhiy%L8jox)`$IpU<(-7qQezai5z7JC5t9&I4+|$wKc_nY^v@p}k z3g!1C^z(^=rM!yuN3P8gg=t_KR0EfBQQy)flx;!|6{y9xTVmrSl- zslWfHiW58=Y!g{~Kg{&A%?i@1eLld?3BS|c!B1!z!hQU>;TGkRa0XZ}{+#v`1tll! zE3i%efRuAsJFRL()+iI5{ag)%@~Gz@;;-VjbTZVOVh}nt8xZzP4b;dMSbwa5ZIxgf z$;3O?_;!heG>mZKNC|uRm05AGrkr4%SjL?mV?m9Y#igb?gnbWLR%EJ;NqxQ;kF+xe z2*Z~SAre}|`R4Ff+1oVOKk!DHvU7>ud@ufBq*;f^FTxbH0`~7I&Y_G_tHThd=98vA zN=|&Cq&>YuP{Q)5Ws#hrvOUu8DRB|1{7k{`Y3ApUo_bXiafg+KySv`oyV6BS$@q_o7e=ZXzY0_J6ao0_DlV1nZ{85#m^4d zpDQ&6AJEmC`kni?47E^SoBapo$p??NS5n;H+KxV1d)q|wjja8AB1MhQGadjqP?Ki< z52#bxxi)Cnd>Q728hzqO{=c$F->&0IM!L)xU31Uz(;PV${+NYQSLwZ)@%W_Q@-s~h z^EA12)fDsvJ##=2qUuQFE>R@|Pv(1hF^z&2>3IGqX{@0lBsy?{k@jw~c9H$Z(tUfz zN4mYNw<-<3u6;ch_t}TyVW*zhFz=VJnYhBAZjE%r5hE@|Xt3QN-qdzcB^qeJJC1yH z?0zxC4;e>(tQ*a!hNmg=q?@Is_)~PCUd|WmJTa732=q&nzew4Lvm6QtQ-5Nfkm;RS zz&}f;B4^#)Gtmnty-zOl_woV#lu8Q83dK-s0$DAn_!OAhAf#damJ(O{*&a1vmivpR zyEX(*z-jsl81U=VgL@k3Z0eIh_Grn<#loA~c# zUT%)Btt z&dJ0ta_h6bCHsnr-yzI1VHy+$+w_iEBweZDldPQ%B+1cK096pu-x$8C)K;e5$Fxb( zw_)1slwU?Z6Il0=MqEq;*nQ>qyRrZ-vnNL%%j*vh_Xyf-$Reye@@@tzIr~NGev#6fI>LP*aZ;aJisPTsS#}djA3Hy{ zv&-2jL+&utY8t9JXaT7tZj|`{SJk!0GrhlY=hgXjlG~|-UoC4KA-OEAOm3N_NC|~a zQ&ugL$>cI*zfNYAxjP8inUg!ALaRyTvek@}au>gtDUxhugzfj)`u)-Qb9?Rc*>n3o z&-48}&*%L(3csN^xieQr(@}Y?B8{bMMzMUT=!nDK97gc)57Ic22(Sw(%H!l?aqP3& zI{xCnlA)yS3lgg^3%I5pzhIwGE9W!065W}#dlyx2Z10cezfPcbYUi{^lPt~s;F%jp z$A^ooByF};s$gr!jBUry+`@dtY9fMuRIqIq0pID@gs#8yL;3A^zAk2NoYs%K#QddW ziv*txJ#Ul5v0+dmwoI()&Ss-D&s5})FT5N%pubndhDx;(aau0e+ zxVdzWfcdJTvR-u9<$%xjz=gd15;*A!9}&$n+;sx4IhpV1*A;ZYZu(vOKkL0tA&V>; zB(@7GItCm)lgzzs_uQ*Nn7)t|pT-?`jt|? zXffVy`8ZjY?&?>0HbLZ+$|WFOs>hL=H~qM@2E0qQ=RIGBp*3N2eKAj@6;TT5XbcSz zK*j5jF8se5L|Wpym<>%f9c};2Se;TjR=|kRbW|1PGEuhOvg46GwiBjU^ukcg#x0CF z20grOI`RDkTk>tD_Oak*cB2xc3vufk>PCRGrupQnH^{_zJ?Y+y_H>@1umJux-Puj_YTrUAC`-V zFztajJLvcZVMj;8zt?*xU^GPcXv~C0Q!p&{=&%9^3DI`b z8zVL0O&R+BWbOz^#vo8A*@Upn<5Wg3(}uJ4d)LCLD@ zH<|8x>Acm4*FEAJI$>m0WcEubMzjIQYm(&Jmx>g5OhU;gz^Nf5gdTr7G(?t+Ji~jGH*z2XhGQ(nK=o;+A!s5x-7|FUNpaUY) z70~UU?=CcGlNGo*bY}g(kDOSM`eKk1^PvRFZ2sUu&;>&_37?|$Yk_t-vxC!Ib#`Jv zv9Jf!MV2s#iw5$`aeswp+SH%t&OvWjBs8SvxWX z0O*FAzG~?L{z7l_g%Q~6Q+qLxXz(%Rg>fiq^qn4m19|eH*TnZoB*|i~2!9JLv)vIb zdEWITMz+L!)D0z`-1kQf59`dkEVDMAbNoB0L1Nky;GEDFWna^6 z1oqv=HzsS~2dU91O&;BDsjAA1A+x{Esxf6cuMi;Wf$N7Bmqj$`3M}fD=^S!t?wEaK zxOpYhLC$%s5p-jvH4%D*S8>^4Fu)*e*pIAH8WE~X8zj49Zy#HU{u$}-km2pOQ&3VQ39gy@Zue2;;jl`)TQ%j~;eyTM zq4*>XMho@JtNVeYk^NoWKua$p25>2k4#Xkfwp?o_vA~^|wm8)qKZI9Lza}sitFrGP zd$G8hr7OzX#%E3ktvuCqkfS$T{dqtw@|Fq0JmK?v8gsSRto9=*;zPL}QO0^Tto2)V z)1ES7(J@|HfM3$mOeS2B@ z@Ic|cB^S+cZSq-&=yeQZQ%BzJGM=e0h_cT}POP7lD)TpmedUN~4^Fln?4M4fYa?kR zsgpi464i6*)^vE8=uGD&-Ut$X6lTnCWiRO+PMlLlaUb_ca3G}K@ELVx7kRyrx}>rU zN!!h1R^@qvJWdajzp2w*cy`Gr0*4+9Q@FQ3F_m~C5qGGjJ>+tO+{W+sLT6HrcXO9% z_5llc85zF|#-7?tp%w0OjF<`z&RFJQSg}1wfoeKap%xQTZF*yQWgj2O1OE zTAv?e(F@;W`3;Xo;3+rioitl1XdEkXR7|v~^<0Y|$wiC{M$mw^;}K@WC(uyb`|Yi) zH5#^7^Br5m9{!>LaBTrQX)y4S?E!$N5Zs|R&#fMn(-O298lpN%qzRR0mJKdNZ!7RM zxi(J%7{NC=fLCq?*^FES1sGgEC82)#_ZLgE<~2G1W~_2kQbTLSP>`mAsv+H)hiNzA z&$aOiVT>fKOhB2d;@lRyWyQ23>(#j_NBPpAv_-ypo4QD+4gd!zUkH;g1jVh+jmcNX zx)bJ;13xw{bEzxbxA4|ZdG@n`rV#!>rY}z*qtfOZ{agE( zj!DCuLs}LP+ow-Uid9se!jvTCeGA&DB^Qs zUvST6w?kZ2b9}MFljckDH59mA-KF>c{XP0mkRCo&524p1I<$dgL2PkMh{tZ8t2zDg z60e7iCc$(Oy48nS%T&3PN?o|mT)jwGbVNX_Ol zYw}DccYCo?36Cb!Ql6drOCicKi>-)wlG5U)Pdu`j+l*gdfCYzLBhH$AjKwlAfcn(^ z10mk=U#kL}8*^x+vJihiiF1l70QaP}&h|*n>qY1rY#RCciBrVuHH|>bBFTpOI8lF7fLB^_ZS!$+9i&6VcvaoGgx4>Typn!kB$|fR7xnELbxq%Re7W zAKjOW5b3_$9yQ+!F(j)Q4HWV+0@3gfb3#h&=X91;OVJ<@4-OFZbsa4V_~3B*X>3o7TX2(IWln} zG!;m)UG(w|B*e%#S`ldegL%cdPBD{x*KXwoi|zhOZ_;8O4& zVn$u0#rd&~;dD%d=`lXiw{$G!Iz;EzunN?_e-5Sxi1C}dl_y?qy4tX47* zzoo0{WJIdcNo^4%hpH!^YeN@K`@Du2ay`9(5d;^3qJJQ+8{bLAduC4h&QO$n@;)h| z_SjgHR4lr4`uGIIu*DPe2~rha#Db7n>Y_oTj-N^f>_R9{l*#vr5Ej5^N*3fKzr+37 zvPfhSgnDlL417bkbvBA5Z1wx5w@J9CkPy|wNn6{{dQrzw7`2 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-yeti_rig.jpg b/website/docs/assets/maya-yeti_rig.jpg deleted file mode 100644 index 07b13db409d0ad6bf1f887896e6f3d0fd5e7e10b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59405 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%0D1;wn^$71V{3R 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 EFKbZ!`Tzg` diff --git a/website/docs/assets/maya-yeti_rig_setup.png b/website/docs/assets/maya-yeti_rig_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..876ecba0a2b0e4496f590a650de56532c3b104f0 GIT binary patch literal 111360 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
^s@U(eB2|H$bp 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/maya-yeti_simple_rig.png b/website/docs/assets/maya-yeti_simple_rig.png new file mode 100644 index 0000000000000000000000000000000000000000..31a60ac40b3f712e7ecd1bcdac4fc80017977594 GIT binary patch literal 96188 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

`}O z9^j-WSHDP=WuzV)*zA?i-OqQK_2B!Kq*MiZCAb<#B0|YcAE=-PzTZu?F4T8ty^Ht6 z%9g?a6A)|kuLpvcK9DWtX8~1=_CcJeqx_eFFxrP}8uT31zNpAgBkdv;UaCs+SFvDUo!&CJi*7eW6pnP)^V&K)J)z{vFF|_#Byk`2won<~SAH4SP zjw9Y*PeY57-3xNX3t-~)@Yuc{P!u^OOK}o zVz&j2d|1v~E|A#JAWf{|3VjexH5A@ddmew$(DxzMduH#!x zVI*5%t~B!KCM2`6+ST6YDa-3~9V~eP7SjPTsdFZ(-|%bXQr1cguimsrqhS-fN{=lR z6DQ%8IRF&2?jSnCa1V{Qexz46cFQrN(24h{}kkxdO83dsEw0l zglw_N9Gmce`iys^l4V&1^&8Kx0@{rDW6ZM~w21FP7B~N?YzSk7lqHY*ba~& z+Mx4n>7~~!zjQJSh*qrELR08rR=H+CsoP^(RZ*g~^$LL6;BX4+2f;fp(P|X2fc4vv z8t(OJq-sWycX_)60bh^b<1_L=!alb zML^M~)(Id?ISAEzA%g)nlDDZVo4e~`SiYVH=;?c&hJ%2YdZoA}x>Bz#oArc$cp#bg zH-A=7hJmI)FALoe5pB0ef+$8w+8@kowLkoN>zH7>szYA*AMzzry`G#QzKU18T{DPV zB^N1j1krinOQUTBvlSm52wtiy5M$`<->9}ZV00{Qd+mJvuEC|6Dt`rp#m_h*&MA+K zkOAx!Z{QGS(a?9yP>q9-^T+2pq&Z#%wS*V#cUdPHuuAyCJS)^c!Z{b1kre)+%w^%m z$C#J~)lc%0qPSarqGs)L;FLrV!YSppY{w zZYR+RILZ~%C{eCE2eZ`Bi&Z7CkQ?q!l5QKcLtaK(mRIZ^IOw4(?lnk>39oa%w&$JN z<|}=tp-}$(=T8&jfrmT^GqI=OIc#hOCvds3r9Y_@EaR`5)%{No@ zbK9V^OH#Jsuk$7I2D^q5L*y8%p#6(g*NGJWLUM`Lsj`NJu!nJo7yo;UI`UmP$?%$h zQ)oQskM;7iX~^cPu>N2(sxVr;=5=QnzQ%1)>PgT~wWs^z~t?w4) zN&1)!Q#1uU_9PR|Ff4>9z@So%zYvW0wIuY{c@2)Z!MGksEqb8Fg07?hLoK)L!Wa>I zdE`L&_!%&MFV=$_U8z-KFQ^&l%<&6o%3^V$mY*R&h0+MIWOYSh<-&x2;i>5 z86ia9@e~wz>Mt^*MAqoR8U+q|&T3YHLQvluf^rU(y*jhp{ zzKVJnH;G$&i*_1XhcMsaL|5KCUQ?_9?T!%4mBdY$TKSKW>HwIl>=4m(e=}bT0SoKmm-A z+OPi_mpX{NU71y%1E{>*&-zD8rM}c{_~!=M+8|Z1W zUT{wd5L0^T06bPNI)cfmOKOo7EdmW-+pdhWgJMr^&^l!eKtliC8W6Yy@^EVt$$R4V z7Lt=s)VG^T4LwTcB?^~D{SpY-$IpSU4aqSkFB&JltlTcc7VXEqKa)od9`c0jufF-pDEiz~wIfd+ z0{>?aoFBNk3G9@3jRG{DUoLC_2IdUc#EHngb)s~j^1V5dH;6>Ak0=*Z@S+#j`G^SF z_)-RXAI?cvQr09*C5ahmmAYj#yBv(D`KJZ8<6qqKMtU|?(8eS3OXqc!~)m@@sO!F zv+yw`CZ^6mt$Cw_mS2SFyCs5MLW!U;?Cal} z$>yCD{v^s-M$WgO%)V8xD5kIC=tRSp(t=za2cjNa^9ldxUNmWQNEPv>q)`N)6yc7` zP5s2{{8mJ>p8#?5{1M?f64iMN=&=JGtHI5Xj+JXPZf(*iw}@nG&V^evDoki2ubUx5 z&7fd??$ZPILbSA=Lp@{7Czyo+1{XX)T8KDId@Y za94b}gQaR&ab0WRzLsJ8&D9!Fn>4WL!vxP6c1nmV+25um!LYNl@i{!FViU$1dL`d0 zj@Z2dGWhi^QNIA0PwKK?S8${S1VbZh6E1rAj_L;|A45*3NueEJmkT9{3vx@Gir?HOv$b*nstsivW>MADSck|h)78hMKBO39O5cD--njX1e-PQK3NK@-}iz`ZZAYjA_5 z>VSU?!_d&M3DHt|9!O%{B*1l~uzaFs*fw}`KLhnnibYIj5l)dRp5g|*~VzGvK8ncJPqLD>Juqy?C??vs?c-(U(bdfNfddy zt66iWHl0oyvto{k2b(SWXz+XTlM)t4G;#76n4NUIn5;zINs)#yGbMNa()Xt8e%w5@a zkkbGWkJj_x1clRzei2UoYJKfx{T3?|wY1t-#u{GzcI-~U4C%o_LI6)$3*GSil(`6vtD9s8^*I0WsPw~J0k~;g3FU=nmUri2I~Qd( zqox!;eXr4&fC&%Lrh|3ZnKYM0d-Q9bs_(O8%z@F-?BChkN>)im*{URwg$oZ)zEqW& z6+HyWqU1GDvro1gZe+T%^@ma{@Ty4g`A5=Xou_14uXtj3hVM9g-D)}gqS91mnB3mX z5Nb!bMqf>sL9y0Ad5JdpZ)FICeDLTD(EIHgb-9xK)>uazD`z~)@0$s=7?TOs*W;RZ zN@zXL%1e&MBMMyO&w{RBA48y|DipVJF-o+exlS?M`KwpqYIJ67|Fih0V4nuUP+Fx5 zom64_cI4xEyUJ<;Zkm{;IpmqIX?))GrwnCTNUVS7|IKvx_WFVoib?6FW(S9jJRR%B zGsWQw2qbW8C$_7Gh`~};Ha-U^?2SjbS0^~HKhMp=rnFB*f{x=vgl+j}am%^9bby+S<|T^DFR*W+06v9fbLvcBL-V4V`2y9%KBU9;R&#Djo5#Xn0I z(K8*Yc1xt^=XfQfp|DADH;0%_c|}_ZuWU2bsQ)+5hgp3EQp7w}Ofp7G+W~n#Uo&Z!62Ef$wFxHTcEn3@;?14| zSe+R?SZ$$7>;~Ncx6z2(#+oO(!G}gC#1+RSQtN<|4+nZF z-4BEjsZdu+LZGC4OcBGvfGRK8ph z{sbKt1wM8ry1k&xdT9IcTd6Hd-~U-_>nZG~es~SgJJYoG1|-vvdb0)elD8s3Xuk`| z{F`6e?|jW*uf~7{n9I#rt*e_f9&D1(DLcL>VT>^U?(;d8@A}|IA@)Fx*{Ga;8B+(gN zMEG6n5xUihpJLq-<*srJn>_0gKjR|9R7B;aiFB>#QhV6kv3^og2&ZFMeC6xhfQV`X zm)fsgmClyuHW_U78(%g5v_$)bwxg5({`DZRDbAcbHf8Xyuy!?%FC~9{{xh=O5j-6xGD-!`&K-GaA{^3yhcr;=(} zFdeWeW2Hv*sw)1;`Y%W&JXS~*)|g)$$(r%l?(kD=Te9E@HpO<$+$vYy+=hP8I377< z^%k|0LAd;eOl7mMscnHM8tua(Ic9HPgt%o9tVreJsH(Bvq9|ADoBrmq677)l(X7*Q zJ~f|L%LgAztW?@1x$oMNZAeOVlYjTA^v~{@nxQ3ofQai|=l2s}23AA~ty4-8T8d~+ zSGO2u3cj})M@N6J574`a&$MdWRW`SKl`KtRInIDyT(l;1k*j%sDKLA9 z@E;^(%98x-qcWx3a;Tug(>k>|OCJ8!$1|aKWgd&YCDIdgFKReU1|#%`Z_S92QTO(y z@Bg}jX*`b+TlTD#)9(s+9Y@b=GAGj<-q#{bR7JB_l_9$1`%4x9M{{rXI5gZp4^kQn z!N-*7GXHz4Wu?Kcs?d7ZUiP8$yE;URvk?M-dJn^Rac-%5u@RG=m7HR1uB@PH8S_ZF z9pnjN31j3M$J9C(#i3qQ9+#CgMege-JxfCWx*CqkKvf$atQyg!dIL^FO{MY7NHRAK z$XVZjXHju4nyVAz2M0bG@#dlVB6mCCGVNJUCZzrM@#CB4sDGZ&*u&{1BH1l(J2>H|SmJ?U3 zZb7xL)AW93bR`o6*rbrU+IJ()g=PM-1b1Yen|>hVy`4&A2=+*)wh{e9oXb}Bl3 z0K$aaSRkL4(|^qdj#Ewn5j)KLrG{54q{K@(=EimmX@IZS?1)<}>yutFI+>vLu2Xk{ zeGWsbIx-^e0MP4<;C;LtxC)9)Br*Us(`^p$MYjQcI#CXvnQ?vutN3z}>p|T>Q<{!e zJYh}izgcN4BOEbR5N<}EzlDBrk=b`#Uij0P6L0 zrV(5oLvOo=n#@=Lc4=pUd$Z%5iKOI`ea4^KrNDYhYMy>tF6`mW*i}BBqdVgBubV(k zZ*M=7KSQ+^0IQF_aM#;jkK$zMxaV57Pz{VL^vV@C(9X-w@<4NYW}WwSMgW{mx*oUU zIj}CApWg`S8Ox1V)JFp`tS$k7#EflM^5Ln875x5o7kXH?fM*_tTbhv!iWb*}JVz0E zg4I_@I4bI822IV-!K93>Dp_ZD!q2iqQJAHq zWvybwr!(cbm-Ru%i=yIJFtH}jkQJV)Gpu4r<|n(devsR;?-`C-08Q`ceXVqHF8GCQ zw{oLp(_x6_yOe{7jlw$u~fOa z8isLF{zH~qFcM?Epd=P%h;#Gq?Tz1JgL9@IgJg&U98=sT&vw> zU~G@C-L*m^F>jhp%QP)`4S#l+Bo^hE%=~@c zN}yhWd9`=tjLTQ1LcY8e+fwLscIrj|!zu|Lkn0OL z*CnGq+2&MKG^*h4MU;Isx#h3>1}h|-!ii_#Soa*X1&1pqp3VoZ13xDM|5vJjjQZUG z#(weo&yvFFa^PabOK#ec*UEik*XjBt0)Nu5E~gd(l?#>)8w zj_Fm0wZ=$ zWN7v*fTo7-Lf~sM92NU6t!e&Cw>T)fa$Qfh)RK{R_!(s0LgP5tOuVVh#6@RUVs^j3 z_Xu@X#*E$sC;lWZS-w{8$RI2zmY2)O9hK7_@n}=%VuQvtI?9F0j4pWebk}0LxzV-g zxRw@4`LY?xP@OCk@!9Y4pdmghysu9a5FKWYv&%)fps-|P6^n9Tfa zAR|nPlaapQoL({ZW~0R6kl1^rAzgiEb8;Dv)H=T*f``!jrLtT$o%39$mei@X8<-t$Q?JddY8dHPJCcYpr^rJw$5_)+l+=@6`J>*VPa>6mg;=0H zc;iHCL`TiZo8Bzgf=^&X13%$BN5r?Sv+h^gpv?)7E!FLOr5(~7JsBw{9iaBVPDbJ9 zIJNzoB=k9RKAmEt$Pf$Y$p|v_`Rmxx&#|jzq`@7S949M4of77tE7tyP7b{Dbi3V4Q zZc=7*uDGfTVr~84gRG0sHd*2qk$^@9E;clJwQG(S8G4VOVO`y@ozF3f>k{Vkrvs1n zD^t$LsAEuN@RR48ej5LB!KOVoj-#tNc=JRw-u8y<;z&>N(S9WW-j%&=0(fu173IL+ zh2Bfp{m^#OQ$mLmp0eUk2|aR7OCz;;wS?SEjxxjm$l=JJj?H}ADS2=|c*WV(r)e+z z`Mklriq7=$F!v2HoN(qW$v7tZuNt`Vh_$DHJR!a(j9Xd*bh!7LzTac)yU6=f>!c6s zUliuCP=7y_mR{Yf%He8~g9_KQy_`;RzUhGUwubyP`2U)MMSX|52)Cf}Wu_4F0)QZ| z;mV20R3-h|1>m72oPP=#gjzan8`qPoBOg&6=eod^nhh%0mVwp<_y2DXH`R~vECFXy;FQC~(dc_UCWHLP$1#@h-`az0V-xeKqXE0!L zd-Uh+wxDapRT83=z`g!ha8@8xE8_-z&2@4aT99^QN+GT`sKI1vw?F!d`UyrYCG09l z?2bI!Gi4PWQ)EjPl=3OU7jd6w{s9F)z(fx`;@q~aMv*>Rv_LW&sOpbq;_oo?n`{?= zI~6m)e$#bU;cE2_?yUibf#V$SvrBrerhRALzQde@SHYE7U$k6k)8T#yZ#-<8p8OVN z+Ca@MeMD@edQ1dmCsv2p6uq8KEdHaa>GD_S(I4#wOFev$Tr&edvVQoC3p7-hUIon; zeV(S@yoi)*Qy1l?eg%~a;Rn1G`S4#M!d;nI>X5Q@!1%64crYOLbN4B1-J-U?oaVU4 zaMSAbVX*j7nr}?u$I}X77tN7x)lwG}(!bu@4qmSp>CJt^cb)!K|m1=FE`Nx<_svFQO;PL~_z-UkM#bb^|2pE+e(smR#uBgw^zvjL{-aYz5d zQCy*W$xo)JyWu{;pkEyLh#eYUrJecItZ#9%?^mtUgINmwUBKTG{CZaTwUHBp@>Ts7Hoz4?aLX#34SB@DLeUJkw9^1^Ur}O7J0jQB@P?W<#v0LC&&l!E%{OQr;93a|vl)Hn z`O`|Z+s){F7$RXi>~?O5!qhC56-AFI_#yH7uZgMEOUgcQC-mQSp|=mPQy=h=l&<~= z>Xej$IGLFOTY19{h>jT0)qjpFb?Y2b-|)Y!tHvkSxPQqhQP{dw?Vx?Trs{a-f^(IRRJ8~U0r5`+o$*E?NNPPjG<>7-DyTJb zH=6a7nwOP_I3fx>zyAQuI|_Srqbu`>0{)fegCFsGS&!qhn{r=KwR+-0w=mKa-n*l& z(hkss6-n(d3)F=&f6DsEdW4D74zV|Pc-cTF>yeoD5|8_w{r__QI&%GQ;4?dU^!oME zzd=Ae5UWs~6#1!QSEa>G33QVC9j_g6eWzt7)Sq+0^Gn}+0|OVG#^xO~!HhZ;ZQONZ0Ivz74m79&&AAd(8B968;Q%+j_9e_r_`sbUxg; zL8p=c{;PQH>NU?w{@OoYBOP|zsBOgtiTw?o!!B2M2o4B6{7s@y$1MajgxuT1Dr}Z_ zuK)~m(o+Bi{%r#Vxqq|CW}G%x%-rjb<29>opkSh<};&|xo=E;Mn zB_>Zf{fHd@?GMUDDGps_63(*T8d(O?$0wjtY?N5sGGO%ai7ka$3~^8Xb_bN?<`4S0 z69CHJpcBHqVY{$zl|4A2@|BVsHf+5*&Q^?FTL(Q>8mk~3F%Fzcl_C3QI?wN|*@RAN zbo7&}JP{nIS+L36E0K3rxX=NB_WWPp#AZH)I)sT6fb>IgtOW|NrnS_E*grxC0`fI0 z(U_X1WW2u?c-I|9tJ0ZV7q2osVZa2Hl3&;U;yP|8Cli@yp`m`C~;0AK>k&2`;~cjwR`P3{PUACIZddLf}1y1fR`P6S(HuP*ZS7UB=1u{5GyS$K9;ZVHaZPudp`WUT6Tl))(hirw!)oEk0u7d zxUz=Xs(dGCx3Mkf?Y8Z#pP|D7AoV|SR_ zM)?oF9~J&d^m6In2d;Bh%6FR=j?biFs4o^^Kcsj%1xAgVDAl33aixA1Wpw*1Is0@& zcHdg?AfMj6VQWb2t<{P?eaRjly{^ZvkPy)UwGBDIsTD+_lg16WgZ*pj2@h7tplp{i z*(z9b{8N<;ym|@KY8`^(4cSV2W~wnNWh+DVY)ia{yh)rIyX1EY zo+t%Sp!`9U{MY4EYOUI5^D@xe=4*%el^5p28&}T0DEc>xa7`<7l*&p6*KYLZD3}S7 z8+T&`Jn5LzQMDtThxdY)Y~R@@UX@?qtvk*w+j)he}p zzmCxbh=_YlsVZGI&lz%&PiLaRPA+3%+5h?nBZ5Mj(^k{qlH~*?vz^5I8DojOm)>`Rjq%OupaA?)-Mw6l4b1R$F+dHc#&&tki zlO@uIwz#k#_u`vQJC4{Y41b#puDngFjCbJIKS&udSAWw^vsj*je*y;&S<`rl;SD1I@DnD8VfXNX zyE(mwCl)v_?KZzx@SfcCwu@WNE>zXkepIaIK{@~Ax7$LGavzrJ`ef^2y#r;`>31j< zAX2%}PSehi7VJ*Z+7$u(hNAe=r#EZ`tLfG`yKk$O+PBWrAVjI%wpb|{yDjouu73XY z*cDRfI3w%qqQ8!VY`enMTFhM=c>-ztlWAV=t<8co{h9^R6;tjKlg5~e$Ctcc zQNK@#&;;<({?N7^()vTR}oHk$-F4T0~q9UZdBB%HapuV&HV&zm#Ew4RQnc{ zTAL6)buzsWo;vF>bQf%dZNaEGq+eS6#yzW*>d+4rdvWKENq())85T>b^sx47&!}qU z_V5OhWhBj>{IpT_kCAe>_nLXq6%R_|(TXsS%cB+dYS_wD@6unFW?~0r)020HiZHpc z&{25mjK|PRMgE=}Rd5sdBrssoJC(#~B9IRF`?EO*6t)wIc>Z_$d(PFpH<6EE zvT95o<-(m`AG>5{g0u1PzU!RYa2aqOs6)+0Uvp29?30|WC>*HpPehD@cPOoEI3w=6So0KUYB;Nd&p+=68(BT{%L zfa}Z^nRq5|N^Wv9sW)S)#~@F}-_W&hD0#B$Ze<{rH0^>jArZP~8{a4Ypn8@cmV6BI z#4q6S)kMRynBuS#NqrFjY5~b*?J?PvVJ$k$73$iyW+c#6m02;rqK7G2L||EVvpiC*)jpLHkT#Bu&X>~nn!dkpK-1NkG{bvU_%@^TG zcE6790eIL>8Lwk~%v+VAX_F~DTaXaF84rp=jVlNioE_n&?&mF;TPw63@V=6HU&DQN zqpplbC%g!ApPDTaMZpu#{`y2e2yCaF0GV-{w-aaCZ;-`7V@u+vzgC3n{>g*k%M)`%}iq?{ny16OS${14!&l3t>}N!5CisdMRqUZpw%L$ zA#n2xRxXTv)ZIs&U-}+cm~S9n$|4AF*R@tw9iUVt_O`)(y+nX4S^JJ}&`#>i=&Q9p zY(N`)((Up+gkWEN@!<5!BzK3*6~Z8{(dY~6%A;3zr_C7yedfi`s?wq&~38C4mDZ%r6f*etx=-x`8DKw-&f zf5j#IMDH&zCr1&nPD)fqwnTqG5o&qC@{IBRcdXpankuubkLHx!&ribbfh!0JJMytP zI?3h9IJvH`tjKYrI%VgtGh7ENk2^sJw&sQV(Qw|Y%l_nrT-8$Vgy#w$&CSso2dq)& zEz!LVYu{oH5^=nf>`)(v5`xy<_NtCwL-gij5U-gg%n34hSen|jEH>|wzZI|ZwhSKi zjJu`kj!!`LyGY0zX3#s-`0m7bnFf9PCp9m$lP6PuUBg?@Z~3V~Ky9Orh*Ei@cJ@gI zTlLplA{@UM;%V+r+yI6>>yg5P9uhpn(XY4Eb(n9 zv?VHPZ7=O)He7hcR2k#zOQ4Xe6IEEd5nJuSKi^o(wM5;{_?pK6WI~$mpTicyJq&_k+g*>eWZ`9ss1!%3IhkF< zdvQYG{%eQ?E{)Pp29OO@Hn{x%(K(%c1+Dcgi`du}J=~|;TM+#++aWJ~XK!3|ZG1)F z2VyxXp9qTOjgTOXu)uV9pVBVM940WXIN&A1ozc(uUAJ9I+t!IF!iD3n@(an- z50P-*_FQdFn-=j>Exav3(0ZoWD9z|MN9lDifwz=P5?>&89Nk>iYn_Lfa8|0w){UP0 z*zX8T`2@UD2)tsWcj#cUw<-F(t82C%`zIIB<+~bKKTP->3zYh}hmLyY8(NP#++$5q zZNvh7v5%>|$;F3LdxS4ivhTd)VIkb1xaoRza=LwX-&pTuONF1L%1bwFoPFL2?K|9v zvOotrAvvX%V2U2TcQUUQ4ucXeJn2WD7S2|apk20z6rc2{mUU&+effnj zd5_JF+n>2nu|uUy5nM;(=*4HwG&?Y?JVa%6p;@cUdwjTO!4RGre!dpR&20?D=r&f9 za8vx-FvR=btY2IBp~oPLmpA99QURN;h^Z_??fqy+QEMb1P8EZPrJuOh>9o1}52|{R zBy4bK-N>N8DA7Jox*+tdYu>MyuPlID7E<9)^1*gU0;Hry!6jpdyzht}>?Kc3Tu{TF z{)j%FDeb^O2+oMYj)3>(;L(#6X6kjP)(Srk=>Im+@!Ys~nHziW+gb$EndbjSym)n4 z{5-3kE2KtfSNll+vB0WwOpQ|^hf3N1ND)Csd2$yHG%4n9akbK zaaTfLvU0-%&fC1f>Lv0%{su2@LHRE-?Vn4Tfm`u~&LrS99XZi*Zh(x$^PkTS227bi>arPrwyQQq3+y*O|_rif#%WWKvxV?|14rcji zQVS7SAcXffvWr?}9P`4a+*Og@3PjMbPu%l+P!7n*xX`~Uu8YpqUVvK9K^fo!)9-@%>Ih1GrZ_Y_aW?vynoZL$N3Tm0OqIQ z0NyTq=+foNq7)zBEi9ohavI^j7l)1q?oUfp7|~IGBjZpQwjZ~x3jRUOTPf7|x5re1 z$7E#9hfc&Omtw1|Q1_ziZU(89gvB_Ng;8Fdey=Ep!R#K1bfjqJo?+FT*@uudF9XTj zZ^pBSk72!1ZIbv%VA_6L*$Owt>ur~L;?Mi4IoXq#Qq^A%uxKKyeS}7qNpLId9fkK; zC1J2>{gp43ehul}-r!NzAa?Gt_Du3tI9*mHf*m3v9_OJFkX7E+*?p)YzLA*%`wTy_ z)ujsjUEUX$s>;FN9%Bu_^Lg-?+UiCnVSk>7Phgi4Q!^~>I1`NNQ7!jw@8vwpwkAH5 z^D;@9QhYHIjde0`DL#D<{Zk+9GUUL;LA4)qBFa2VIE>s<7jAKnXP&oV;9dGf zeZ1|q9aimX z+*>|qKM2*RWN0bMeU#*yG#$(s77;TIwUk<8reYpuuG zfXBdP0aw*#=sXlfW^~O*TArzj4A8lNZt<*%Eri-MhJ%K2-{D!7mI+wGb|}idBs8ASZ!KB%0NspI~7N7EWV&O_i&Gp;x!@Je!soB&E z=njX{FG3IhrvR)63c&tXSa)OK^~iMnUvLE09Dz05cY zX>Z>l(PA9JPO!sX4lEUfTuT6wQUH=n1(A7v(E85eQ#l1Jba+b|o&s|3bITNe?yT~G zGi3oo&-+H$FS0!9B`&P6alfS#bqs)su7QbQ8o_${{(~rHg{vb6jb5B+V4^se6plV7 zKZ_DNU0yo@+KXtfBHWe~;w&+WrN?$2)EC)m?`{>U`TU0ba>gB+!|=ex!^2LZ!Pk(d zA9jeeV^BnKNA^GkPVWsQa@v3P0O6j*x5J^zf2ke%@DK;U%Pe5zYUPJld_wMx8eRsX_pY}b=vf)(a6Z_868{_| z3tiQ$rGdbD8^auj2ck>rA0_Bnh5aog`3tW0S-yk!d|4bfUvx|zl z%c^97@v2l!>a4@iWC&XnB+E6JwbxeTJ1ms9W}z<~pB49fTFPAQi#_Q#QoqX?mzwwM zQ4%TyWT^uiDV%x=4qWl;6bvNKaUPM7j-6o)kdS*u63o(S(BaRKfG( z3!W{p@nZbzy7Zu@H|L709A?RRr#?ng@@Pm$Vg@7Y-PV}_IUhY7;c69;7lHuN~Z4cs0-CCUfVae0usk)`MCxx@)g zbRx-bFp*h>_T<+;l0KU|`DR3MVYFTY_DvPOHyd}|+fEQ6My)D2s6Zq@I$ zs0tM+Y@JHxNeSqKr3qwN#6|q5m72`#h2`lSs+c6Pzy~DhN?B~TaV;9c&fzo|1Chz-8?gi3m6k#_v9t`y3*St`8(zow3-(UfI3 zp%Le4ZX=#%B{8efRDS)*g{z34oHoGvURaJjb{3QB&{I#`^hKv@abKjU{bOLYDsVZ2 zw$nkM8D2>*yHCR0(yXgEs~}9-{q|~&;FifY#1OKd z4U8XnvAQt^{~qHh1CyYg6$PT-rW|w^bTQQ_IEao|VBZccTu0PPT6%Z4Gm9^D-Yj#? zAl^5T-k1w8Jfr8;Dlz5#fHjRFo~z9k)wR^w(u5~Fn=qa&5~`0qX2w!0oyPd0K;}QT zc7ExBU>MTNT9YmX88){xCBdZxQ_=S`Q%@Z9MZt|k*zAS&3FdVf)eN(VM~Rnc7cB!! z+4y0WVKdh4O5{2&&N~%%bd&rRn?lunsY7Fh)!Ni5bkHp%e{WS}Uvmhwu!!A{SVp`h zHJhd%&lzXuPK`Boqq^+PqO_@1DK;-@Rv25e6YbUj|Y=bf^ zlqE^NztsV%3cBK$DO-c03zO_%oWM5O6cTVD6@+*_!aI6WWIvo|QFI-*zkncejfe7; z>^#Yu@&-!_Y8#5DV)8~a-$q}wgK=0hRs3ZQ4qqK018)^007=w&i5o7K6B`mA|z7U$3DSX6>} zptAF`L5J^IETbkV`jRuQ9g>590Y#sq4z`*K=CIwtjB+*`o0xmMKRj9l@oELpTLy}s znoOZf?b|@lY7F}ry0weC)$?T!Qs+~`YR0&ylk`km=L-s1kFH zgsV(Hosnd=ne!Bg{ zhCeT?jAfil>{4$p;FQ0dtWTO#>ah3mmYiXHSCzFe0P1W zr(6w~O&m(SXXRE-I@sN;befcZv^;Ye^zI|4+s;lJ2fHJF9WZmf()m3o)ew{mUHxCxK>d-|0VfetodgZwl zoQ&qCl*k*T!|(j^9Bp}l)dq1o)|jbr@-Im}u!wyGLm^V~r{ z#{SGpqAai8Ka}?I3hd(@&Gq4_jiP+c{7W5EfH{$cF2i_uaeQc^w&x&_tuNsPvEB4r z*>q(M3)qK`&BPbH-SIH+t}MM#Qbf)U;=2yD{-I4eAJHRKP_k!EoWz9a;pd{d!5)m1 z@NiQb*_Rwwc+6tMmDsmJcjQ3PRLq0koQ9io11aK(Ag*g5pbYo40ruxG7sT7iQ1ty` zl=jjGd@JZradUQR>uJM<$cn!v35f|D%xg%QE1P^xAwN=368?K)@J`+}ad8reeaf$7 zC=mq-*v-d5lvWDm2KhMAi({h`$njF#%z<=96^M)&#}dmQ`6HixpcSVy4s{Ri_uz1s z3IH8yKotwQ$87RQKM?cdh${5ky^brDA|oN0o1_w-{-!?IAI#wrng1^ZP)jT4xAU>s+E^nV?(g zM125BoG>gGScHppy1^JY6p1}d95bZPKlZ`azCzPuf^0D@DH2got0(p%Q!%2X1T4BY zyxzR5-AJbp*5jPe@)HGhYM5{Ld8Q6xc%H~XJwZnh_*U%WEfZ~~=PUW>K=GTt)fvY^ zbnqw*HF?H>BorN}FWDJhV5yM&KovR<6oKIP0hWCr&sC4>rR1t9SAm8-ZcDBKLLabF zg5{}&b+@tK)~ETEsmU@B6kHG}648{Gk27We8gxG;^1n)+??tCY$=Rs*jr7aSY#9JR z;Dz=Vcn|FbMQnw=-IDrs(K2R`Jx%nN zeq+JOh%?q_{@Yz z@WL`ccsp;snB!4j+o5J2#OSMmmqac|_#_%eeV@gQpmuk7-GhoSGxj|&$3wI}+iY}n znK@35w>>e{b?`rwLH|QIS)4$oE6PrANf`Bd&y;vS;iB`QW8!b zd*b?#I6}y^s5^H=c#1#NVhSbn)ik784KulMeKwwxHe@lGjuVv7qa|OIaQsL79*BK; zc1Zmzn#pRvXTA{}yH2mkdD9Y#*aX*|dmCkGH~L84_bGvJwltGjpJ=MbaIX6zvFQIM zsb?G>GVH)!-gX(Uh^i2qLUmiIR`tx9EK9#lj??~wliiZ1M$vr7G)9-#15Vf%bwvI) z;CzFutzxepGJH$5(QEer$eZ=&Td%EpXDT(ltk1kCYNjgmaN2$~kAj$S8@mNfv4hLp zNma7psO4><^*ud}m^m@Ko*BO}bkyrpv)C6dlNf5L(*M(30WElkxZyKdF}RFd4%fH4 zje6{3X6Lhn;$9d`Y8o@+Kk$@^*+^p^5C#sNMYKX*9dFzPAW$PrZW;X|$>MK=WU{8h zclUehU&2fIo~U65ejuTE;oFa z>t*zr0Av)Ni}z{0jlIjI;-ay4{fMMr{W<{@=MfBu?uAZD(K~J*i4ARD=166C+^?X$ z0x)Or+%ykwjC3>oA&mXh8Rb~PP!S!5Po6M62k;FsOaU>63cz^4(*9fhS@C2YHcHH?`v@*rjI zW{Jasmmo4@<^gm^SnK+*`x=7B1%w}{x{OS}S(ee#2Okt0+Lf~!yY)THkZ~iQe&Ow=yQIh();tT z&f!NR%M+!g&vQqwafO~J!4ERNFEf~?&Pekke(<9arE%Pmf1Ao$o?Wj;-C9*=JB!F%$u{G>F2eL zygHmk=DQGKf>dTJ6Kg`|>y)ItuhH~00(NzK8e>cTi1Jp{Qe5Ml-7v)5LkPGd=c$7# zcQ?JU={3%U#&}E~@x(%G%C^NkEs;_lvK7umo`{28Dggl%4ADPc$6hThzPX1GeYgW9 zF|R~Sw}+64B~sr^$vI=(#ynPpNRzO4E%PbdI|f!Tp$rGbHs|#W^xiPG2M_S~i6)2i z7C*;SNGzawo#gEL0mkoSv3A8H{h1n#_)+Yh`b;eQptANQ2~Hn`8C3zh?lwm8iCNO( zEN#1CA~1`4&?j9Pq_@0TpP3cZoDvzr$+j6OlP<$Kb#n@5EIet3z;XjOKrbwoD7;v4V=%d~j%gibhR_r@ zR!D6o_Ukx;a?v*GWjM2#*-$w0%>6OrK$0JH8P!-A_VE;ArJt8bXs_eUA8|6{?&j}1 zZ{{sca24-{>ZOXGAGfW2_yKLZ1)=UP4BDmma~Zr6T=$~f&Z9!mSjsRJAhNw7jENzP zv}-eP_j)@?($6=_CrnunY=#LIctodFMyZmo9Mry+#;4x1O_cyKTL+Kc7GH3t+f!wl9L;iPEq zF2v_E{WV1;in#KYsTvF6DQPa%@_IC1i9SfzG%a~3^kctOh^`bU%vH!>*$Fv|6Ic^t z&boU`Eri|CAc-zO0yR3*Cf283vVS4-kc~WgLF!+txl#w;kMD zZgDE2lGhD_hd_z;N4I5u%9U^`QZy~M>zwlfxY*X2i*r>lsF#G&X;grt&YRGRsso!D z=csw!iLydl=EWh4r6OClI?%uH1e+hsZc?mAZvQphQAp&2;emCbwEB{Ttn!E=*(JWT zo9KzMj)H%Y>i2gqHDjQCH9O|w*)JP(jR43C6jo_s@(4YK8KNN&1kuxEMB~{HA`YZ= zr}$C53UtMaL;8P@90yA9Lz26l^u_*Jqcag;DPg*QGUyI2t8cDewC1tEUU5J0GxadOBQZ=C?H-Bwvp`}M& z42B+c1+TfDvwSe9MTDDjHWnATS1o-jvIhXNci#)CyN2oLjZW-c zH3e36pQRSHR3R=8)KM8ih3I)-fn){kfRf>#x#)>&`%g_EbLO$9EXM|tE|%`Z5!x}+ zUtzEs^fH(M>U`dbk9)cw9TgXCyQ}2z^a5Z%nD=PUahQ(8oHp@RxavK;tDD7-1ho3< zWDP@D!%L#*46hRXX0?>>)PTCg#|*Z%+jySD1WYA0cL*b8D@>)&?&~tF@dy{JxB=H0 zG@8;NnfVG6t`ru03h1@TcaD94IF#>!C1@eK^Pn&{z0;`$%)c+Q{<1z z0@tc&QEhegwJoXS50IKVCO=xX!qCnP{Qs8m2WK0t6D==@Fp^Nn>!$LomK|OF>~wzM zS_C9V951x$UT5woexZva{8{X%Q;}ANjzA5jeaw`sfO#*I3UazqeVb<8p)EI-s*Wyaz)XQ2?reVd2w{H3G@0*&6X-u&FE?rw669~_3b(IpVA zbm<5J|7Q`c(7n!8AZYfnZ695*3WKm+dg7qF*6GJ8TdCW^JP%jRq)=?!YJ-ZlopzGi z)dn#xGsB?B+7)JYLP*J|g&tI##-6QfAqn@*{LXhX zn^s~d6E@ySDn?#dX_6^ssx_|oSiGt9pyCR1C?SNpIKKIUh*G_%>o`~xy%YV2Te@ZY z;;T@})H?sw-Pk!#(rygzlI*JzXGNEDC!+bbz_n!*^o7h4p^u??*BY1Z_&jL%;Ij92 zDNEG^IdHgxEuc*D))n?7R3{WU(xHAro;1|b67LDGW2OfcFQZpf`?Muguh|G=Tk`jf zu7|nXe$T?TAnvKjuIA=-(tnvl`nPHJY&f^{Zfwe5Th|AQ1N@#}CHv8|N~>a$r&72$ znuBmJgc1J?8-lc0vWhim9!jPZ+F;Ksna^W*kEoO*!Gk)m_U-wvr>fiA~cD4CR z5u^F2T({jKR#@6E7p<}OlogzxMc9sIaT%MH65(Q|YUr5k?dq zb=gs5i~9<2g`;Nl^UwmH4^*36(M$Bwu{D;U!ec7V|LOM+&3*&5TiFuZ%wRm1GGhDK z5=@5`edwkUKa`(mqMwe?Z|62B-K+J`TaRD8%^c|2mbHbp&q-vPiD*LHV9T{rv%E-V zQ}uPH6vbRC7*PursbrJ<2KmfTZXh@o@oX#A%t;D{ekN|EE$}wQIf;*m<{7(v+>utJ zI}uO}T*)e&>0$4?x3+9zJh%B-#!gzW!o7J1rcAHh7hN)Ujzs7z zhA9Q>Cu<@oYu@U{koK8>csQF)Q%8#IG=`ydTME4DO0{Use3TI_*_=)68v_9vV>7@7_p{ zlCAPaimvOd5(UbO-(zTct3+#eQRb%<`=9M*tCGZP7|$-lh$obRukBR7;3+ti_}d*X zh1#ExXBg(id$$Rzz|sy@o2&1O9cN)LBCFl1xJs0204TMm$t>rm`rR|4b-Vg|^h|Zu z`KHK%_VFX1%@L1}sEXD-@{JJn>4P4UY9MP^nSqBmmu(G`t-5O>R6V$Psw~n2rN?-# zgMZ#vsk3fz@T{muUxbOL;guTvWqb{I(nX!RJ|ylkPc>k7ku7Hb=IYHcEeygc`nVw- zVeL#<1#nVM%oKU=HI2Y-z(AHU9^Pm(?1ZKLpty=^(&f$hKC>c8S1_K(;5#8~-3{9I z4KHORvTlBnl=U%gsW1$zMB0{~Gx6FywEzUHM^3pC<1np4xONUwmhh{U`jnarMc0^x zf_5_)p^?)|b>~NC!7EUG7Zv>e_}7Z7&eOgR-G6F3niv#iQr_ACEQORq#NW0Ep4`A~ ziFxYK>!+j4W4I7YHQmUjDYfyBh^xBKp)l#u1nbi}bl^c290h~rHGQB6gLj7TnQH}e z8IQjNksd_9Ve?$fT^vmlzb13P5}4--2Ly4yHMBz6`i>G6nC56VEJ`U%Z?R1FO)1Rs z`8k*HGuCwKcpQMPzl7bEXH1xik>WX+iB^-lp6cN0_yp5RQp%1$D2yCmC6GMC;NP`c=rryHrcC--orWaX%;0TEXPq#{rOPNwFcm~W{G6MX zCYvr}A$~?^MEmBM2)~n|tQ}d_OP@C6HO-#Z{|p=E!ls>%wuX$A1tz;V9g8{LuucrnNtVDj}JkI?&=y?DXWAKr36rc;a zkgbVkPXD?TeHs=(CqukMYw17__&77xf|Z@bPR5M2b@}x6G)!2RaRZ%@mLawJ{shT= z>D74@TfAM(@OKl#fkQY#H=puj%jrrg?F^X3Qk@S|WvoT4$m492U_~1yb`BdaC7oVu z@$<2K7!#!z3qo2(z#sYb7{j!+`mW2|xqmdI&0TrI+z>krnS~xIJzlkxa9xQ&shgL~ z2W9*?%;yXH9>S|%QWJQ>Kb~2NUbt%O-LI97sU}=(MJ%fS44?>)n{e(T3wb2zkr6BK6fbyFGZJmd$nU`puL{;!teT(bgyp6fe zC2gcv!^uQ7c~ehbYO^U%>A?DpnYT>SFB-}a1tXs#@m*#OZx7l`X_&C0JL4GZ>w>N2 zUUCSfvnaxxGnAmDj=ly+>&a|ZRXmh%BJo2neZRjXgydhSJ{|#!7DAp|;v@7VrTO9w zxFmy0vGdY54kWt0zTb8rJ+5%-+Tb)KIP5Zh!`U_qyUx=kx|s8A7C#Fi0!y8k`+d@$ zeZFT(m0~Fjor<5o)bXCWiQVr20te}JlKuG+)yLam_APozkr64#n2WS9tsP+-2Hq<# zt_`y6l8y5&e)Z%wHko@?hmNu~AZ(Sbb*8fSfDd8zo5oUo^aL8``MH;=$c4vUJ?Ke{+o5MvhrV5}W>KO9_xUQ2F?eN<8becG zl%`{(t^dJ16IY76-?h*8zsOFejZgI^>P_I!=(BL0J*||kS)ze-ac#Vpj7ZCs(+|3N ztmO{er4hc9twl|<@}dt9s0TQJhIeCs3|iJV2=_ZIMLKvQCfus;T|uOc$j-l`0335A zI+5jIZNcl217p5FYB14on6`19s%Uei(t&&B(|c5)=@I2D%xoX!g+;X){i&;?p0Q{X zd*JCMf{l;ITIi6YJ2#BcJQuWT=_TvP^gy()&OWw}jig>;>l*J&nTtr$G4|vi89iMA z!x;5Ye%@Ui3WE_Vgj%#!N~wn?o1ji^S@$j?Dts_S<8wa?i|xa16Tic(L$UY|UNbhl zdD#2`AJx>CKzQ7erKGR`$2irH;ZjHO5sj2YHa*}F3_RM2|I<^a$ND-tnBxG=@Na&ReY z07^&2FFglM)CUiD5PD@6_Fx#5X5abaR8st2H#`ewsf%yxTV|B$!O}$E@9T&I!m1*~ z_4((1&V-~~f=0%VwgL1%{+RCE!lkXnEwvbAgHVp(s5Gi%Mn^pfm!B!_x9H{i_S*K1 zc`b-AZy#=yaV_a*b+wG>Nb98>H2PxkT)wTmiB11_tGi6!3`(EYx|m%TUUI?a)MMXvIr<{To8LF|)9FMDZTqE!tF41B$7`8|^htVC?ps$D-k`5tj~pDv#^JjK`qONLH2-Dk)Q=O83$wO6Vk296CtBEC6!B2 zwRJnQ%d{wAW?^Ar2=ldn`KcR}EB3zxWibd^bPxx<`fk9%@F~487D_enU~Kh+Gz-M= zwz6|ew{%zqDzZ*9f+zMU4w!T9AAxMJ46uW{a2tU9b@f6U#a=bIEEf~fMoHQo_Etk$ z_W`;z+%>en1lavyHk*~j-%{g94*W=|ZVQ))IM*3?M)-eqS^E)XwJBj14)9_Sx%e>HZOcCM7QeF6Nn7H9q!CiA0?yx%`M0&_2U5OcHefx zPaH$>vjCDhO)&qnRLTN&=ND%+oZ~i_VAdPp zyBuRb|MZsG+9yF*aZ_yURJvs$Ror#wd}+o0+R=hrOPP1FW=Qw_HQ(Wqw_t=M6hrmR znR0pbON+yL`N=zRhi&WI=RcFKGfr8}|GV3a#g4z>H*WW!7W-5ma?W)7jc1V2<*jE5 zyY8`>JCZ^oZkMe$(C%kA>e=sX(4iRp<95Kt=U><3q_i)Av+eEXh2G+8w_RZ z$+aWSDB|{vBbx@k<3S3D9&d7K-|`@K2YgN<{YNt+zlcn>UMx$uZzQgD)1Je&y??`T z+%j@>V1sCjQwWvlq;0=56;o@#w|GfO^O8*IW2h05$=jfqA-e9%?yrvd*DC2pBaV>a z0;y;Z&0#ArG9LEE{CLllgpB_8mFZ>?? zhuksT>$%wc$eJ*a`aMR4e0YYO&G_aG$;W~kr`_$mlYHC^e8}He27^@xwtdk_#I+|G za74!e)N^~Y{K61LEhjShBRHoDJx8#Q{sCvvj+gT;WIgr|uz#}|9v;i?0+Cs{@e@Qh zTWNIB)0vh9_hC+a6VTDuODBtuY^Ei96^MG&0IcBX_`LlEcRk)@a|?+rfz4+=gD~ouO{F zJg7)#7ezP!i}fI|r}YOb@%?CKzNmqNTlZ3P5?Gv*K`7@Gf~YC$384l+fZ|KKefH z$LkbMh@JOtX4kJ8jdqAObkWQ3lOGf=^HreM&kU?$emoU^5uW6`!_3$BXnx^<;w)ne zZqODPFnW}+MUi~xV}d5n*X|Vipr{Lm#ecz2d0r2<|Ao+tG1u38`!Qct0ZO_ZgI{R5 z&~m%kEhzD0TW?;AAh2jb+$?X}uI$_|W$1Ydoz@@oSYzes&?104d0IBu*{E#s`> zuzPS>?OlhNBAE0GWd`&HxnZ~$np^VznEl4NAjHSh+!j#Jrl=(j6a$_Y&C4S`Z)Bh% zJ$riUb~N8nFb61r4iXP&yI(S2yYo50NTxzAghBn_LR!ne_i(RS*Eitsu4_iyZzR$@ z>vO!a=Hx^hj4bJHkkvxAzsIYtk1NmCUfaqa9Pl>PbKgk}>buV6S<+Xb;u%{N$zJ6x zO*VpV&o4Z9*@Y?{K_`*r=ibxTrw^(39d1g&bb0R#Mcay_tn;m9XATgG+96sdF(nuF zk9gb>dw1eu#uNTky9`6^w!)a^G`Dd7ZtlvYT)WU{>>Gj+<{M6z{->X;I~ensgLch~(} z6k+c%z$wxw)Vf2(*DMYl(>_^zZoGLct2D{BihiO-0zj`xNE`9@tpCf^yl|#?j*OQ+I_GxlYxw7(j)bzkJ+a17Eq~d2K&LsYKh0xS+8LRE?%$iFg=<%= zfZUx}7gQY?8brx7+*@%?b4&aS$SLNUL*L>zv8%br(!?oarnwzF(5lj79e21zpI|Ke zntV#nkzX-|`^&pMdtEbjESNu@G;i?e(vDTYN%|@I^2|@@aLV;XTq{y7dBk5VUTldb zE{;@m>nXZ7v`Qc44A0*yfBFJgidwrz zZU6EsEmU~u!+7Q~V^{s%zFKkRhis$q=#&GWQpt;N(p0)@wMN-Fh*?0*#3J_YTvL^e za^Q&y?c<-@@;gO*R1-nFn%Cd{q{z9RAWb%{UC_XF@3<{Uz9Csf+B z+Hewr2D1?xVmwls4>5PM5B@|`%m(t#i`Bo|Ehymg?TH5zyu5kdsrkgO4NS^CvV!yG z@)kZN+AQSG&n7CW7GmbhKSvAq2w~K)MI1mw;Y>v8h+QaKfE9lZ8uj*CB;kcpV`x+Q zw*M`A4{owZHxBZ!GaXqV1$xJck9`I89zVZWr^pNRoNHN-E;M7w*H?ZWNWR@-qYA}? z;$@uiJb$@U+8u^E|DlcWm!N~7b#&jTF;rg08u`nV1Q)w(3b$ReV#T9?r;6kAHPl7q zhMW{0lNUq0`zR+Gw@sO5oZkK_abb2rp=~$Szpbk27i_2Xg2Hrf zJ}Aa>E7K?w#mX+^8qzPhV72{4>UCa!+!`@_6DF)E=`=k@wr}Jo_p>WjT!{=#co=VIb7`l}A~#OfL{A0p9@eB|wXu3| zFmc1THetL9zkK=#V;eu8U?KL{_7_5I>DUe^O?3Wmquf2>WLA&=57#s^zO$Tq)(YL&oJKf=n6`V{t|`~ zcP)S^4jHe9j-A@WbI@VtyQ>i_z>?jg#W!WT&fHPdCd|k_?RE?>J$A&qf@(Xkd~mfx zbn_iHTE@?yR}Ne8yYGfDAc||*gsCd&8`CZ3lr;98=ih(Ho@r?oWt4jCt8sUl$?ThNN(Ru&u<* zJh_Z>F9vXeIGjlQqKdW~h>%>(N;}#R05awA0)GiM^26mfP ztmv|~OH<%?ht8-$QI4sLAPzW5eN!14{QPbF-}XOEi9BQ)dtaQ;^v~Zn>Dq0&X{{o} z-LYQD-v$>jG+@^UoJ79h13!P?n_v?hUd8DRagTg9NNxfj-HHSUI?=Hhy#c-s$wB9J zD-H0etMqbIx?9}eKh*<29Z?0q``IsU0sLLd`o(aMlZ*d?uPM&3M{B}gzghhD8;wQu p{{0oQvOaI+Ki_QnBk{p}B6MZaPu3?BE-e1^*b%qGm3F6p{(mVdF984m literal 0 HcmV?d00001 diff --git a/website/docs/dev_colorspace.md b/website/docs/dev_colorspace.md index fe9a0ec1e3..c4b8e74d73 100644 --- a/website/docs/dev_colorspace.md +++ b/website/docs/dev_colorspace.md @@ -24,8 +24,8 @@ It's up to the Loaders to read these values and apply the correct expected color ### Keys - **colorspace** - string value used in other publish plugins and loaders - **config** - storing two versions of path. - - **path** - is formated and with baked platform root. It is used for posible need to find out where we were sourcing color config during publishing. - - **template** - unformated tempate resolved from settings. It is used for other plugins targeted to remote publish which could be processed at different platform. + - **path** - is formatted and with baked platform root. It is used for possible need to find out where we were sourcing color config during publishing. + - **template** - unformatted template resolved from settings. It is used for other plugins targeted to remote publish which could be processed at different platform. ### Example { @@ -63,7 +63,7 @@ It's up to the Loaders to read these values and apply the correct expected color - set the `OCIO` environment variable before launching the host via a prelaunch hook - or (if the host allows) to set the workfile OCIO config path using the host's API -3. Each Extractor exporting pixel data (e.g. image or video) has to use parent class `openpype.pipeline.publish.publish_plugins.ExtractorColormanaged` and use `self.set_representation_colorspace` on the representations to be integrated. +3. Each Extractor exporting pixel data (e.g. image or video) has to inherit from the mixin class `openpype.pipeline.publish.publish_plugins.ColormanagedPyblishPluginMixin` and use `self.set_representation_colorspace` on the representations to be integrated. The **set_representation_colorspace** method adds `colorspaceData` to the representation. If the `colorspace` passed is not `None` then it is added directly to the representation with resolved config path otherwise a color space is assumed using the configured file rules. If no file rule matches the `colorspaceData` is **not** added to the representation. diff --git a/website/docs/dev_host_implementation.md b/website/docs/dev_host_implementation.md index 3702483ad1..e62043723f 100644 --- a/website/docs/dev_host_implementation.md +++ b/website/docs/dev_host_implementation.md @@ -45,10 +45,10 @@ openpype/hosts/{host name} ``` ### Launch Hooks -Launch hooks are not directly connected to host implementation, but they can be used to modify launch of process which may be crutial for the implementation. Launch hook are plugins called when DCC is launched. They are processed in sequence before and after launch. Pre launch hooks can change how process of DCC is launched, e.g. change subprocess flags, modify environments or modify launch arguments. If prelaunch hook crashes the application is not launched at all. Postlaunch hooks are triggered after launch of subprocess. They can be used to change statuses in your project tracker, start timer, etc. Crashed postlaunch hooks have no effect on rest of postlaunch hooks or launched process. They can be filtered by platform, host and application and order is defined by integer value. Hooks inside host are automatically loaded (one reason why folder name should match host name) or can be defined from modules. Hooks execution share same launch context where can be stored data used across multiple hooks (please be very specific in stored keys e.g. 'project' vs. 'project_name'). For more detailed information look into `openpype/lib/applications.py`. +Launch hooks are not directly connected to host implementation, but they can be used to modify launch of process which may be crucial for the implementation. Launch hook are plugins called when DCC is launched. They are processed in sequence before and after launch. Pre launch hooks can change how process of DCC is launched, e.g. change subprocess flags, modify environments or modify launch arguments. If prelaunch hook crashes the application is not launched at all. Postlaunch hooks are triggered after launch of subprocess. They can be used to change statuses in your project tracker, start timer, etc. Crashed postlaunch hooks have no effect on rest of postlaunch hooks or launched process. They can be filtered by platform, host and application and order is defined by integer value. Hooks inside host are automatically loaded (one reason why folder name should match host name) or can be defined from modules. Hooks execution share same launch context where can be stored data used across multiple hooks (please be very specific in stored keys e.g. 'project' vs. 'project_name'). For more detailed information look into `openpype/lib/applications.py`. ### Public interface -Public face is at this moment related to launching of the DCC. At this moment there there is only option to modify environment variables before launch by implementing function `add_implementation_envs` (must be available in `openpype/hosts/{host name}/__init__.py`). The function is called after pre launch hooks, as last step before subprocess launch, to be able set environment variables crutial for proper integration. It is also good place for functions that are used in prelaunch hooks and in-DCC integration. Future plans are to be able get workfiles extensions from here. Right now workfiles extensions are hardcoded in `openpype/pipeline/constants.py` under `HOST_WORKFILE_EXTENSIONS`, we would like to handle hosts as addons similar to OpenPype modules, and more improvements which are now hardcoded. +Public face is at this moment related to launching of the DCC. At this moment there there is only option to modify environment variables before launch by implementing function `add_implementation_envs` (must be available in `openpype/hosts/{host name}/__init__.py`). The function is called after pre launch hooks, as last step before subprocess launch, to be able set environment variables crucial for proper integration. It is also good place for functions that are used in prelaunch hooks and in-DCC integration. Future plans are to be able get workfiles extensions from here. Right now workfiles extensions are hardcoded in `openpype/pipeline/constants.py` under `HOST_WORKFILE_EXTENSIONS`, we would like to handle hosts as addons similar to OpenPype modules, and more improvements which are now hardcoded. ### Integration We've prepared base class `HostBase` in `openpype/host/host.py` to define minimum requirements and provide some default method implementations. The minimum requirement for a host is `name` attribute, this host would not be able to do much but is valid. To extend functionality we've prepared interfaces that helps to identify what is host capable of and if is possible to use certain tools with it. For those cases we defined interfaces for each workflow. `IWorkfileHost` interface add requirement to implement workfiles related methods which makes host usable in combination with Workfiles tool. `ILoadHost` interface add requirements to be able load, update, switch or remove referenced representations which should add support to use Loader and Scene Inventory tools. `INewPublisher` interface is required to be able use host with new OpenPype publish workflow. This is what must or can be implemented to allow certain functionality. `HostBase` will have more responsibility which will be taken from global variables in future. This process won't happen at once, but will be slow to keep backwards compatibility for some time. diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md index 135f6cd985..2c57537223 100644 --- a/website/docs/dev_publishing.md +++ b/website/docs/dev_publishing.md @@ -415,7 +415,7 @@ class CreateRender(Creator): # - 'asset' - asset name # - 'task' - task name # - 'variant' - variant - # - 'family' - instnace family + # - 'family' - instance family # Check if should use selection or not if pre_create_data.get("use_selection"): diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md index 94590345e8..1010169a5f 100644 --- a/website/docs/dev_settings.md +++ b/website/docs/dev_settings.md @@ -355,7 +355,7 @@ These inputs wraps another inputs into {key: value} relation { "type": "text", "key": "command", - "label": "Comand" + "label": "Command" } ] }, @@ -420,7 +420,7 @@ How output of the schema could look like on save: - number input, can be used for both integer and float - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) - - key `"maxium"` as maximum allowed number to enter (Default: `99999`) + - key `"maximum"` as maximum allowed number to enter (Default: `99999`) - key `"steps"` will change single step value of UI inputs (using arrows and wheel scroll) - for UI it is possible to show slider to enable this option set `show_slider` to `true` ```javascript @@ -602,7 +602,7 @@ How output of the schema could look like on save: - there are 2 possible ways how to set the type: 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) 2.) item type name as string without modifiers (e.g. [text](#text)) - 3.) enhancement of 1.) there is also support of `template` type but be carefull about endless loop of templates + 3.) enhancement of 1.) there is also support of `template` type but be careful about endless loop of templates - goal of using `template` is to easily change same item definitions in multiple lists 1.) with item modifiers diff --git a/website/docs/dev_testing.md b/website/docs/dev_testing.md index 7136ceb479..434c1ca9ff 100644 --- a/website/docs/dev_testing.md +++ b/website/docs/dev_testing.md @@ -57,7 +57,7 @@ Content: Contains end to end testing in a DCC. Currently it is setup to start DCC application with prepared worfkile, run publish process and compare results in DB and file system automatically. This approach is implemented as it should work in any DCC application and should cover most common use cases. Not all hosts allow "real headless" publishing, but all hosts should allow to trigger -publish process programatically when UI of host is actually running. +publish process programmatically when UI of host is actually running. There will be eventually also possibility to build workfile and publish it programmatically, this would work only in DCCs that support it (Maya, Nuke). diff --git a/website/docs/manager_ftrack.md b/website/docs/manager_ftrack.md index b5ca167838..836d84405e 100644 --- a/website/docs/manager_ftrack.md +++ b/website/docs/manager_ftrack.md @@ -4,7 +4,7 @@ title: Ftrack sidebar_label: Project Manager --- -Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](https://help.ftrack.com/en/). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and its basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](https://help.ftrack.com/en/). ## Project management Setting project attributes is the key to properly working pipeline. diff --git a/website/docs/module_deadline.md b/website/docs/module_deadline.md index ab1016788d..94b6a381c2 100644 --- a/website/docs/module_deadline.md +++ b/website/docs/module_deadline.md @@ -45,6 +45,10 @@ executable. It is recommended to use the `openpype_console` executable as it pro ![Configure plugin](assets/deadline_configure_plugin.png) +### OpenPypeTileAssembler Plugin +To setup tile rendering copy the `OpenPypeTileAssembler` plugin to the repository; +`[OpenPype]\openpype\modules\deadline\repository\custom\plugins\OpenPypeTileAssembler` > `[DeadlineRepository]\custom\plugins\OpenPypeTileAssembler` + ### Pools The main pools can be configured at `project_settings/deadline/publish/CollectDeadlinePools/primary_pool`, which is applied to the rendering jobs. diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index 6d5529b512..9111e4658c 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and its basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). ## Prepare Ftrack for OpenPype diff --git a/website/docs/module_kitsu.md b/website/docs/module_kitsu.md index 7738ee1ce2..05cff87fcc 100644 --- a/website/docs/module_kitsu.md +++ b/website/docs/module_kitsu.md @@ -7,7 +7,7 @@ sidebar_label: Kitsu import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Kitsu is a great open source production tracker and can be used for project management instead of Ftrack. This documentation assumes that you are familiar with Kitsu and it's basic principles. If you're new to Kitsu, we recommend having a thorough look at [Kitsu Official Documentation](https://kitsu.cg-wire.com/). +Kitsu is a great open source production tracker and can be used for project management instead of Ftrack. This documentation assumes that you are familiar with Kitsu and its basic principles. If you're new to Kitsu, we recommend having a thorough look at [Kitsu Official Documentation](https://kitsu.cg-wire.com/). ## Prepare Kitsu for OpenPype @@ -38,7 +38,20 @@ This functionality cannot deal with all cases and is not error proof, some inter openpype_console module kitsu push-to-zou -l me@domain.ext -p my_password ``` +## Integrate Kitsu Note +Task status can be automatically set during publish thanks to `Integrate Kitsu Note`. This feature can be configured in: + +`Admin -> Studio Settings -> Project Settings -> Kitsu -> Integrate Kitsu Note`. + +There are four settings available: +- `Set status on note` -> Turns on and off this integrator. +- `Note shortname` -> Which status shortname should be set automatically (Case sensitive). +- `Status change conditions - Status conditions` -> Conditions that need to be met for kitsu status to be changed. You can add as many conditions as you like. There are two fields to each conditions: `Condition` (Whether current status should be equal or not equal to the condition status) and `Short name` (Kitsu Shortname of the condition status). +- `Status change conditions - Family requirements` -> With this option you can add requirements to which families must be pushed or not in order to have the task status set by this integrator. There are two fields for each requirements: `Condition` (Same as the above) and `Family` (name of the family concerned by this requirement). For instance, adding one item set to `Not equal` and `workfile`, would mean the task status would change if a subset from another family than workfile is published (workfile can still be included), but not if you publish the workfile subset only. + +![Integrate Kitsu Note project settings](assets/integrate_kitsu_note_settings.png) + ## Q&A ### Is it safe to rename an entity from Kitsu? Absolutely! Entities are linked by their unique IDs between the two databases. -But renaming from the OP's Project Manager won't apply the change to Kitsu, it'll be overriden during the next synchronization. +But renaming from the OP's Project Manager won't apply the change to Kitsu, it'll be overridden during the next synchronization. diff --git a/website/docs/module_site_sync.md b/website/docs/module_site_sync.md index 2e9cf01102..3e5794579c 100644 --- a/website/docs/module_site_sync.md +++ b/website/docs/module_site_sync.md @@ -89,7 +89,7 @@ all share the same provider). Handles files stored on disk storage. -Local drive provider is the most basic one that is used for accessing all standard hard disk storage scenarios. It will work with any storage that can be mounted on your system in a standard way. This could correspond to a physical external hard drive, network mounted storage, internal drive or even VPN connected network drive. It doesn't care about how te drive is mounted, but you must be able to point to it with a simple directory path. +Local drive provider is the most basic one that is used for accessing all standard hard disk storage scenarios. It will work with any storage that can be mounted on your system in a standard way. This could correspond to a physical external hard drive, network mounted storage, internal drive or even VPN connected network drive. It doesn't care about how the drive is mounted, but you must be able to point to it with a simple directory path. Default sites `local` and `studio` both use local drive provider. diff --git a/website/docs/project_settings/assets/global_tools_custom_staging_dir.png b/website/docs/project_settings/assets/global_tools_custom_staging_dir.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e8660a8863239310a1934a49c2811b4e0c031b GIT binary patch literal 9940 zcmbVycUV(jvvq(7#DW+El}-qvAVrZTy$6sYQlz6u4?XlMMNp&!6a=J67Xj(g62L<5 zz4zXGO}GdAz4zX?eed_&KX^h;_St*R?3uM@);@1kmF2Enq`n9Ofv(6Sq}4zmLShhz zz><^@xU%MdK@s>Ma8#3%1m(5SE&^Y`W)eygAW%X0rIY6ufbS4Hgtj9HME2wCkD%T* z>m_gzijsMTdTMKma(V7x0+MquF|lzpw?!FfkOH^dR+N{PPH~ zUJ((IACr%ltvn4LS}mo>$DSIxwA#%Xj$n+hfIZ^lyd)v=(u@m^fKXXD@!M#u693Ewxj~ zP5)$YXxmnCDN}vgsEwfJHnkpnMa<(j)%$dImDYqSLMT3$xt-sE<>h)F>vM&ULLBoM zF$+%R6pmV)pD+%lJ_BPMv8=fAyEl{#MR^huG=6EE$mi(^IPFR2sgN9Nal(z09yuLm zFABQo!wAfSC1mObGg0>={XCZ5ez})f5gw2Rx0ZxwbtQcoHu17CebcIwXO$6`(_MMe zWASvbQsll%WLlxiX|9r?CeKg&WH30H>5*Jr$#T+cIUO7K?j0?q6SdEZMdcg9_pRAt zRAY*d+eOnal{ve!ls6Z7K;GH<>_6X0X1xy7= zU$lE7W*wGIPs1)b5M8_6e(HZa6!cm#y)s~Ii!p0AFZsQ|C@L0kkB&9J{zoAXJ1pBn zd0uVpkD`?k*QrHDs<)ij-+z%?D%4?EwyEd}GVRd~$e`-gsgMTX3M`NQ>x4@R2!o%L9JuH*$>pN-~8+sLFw($4m`o<3~3YIvn{ zweZe9Ne%h(RCz$08>6MPADeiEgM7%~62!9DF`LG7QEAy_TWe>q6_!cp9yl;~)X^hc zYax)jCDYv&?HTKt`RhlnCw#`Nje+Db{N~`Awm{lJ=9l(d9r!_4lG#yr^ZF>kO)`Hs zckP&W$z6kc#q^7oQ!H{ifl)ba-}I;q+TFV~g99iA-!^_yzVPl|462Q?w!s8b87*2` z#A4W2t=KXCy8D-Mo?UxJ zJBw>`d1j{){5o0Lh;6qh@01GV@o%pW%qbh0Ivmi#NwbVulJvv~t<_E+RnQMpaWrF9 z|AwBtRaDuVB;i-t%(aX6ezDw5u$eFZS#LNp^`X2C&FymAgAV=a_2>#&j1c zeza-Z-??t7N$t4AaK(Ak@uO8Ajy#azOHTxkB{?j-H2A1ODV6nau zg{o%%>%zZBkbem>1_12Xy!<=FIzyw*EQEmF0-m|v%#8jc5vZpl0&#P=`gGw9iHiz- z)pnj&trW=jEj1J(rz|Xh**EGS1uZC0z$R-_j%V?V=3o#i2ZFYnIk&H)K{8jQ#aR8*0r_GiyR11|R#WX%&Acu%6 zIi&j(3tSDDksv3|QxtQL@wjlA21dZ2aTpjnoBgdo$m2#B$*l?Zl_2)RW1<-0qtID7 zKfzbIGH@e=Qf@A%HN5Wzt3!BSr;bfYW;fR{IRr3Nlbk4T{d8)Yk!TCE-zZo6siqGG z<*vFtw3=RWm|Vcuc#{=+l)P~Ga&@W1N&+;akDDzqQZ}->2Dxcq1AobXg(l}xu3lM# zlmmkiH49{9vhmZFxreig!e=F-Nx4>&Ds*gU{PFezCWlTO7w3_6e7$yw1z|@ zZA^vkeBG_XDt4gBTfWM&5KLT7(neAu@7u@t&M^VD6e3ie$~P=W(V|l1In$d6#2Rr+ zi}QQ_kvZw$d1!~QG~w%*O8b{C4t`cXlQy)ml>r0#G5}WTsx`9hF_sU1qhLFfxsWd) zxlIlj?F~0Cr%OQ=A$qHV!GRGEr@Pivy z{gnL<93glSIcku*l{tr}8k4&jdDcdMvkw}p6+zWVgm=Fs6^y8fP@~jaA4a4*yExP> zyJ%o`rE>R9Rw_K|jfSJ9^45qWsVFboz&|FyP9grtgUDeXxx5`AY<=fA{)1O|>iQy_|cG{{YIOyBdLnL_DnO!K2o?e3(z#Dwh2-#p~ zTPb-Hvc-_yk(yo7tLZUh9cx||(>FI7t)SCfD=hUZG$1*6KS7^#KwVlA4xJHGOSyr2 z8*m~l6^IZAHg^+oAa=TcioRIl^fmxUie46LFMHT3bL!*i-%FCs$6eu3i-Ws+NUH)9 zdh7PKk3l_bi{!CPeQs_|+9IpWu+ZAWG!Y(Y!nhzru$Bj_x|TF)n0NSW|74!waO{@upf;Ryfav3_JIO3n9s zFwj>?27X-@m@WMDKukW6y6TdrqMToD`D?vIR$iTwpc~UH=!o6RtUWJ>?m>}e>!!cR zc1#h+^=+);@MO2(fI>vXsrPogPlP(@>tww_eM#5d1X({p&B^EW&NL9q5BCDtE)Cz|Fq|FHerjNS12DE2UnGlNi)_;a}4=}Nw#SOPt3FZLc(oj$F1{B^*vQo$vH__qjdbn^B@23NS(n&asc z%t@;)UV`jw=7-P#wp!De-v)`5mHeeoG*;7oU3%?@u*Os(Qon!Lb$F=&D%^YVju2PmD60PB>$Ej45ozeU7cxeKdn4H;rA(G%4Q@Pxsxm{ z2AeF}H;4)9sw8+5kmR)=usHX}oPlkRq*K3EA|{g4N99irDzTOL^6^<&g20O4fP2I{ zxxUtq-A&~q0s^+L`Q?MnBk?+!|X=1`O&Ocb;jWp6k+nRO;LGf3gf@`Bs*Yy9>A}qhK?C)O~(! zg8$-83u!{t?mc}HXQi=Y62l7SgLFdK8)#4us(v6bfgXj|Fe|OvKSs1E7Fhd`DZm@Hef9gI!3 zXa94Zh_@0`Q9P+@_^H7uWbryAcUIHm5pv}5c@)<~e>5Ce*o(wU!5`=5LLuVe!C)kW z3wnFV+N1kh$AeI{DV!`T)Ran>#sKs^A*K&=cOSNvYU%C771`3EW*n7hQ`2zxaq<@{ z`8l=_mP*&^OIo>E7`rJj1kfm0z~OEY`JH3>e*AjGL#mB?*^#sgeF{UZ&v@NBApz%( z_2qa{Y!6?RmkI0fqJu0sh`P6~+0qJ*B0sbj*6tPDN);0Dy69IgbG}7q#*Kug?lkG| zKjE-QQOVoXP<*K9hd|8f#rLlsG2VbI#w_(owVJpjKg8}|(O`i*u__xY7di+fJdc}Vul`s1QTzDXm^D)G zY~M~9Aht&DmzvH0O!ks;8@QioBzOulC zZfNdKo8kD^fzcM9beh-Tk$(kjPVQduS}cOzI(zRW;&4V{e)g`Ds070`i*kaTQo9#qiRrn?DE!L#G=A<09+W2z$i*wW?%U_?1mcoNn#5QMqeC+Gja1xa) zSLK6(RJbmz<{8|+HP4*-6a119K9GBUg&QKMt5L~HE3USV$(Yk~=)W8fI>9~w|(Yj`>lUiUlY z!HJoGsx%>AJOjjHQz>u3NhYy$*445#9E_vQ=a`bUhtII)L(j-akbS;$jV|6}p_A2+ zl)5BP%=TOJOj5<74;xs6;2ueeY){PfEy2RQ@)wD{IH0ci&r z@jTl_%NBRb;`s)*tMTMqlvQjD>U(w$SVsvssbJ#{?arWV+ZWdMJUun8D@`Sra7UfV zPokJqk}A!y_s$m(h2j_5#fFO!S=qx+sq1W?OJ9T3FzR`3C#_NWEXw%Fg?DD?qZ>ee;6p5DpWZkF24-`+B-e+9d z@N;WoW-3&WKi&CMO=0KwD$~nNL&eMK+sktB|BRN@P3ecwn!a(fEvm?vt5Y}@5XL*Dn)mX~jzGuS;?Yj6; z&wY97nYxG>GWW4nv6h{q2Ie00v&ubow8jbk*E~{1d^i6lB#zs< zxQvE|7&Ou^%f(34K+t22H65MI*{(FNSKC{^m`eA>&Q8ICgriWQL(IJmCiJRIi_Mmn zk_-uu`%Ut(ju*i_W2f07cGBdKnh9Y~_v|g9J`vrMQWp`W2bSoGFP{0;Zj8LmKqYwhp>JrkP2P^xRazO` z8*n*~m&x+^aP#}8Ib324hlXC|&Eqj^tmRhiJux<;*RB(aFH6CZhjM;EyCmbVe!Np2 z0>og3ZSbr(=BvHd=qg<3_ja9%PwNGBi7eVG9w2?zCB50|^_j~&}!oIF%7fjXbU z8xpeh%1J_5(j4)orTg+gQNTXzvOdo1ww^m&Z;P32ONLDl_dkVKJ)q4mx()TEcJ|(# z$gAEDT^G9iY?pa*pu-bR3iL5vPsE@KV>U8k-INb4AN=&H{17Rx00!U!)Nx~%Ib}xa zmLg2z^>^E*XAk6W`2ZLMI{NGlp9#ThR-X!;-GAOi@r3~#qQXT4dUu45sPabLIdcNY z7n8*R@e|V^^1Z!y7wRf?31l9b2$K*N2A@BaxZw^pMAEWYuRI2cZ;qR_WomGXA+_#%)0Z;Wq?t*cLBM8N5l>PQa($6{|{O0&}Y*&#EjY$C+g_A z+wbP`kKyfhbN2&*i(NNSL_im0=MR)U&k`SytPb8PGwEnyznkdEw59NtH3vgGJ(Dem z@jCQ38;S|@!Nu>>3c9uL^nYMxoozN7%5ReDV!BC)l7<_(p7;9#eRKX~qc@`wt_M35 z)mZc6O*77ua4q}_X4=O*l2ML6k}O=T2znXZbk@y7A-Q{~jsYGTh_3in@|K~AsMh<7 zkt8Zf9=r{{cNS&YlG%YyTkGL@{Wborqbu~hnCpAdT-;oO?ZG97dpMHQE=(t5UBX|` zc-Ut#*n%9ldYBy!8Q$ZeU9ogVC~w@MM7;_ZhrkGauxo~B94eQ&%$fBcLm@{4kF65poL!=h zHie6H2YF!xfYJaWeih364h#SWIMLuYg7F;00{#YK3O;2kQ!-guVZgWmF^wM^%uru~ z$txV(CjhA0(Ti5AA6l;`a(E9{erU4w*w3t5rK#hA3ZNYGcSiUGv~6uK@gi#F7VfzmUVAie%#h-Uj}-ptea#XuBG(?dmXg`7 z@42|ki-V~PMsSVx-_WqPv|l{Q8F#a8Jwe9-F?#bEb&4!`ZQ_IT4A$9S5EVHkv1~fjBXn{fQswMMSAN^D2e>^>g&@JB>H|WUVK|87L%D_ZGIc*Q_FxIXFeSG1sr-!V(32-t;r3 zz+ePU+3fZSU`pm%oZ9Owz5A4gO6kVGJ61-WmzDFJDap2sf3GM8RSyi6^&96{O*V~K zq8(0O5opJ75nE9FM|#^Z;Csk`;oe`XR*mPeiD&oSC84 zWj&lKCQ@CxV3)|1t&x>3SCQ3X1Mch6l3jUb<|XsJUFB^T(2Nc_^(uv;WrbD%@RFlJ zmkDX)ZbGC9xk-hq-=Ts2%Ri!uboVg%$?}#=)(5=J;I9zXCv?C#QlBrcC*^>wuA|X0 zIsodhKefd0*m*6%{$YG?O~q|k2o(r`gu=yYJ$%dxV z0e)YNz>_A-&i7hH_=R_9uWd1-+Ui9m=TBJNf^o1OzzE&xfe|U7qCEgydL)zOz-U;J z&-G=hXR7N^3%flOcgV~OWTu;lxB)Lfp=qTfRUL;|92c{7O zeJ7RS*uzGWB!kmqt9~&@`DQCIcNgSotr4G5^R<7c+CN9Nav|@j1ct`TMO(GEfkQoP z0MR>_OBo#M&o{vkSl}}08bNS&d=QBhvujKpf94JUNHL((8VbIvL6f7H4d^5*s(B?N zyAfeJ&3m5FNVi<9D#Ox5Pn<~7&mqr_+Rk|dX|#cPAYH|iqtu1jq(an6XAG9|r|&ZG zXaWtsRqI|`PqkUB(^pY4+=hB*ki}wY}-L?%U0=6yL^`?Wk?6L&;jMzpq;0w~FZ=S40gV zjoi1o3Wd{;k8_TJ>txb{caru}4niAh#~oGruUk z18WG46q=g^N)_h?g?~3Bv z4|$}4$WGfd^Cwu+uB=ZG^UVV8#@Yc|d>_M&v-51@9Z^OR6P-%~4%W>cA z-oWNB)I4?Htg1s1MU_t#@T;e7`a>ie+NdF5F%~Q%rv$y;WaJcE{sK7hYmrtknB{zhJFacrey7aGWPEzTqpJSe z>t~M4DcNaDvuqwLB_xF3pOZsUS<1b+FfTYXJs5pyNYUJcJz=Gz%L{V~*gbI&KhWU) z+czQplrZQ1rm}T2 zugaN)>SUAKgTXViilxOLk6-Wbyv;Q0A1>lJW@>Rr7%K=*;E6Z%GAa>kIv!wnI=Iba zmUb$s=+r>(0AmvKDdD+c5@Lx)40z3KU=p4i`}BkcC<(JXBuD#JeSOK=6NHLu-djms zic(p7IIPfEKo{xNP&6#3WhTP+QH&|I3&(KE$yDKf`wiIE^@NjYS#`HXk*aqK{mppk zYEUkoZ1V2&kJlg#+lr-H6dJjY^Ws)teQv7Gt@B16^pCm^J%eY-7QMXz+3e-H4UHsn zB8>F=FwjT{ZlSXt8jJpvHFT@8XF91hm3sAJLFrZIR$5lSgqlSz*LgohEfcHC# zJ>h%vJf?EvF9m^&yS$YjV63V>K$)wZd^cm5PszdQP1wCgipbG}? zkN~^J2t|dA%5+}Uxc7B+uyiz_z;WoX7k<>K>EZ1>DlPdys>^1sB|i}Yt3~keGk6jF&k|2}37mlQn)?M0T-`Jbf?qfa-kW!n@;Bo9FFm|hu(nH1fQkQUk4|DDbyL2dyl;q`{-(Ium3qXp_|?x zDYVwwptrO#-d VmwKMN3A}U$l9y4I&XY9q`!5KAZJhuB literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index b320b5502f..821585ae21 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem'; Project settings can have project specific values. Each new project is using studio values defined in **default** project but these values can be modified or overridden per project. :::warning Default studio values -Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orage colour). Any changes in default project may affect all existing projects. +Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orange colour). Any changes in default project may affect all existing projects. ::: ## Color Management (ImageIO) @@ -39,14 +39,14 @@ Procedure of resolving path (from above example) will look first into path 1st a ### Using File rules File rules are inspired by [OCIO v2 configuration]((https://opencolorio.readthedocs.io/en/latest/guides/authoring/rules.html)). Each rule has a unique name which can be overridden by host-specific _File rules_ (example: `project_settings/nuke/imageio/file_rules/rules`). -The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](https://regexr.com/)). Matching rules procedure's intention is to be used during publishing or loading of representation. Since the publishing procedure is run before integrator formate publish template path, make sure the pattern is working or any work render path. +The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](https://regexr.com/)). Matching rules procedure's intention is to be used during publishing or loading of representation. Since the publishing procedure is run before integrator format publish template path, make sure the pattern is working or any work render path. :::warning Colorspace name input The **colorspace name** value is a raw string input and no validation is run after saving project settings. We recommend to open the specified `config.ocio` file and copy pasting the exact colorspace names. ::: ### Extract OIIO Transcode -OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertable to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file. +OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertible to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file. `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. @@ -82,8 +82,8 @@ All context filters are lists which may contain strings or Regular expressions ( - **`tasks`** - Currently processed task. `["modeling", "animation"]` :::important Filtering -Filters are optional. In case when multiple profiles match current context, profile with higher number of matched filters has higher priority that profile without filters. -(Eg. order of when filter is added doesn't matter, only the precision of matching does.) +Filters are optional. In case when multiple profiles match current context, profile with higher number of matched filters has higher priority than profile without filters. +(The order the profiles in settings doesn't matter, only the precision of matching does.) ::: ## Publish plugins @@ -94,7 +94,7 @@ Publish plugins used across all integrations. ### Extract Review Plugin responsible for automatic FFmpeg conversion to variety of formats. -Extract review is using [profile filtering](#profile-filters) to be able render different outputs for different situations. +Extract review uses [profile filtering](#profile-filters) to render different outputs for different situations. Applicable context filters: **`hosts`** - Host from which publishing was triggered. `["maya", "nuke"]` @@ -104,7 +104,7 @@ Applicable context filters: **Output Definitions** -Profile may generate multiple outputs from a single input. Each output must define unique name and output extension (use the extension without a dot e.g. **mp4**). All other settings of output definition are optional. +A profile may generate multiple outputs from a single input. Each output must define unique name and output extension (use the extension without a dot e.g. **mp4**). All other settings of output definition are optional. ![global_extract_review_output_defs](assets/global_extract_review_output_defs.png) - **`Tags`** @@ -118,7 +118,7 @@ Profile may generate multiple outputs from a single input. Each output must defi - **Output arguments** other FFmpeg output arguments like codec definition. - **`Output width`** and **`Output height`** - - it is possible to rescale output to specified resolution and keep aspect ratio. + - It is possible to rescale output to specified resolution and keep aspect ratio. - If value is set to 0, source resolution will be used. - **`Overscan crop`** @@ -230,10 +230,10 @@ Applicable context filters: ## Tools Settings for OpenPype tools. -## Creator +### Creator Settings related to [Creator tool](artist_tools_creator). -### Subset name profiles +#### Subset name profiles ![global_tools_creator_subset_template](assets/global_tools_creator_subset_template.png) Subset name helps to identify published content. More specific name helps with organization and avoid mixing of published content. Subset name is defined using one of templates defined in **Subset name profiles settings**. The template is filled with context information at the time of creation. @@ -263,10 +263,31 @@ Template may look like `"{family}{Task}{Variant}"`. Some creators may have other keys as their context may require more information or more specific values. Make sure you've read documentation of host you're using. -## Workfiles +### Publish + +#### Custom Staging Directory Profiles +With this feature, users can specify a custom data folder path based on presets, which can be used during the creation and publishing stages. + +![global_tools_custom_staging_dir](assets/global_tools_custom_staging_dir.png) + +Staging directories are used as a destination for intermediate files (as renders) before they are renamed and copied to proper location during the integration phase. They could be created completely dynamically in the temp folder or for some DCCs in the `work` area. +Example could be Nuke where artist might want to temporarily render pictures into `work` area to check them before they get published with the choice of "Use existing frames" on the write node. + +One of the key advantages of this feature is that it allows users to choose the folder for writing such intermediate files to take advantage of faster storage for rendering, which can help improve workflow efficiency. Additionally, this feature allows users to keep their intermediate extracted data persistent, and use their own infrastructure for regular cleaning. + +In some cases, these DCCs (Nuke, Houdini, Maya) automatically add a rendering path during the creation stage, which is then used in publishing. Creators and extractors of such DCCs need to use these profiles to fill paths in DCC's nodes to use this functionality. + +The custom staging folder uses a path template configured in `project_anatomy/templates/others` with `transient` being a default example path that could be used. The template requires a 'folder' key for it to be usable as custom staging folder. + +##### Known issues +- Any DCC that uses prefilled paths and store them inside of workfile nodes needs to implement resolving these paths with a configured profiles. +- If studio uses Site Sync remote artists need to have access to configured custom staging folder! +- Each node on the rendering farm must have access to configured custom staging folder! + +### Workfiles All settings related to Workfile tool. -### Open last workfile at launch +#### Open last workfile at launch This feature allows you to define a rule for each task/host or toggle the feature globally to all tasks as they are visible in the picture. ![global_tools_workfile_open_last_version](assets/global_tools_workfile_open_last_version.png) diff --git a/website/docs/project_settings/settings_project_nuke.md b/website/docs/project_settings/settings_project_nuke.md index b3ee5f77a6..c9c3d12df9 100644 --- a/website/docs/project_settings/settings_project_nuke.md +++ b/website/docs/project_settings/settings_project_nuke.md @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem'; Project settings can have project specific values. Each new project is using studio values defined in **default** project but these values can be modified or overridden per project. :::warning Default studio values -Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orage colour). Any changes in default project may affect all existing projects. +Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orange colour). Any changes in default project may affect all existing projects. ::: ## Workfile Builder diff --git a/website/docs/project_settings/settings_project_standalone.md b/website/docs/project_settings/settings_project_standalone.md index 778aba2942..1383bd488e 100644 --- a/website/docs/project_settings/settings_project_standalone.md +++ b/website/docs/project_settings/settings_project_standalone.md @@ -10,7 +10,7 @@ import TabItem from '@theme/TabItem'; Project settings can have project specific values. Each new project is using studio values defined in **default** project but these values can be modified or overridden per project. :::warning Default studio values -Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orage colour). Any changes in default project may affect all existing projects. +Projects always use default project values unless they have [project override](../admin_settings#project-overrides) (orange colour). Any changes in default project may affect all existing projects. ::: ## Creator Plugins diff --git a/website/docs/pype2/admin_ftrack.md b/website/docs/pype2/admin_ftrack.md index a81147bece..4ebe1a7add 100644 --- a/website/docs/pype2/admin_ftrack.md +++ b/website/docs/pype2/admin_ftrack.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Ftrack is currently the main project management option for Pype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). +Ftrack is currently the main project management option for Pype. This documentation assumes that you are familiar with Ftrack and its basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). ## Prepare Ftrack for Pype diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index fcdc09439b..44c2a28dec 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -36,7 +36,7 @@ All context filters are lists which may contain strings or Regular expressions ( - **families** - Main family of processed instance. `["plate", "model"]` :::important Filtering -Filters are optional and may not be set. In case when multiple profiles match current context, profile with filters has higher priority that profile without filters. +Filters are optional and may not be set. In case when multiple profiles match current context, profile with filters has higher priority than profile without filters. ::: #### Profile outputs @@ -293,6 +293,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"** - It is allowed to use [Anatomy templates](admin_config#anatomy) themselves in burnins if they can be filled with available data. - Additional keys in burnins: + | Burnin key | Description | | --- | --- | | frame_start | First frame number. | @@ -303,6 +304,7 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"** | resolution_height | Resolution height. | | fps | Fps of an output. | | timecode | Timecode by frame start and fps. | + | focalLength | **Only available in Maya**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | :::warning `timecode` is specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`) diff --git a/website/docs/system_introduction.md b/website/docs/system_introduction.md index 05627b5359..d64b023704 100644 --- a/website/docs/system_introduction.md +++ b/website/docs/system_introduction.md @@ -15,9 +15,9 @@ various usage scenarios. ## Studio Preparation -You can find detailed breakdown of technical requirements [here](dev_requirements), but in general OpenPype should be able +You can find a detailed breakdown of technical requirements [here](dev_requirements), but in general OpenPype should be able to operate in most studios fairly quickly. The main obstacles are usually related to workflows and habits, that -might not be fully compatible with what OpenPype is expecting or enforcing. It is recommended to go through artists [key concepts](artist_concepts) to get idea about basics. +might not be fully compatible with what OpenPype is expecting or enforcing. It is recommended to go through artists [key concepts](artist_concepts) to get comfortable with the basics. Keep in mind that if you run into any workflows that are not supported, it's usually just because we haven't hit that particular case and it can most likely be added upon request. diff --git a/website/yarn.lock b/website/yarn.lock index 559c58f931..2edf57abf4 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -3,56 +3,56 @@ "@algolia/autocomplete-core@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37" - integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A== + "integrity" "sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A==" + "resolved" "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz" + "version" "1.5.2" dependencies: "@algolia/autocomplete-shared" "1.5.2" "@algolia/autocomplete-preset-algolia@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca" - integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw== + "integrity" "sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw==" + "resolved" "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz" + "version" "1.5.2" dependencies: "@algolia/autocomplete-shared" "1.5.2" "@algolia/autocomplete-shared@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79" - integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug== + "integrity" "sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug==" + "resolved" "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz" + "version" "1.5.2" "@algolia/cache-browser-local-storage@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.12.1.tgz#23f4f219963b96918d0524acd09d4d646541d888" - integrity sha512-ERFFOnC9740xAkuO0iZTQqm2AzU7Dpz/s+g7o48GlZgx5p9GgNcsuK5eS0GoW/tAK+fnKlizCtlFHNuIWuvfsg== + "integrity" "sha512-ERFFOnC9740xAkuO0iZTQqm2AzU7Dpz/s+g7o48GlZgx5p9GgNcsuK5eS0GoW/tAK+fnKlizCtlFHNuIWuvfsg==" + "resolved" "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/cache-common" "4.12.1" "@algolia/cache-common@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.12.1.tgz#d3f1676ca9c404adce0f78d68f6381bedb44cd9c" - integrity sha512-UugTER3V40jT+e19Dmph5PKMeliYKxycNPwrPNADin0RcWNfT2QksK9Ff2N2W7UKraqMOzoeDb4LAJtxcK1a8Q== + "integrity" "sha512-UugTER3V40jT+e19Dmph5PKMeliYKxycNPwrPNADin0RcWNfT2QksK9Ff2N2W7UKraqMOzoeDb4LAJtxcK1a8Q==" + "resolved" "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.12.1.tgz" + "version" "4.12.1" "@algolia/cache-in-memory@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.12.1.tgz#0ef6aac2f8feab5b46fc130beb682bbd21b55244" - integrity sha512-U6iaunaxK1lHsAf02UWF58foKFEcrVLsHwN56UkCtwn32nlP9rz52WOcHsgk6TJrL8NDcO5swMjtOQ5XHESFLw== + "integrity" "sha512-U6iaunaxK1lHsAf02UWF58foKFEcrVLsHwN56UkCtwn32nlP9rz52WOcHsgk6TJrL8NDcO5swMjtOQ5XHESFLw==" + "resolved" "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/cache-common" "4.12.1" "@algolia/client-account@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.12.1.tgz#e838c9283db2fab32a425dd13c77da321d48fd8b" - integrity sha512-jGo4ConJNoMdTCR2zouO0jO/JcJmzOK6crFxMMLvdnB1JhmMbuIKluOTJVlBWeivnmcsqb7r0v7qTCPW5PAyxQ== + "integrity" "sha512-jGo4ConJNoMdTCR2zouO0jO/JcJmzOK6crFxMMLvdnB1JhmMbuIKluOTJVlBWeivnmcsqb7r0v7qTCPW5PAyxQ==" + "resolved" "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/client-common" "4.12.1" "@algolia/client-search" "4.12.1" "@algolia/transporter" "4.12.1" "@algolia/client-analytics@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.12.1.tgz#2976d658655a1590cf84cfb596aa75a204f6dec4" - integrity sha512-h1It7KXzIthlhuhfBk7LteYq72tym9maQDUsyRW0Gft8b6ZQahnRak9gcCvKwhcJ1vJoP7T7JrNYGiYSicTD9g== + "integrity" "sha512-h1It7KXzIthlhuhfBk7LteYq72tym9maQDUsyRW0Gft8b6ZQahnRak9gcCvKwhcJ1vJoP7T7JrNYGiYSicTD9g==" + "resolved" "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/client-common" "4.12.1" "@algolia/client-search" "4.12.1" @@ -60,99 +60,121 @@ "@algolia/transporter" "4.12.1" "@algolia/client-common@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.12.1.tgz#104ccefe96bda3ff926bc70c31ff6d17c41b6107" - integrity sha512-obnJ8eSbv+h94Grk83DTGQ3bqhViSWureV6oK1s21/KMGWbb3DkduHm+lcwFrMFkjSUSzosLBHV9EQUIBvueTw== + "integrity" "sha512-obnJ8eSbv+h94Grk83DTGQ3bqhViSWureV6oK1s21/KMGWbb3DkduHm+lcwFrMFkjSUSzosLBHV9EQUIBvueTw==" + "resolved" "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/requester-common" "4.12.1" "@algolia/transporter" "4.12.1" "@algolia/client-personalization@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.12.1.tgz#f63d1890f95de850e1c8e41c1d57adda521d9e7f" - integrity sha512-sMSnjjPjRgByGHYygV+5L/E8a6RgU7l2GbpJukSzJ9GRY37tHmBHuvahv8JjdCGJ2p7QDYLnQy5bN5Z02qjc7Q== + "integrity" "sha512-sMSnjjPjRgByGHYygV+5L/E8a6RgU7l2GbpJukSzJ9GRY37tHmBHuvahv8JjdCGJ2p7QDYLnQy5bN5Z02qjc7Q==" + "resolved" "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/client-common" "4.12.1" "@algolia/requester-common" "4.12.1" "@algolia/transporter" "4.12.1" -"@algolia/client-search@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.12.1.tgz#fcd7a974be5d39d5c336d7f2e89577ffa66aefdd" - integrity sha512-MwwKKprfY6X2nJ5Ki/ccXM2GDEePvVjZnnoOB2io3dLKW4fTqeSRlC5DRXeFD7UM0vOPPHr4ItV2aj19APKNVQ== +"@algolia/client-search@^4.9.1", "@algolia/client-search@4.12.1": + "integrity" "sha512-MwwKKprfY6X2nJ5Ki/ccXM2GDEePvVjZnnoOB2io3dLKW4fTqeSRlC5DRXeFD7UM0vOPPHr4ItV2aj19APKNVQ==" + "resolved" "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/client-common" "4.12.1" "@algolia/requester-common" "4.12.1" "@algolia/transporter" "4.12.1" "@algolia/events@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" - integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== + "integrity" "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + "resolved" "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz" + "version" "4.0.1" "@algolia/logger-common@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.12.1.tgz#d6501b4d9d242956257ba8e10f6b4bbf6863baa4" - integrity sha512-fCgrzlXGATNqdFTxwx0GsyPXK+Uqrx1SZ3iuY2VGPPqdt1a20clAG2n2OcLHJpvaa6vMFPlJyWvbqAgzxdxBlQ== + "integrity" "sha512-fCgrzlXGATNqdFTxwx0GsyPXK+Uqrx1SZ3iuY2VGPPqdt1a20clAG2n2OcLHJpvaa6vMFPlJyWvbqAgzxdxBlQ==" + "resolved" "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.12.1.tgz" + "version" "4.12.1" "@algolia/logger-console@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.12.1.tgz#841edd39dd5c5530a69fc66084bfee3254dd0807" - integrity sha512-0owaEnq/davngQMYqxLA4KrhWHiXujQ1CU3FFnyUcMyBR7rGHI48zSOUpqnsAXrMBdSH6rH5BDkSUUFwsh8RkQ== + "integrity" "sha512-0owaEnq/davngQMYqxLA4KrhWHiXujQ1CU3FFnyUcMyBR7rGHI48zSOUpqnsAXrMBdSH6rH5BDkSUUFwsh8RkQ==" + "resolved" "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/logger-common" "4.12.1" "@algolia/requester-browser-xhr@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.12.1.tgz#2d0c18ee188d7cae0e4a930e5e89989e3c4a816b" - integrity sha512-OaMxDyG0TZG0oqz1lQh9e3woantAG1bLnuwq3fmypsrQxra4IQZiyn1x+kEb69D2TcXApI5gOgrD4oWhtEVMtw== + "integrity" "sha512-OaMxDyG0TZG0oqz1lQh9e3woantAG1bLnuwq3fmypsrQxra4IQZiyn1x+kEb69D2TcXApI5gOgrD4oWhtEVMtw==" + "resolved" "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/requester-common" "4.12.1" "@algolia/requester-common@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.12.1.tgz#95bb6539da7199da3e205341cea8f27267f7af29" - integrity sha512-XWIrWQNJ1vIrSuL/bUk3ZwNMNxl+aWz6dNboRW6+lGTcMIwc3NBFE90ogbZKhNrFRff8zI4qCF15tjW+Fyhpow== + "integrity" "sha512-XWIrWQNJ1vIrSuL/bUk3ZwNMNxl+aWz6dNboRW6+lGTcMIwc3NBFE90ogbZKhNrFRff8zI4qCF15tjW+Fyhpow==" + "resolved" "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.12.1.tgz" + "version" "4.12.1" "@algolia/requester-node-http@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.12.1.tgz#c9df97ff1daa7e58c5c2b1f28cf7163005edccb0" - integrity sha512-awBtwaD+s0hxkA1aehYn8F0t9wqGoBVWgY4JPHBmp1ChO3pK7RKnnvnv7QQa9vTlllX29oPt/BBVgMo1Z3n1Qg== + "integrity" "sha512-awBtwaD+s0hxkA1aehYn8F0t9wqGoBVWgY4JPHBmp1ChO3pK7RKnnvnv7QQa9vTlllX29oPt/BBVgMo1Z3n1Qg==" + "resolved" "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/requester-common" "4.12.1" "@algolia/transporter@4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.12.1.tgz#61b9829916c474f42e2d4a6eada0d6c138379945" - integrity sha512-BGeNgdEHc6dXIk2g8kdlOoQ6fQ6OIaKQcplEj7HPoi+XZUeAvRi3Pff3QWd7YmybWkjzd9AnTzieTASDWhL+sQ== + "integrity" "sha512-BGeNgdEHc6dXIk2g8kdlOoQ6fQ6OIaKQcplEj7HPoi+XZUeAvRi3Pff3QWd7YmybWkjzd9AnTzieTASDWhL+sQ==" + "resolved" "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/cache-common" "4.12.1" "@algolia/logger-common" "4.12.1" "@algolia/requester-common" "4.12.1" -"@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== +"@ampproject/remapping@^2.2.0": + "integrity" "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==" + "resolved" "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + "version" "2.2.0" dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": + "integrity" "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==" + "resolved" "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + "version" "7.18.6" dependencies: - "@babel/highlight" "^7.16.7" + "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.4", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.0.tgz#86850b8597ea6962089770952075dcaabb8dba34" - integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.20.5": + "integrity" "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==" + "resolved" "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz" + "version" "7.21.0" -"@babel/core@7.12.9": - version "7.12.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" - integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.15.5", "@babel/core@^7.16.0", "@babel/core@^7.4.0-0": + "integrity" "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==" + "resolved" "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz" + "version" "7.21.3" + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.2" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.3" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.3" + "@babel/types" "^7.21.3" + "convert-source-map" "^1.7.0" + "debug" "^4.1.0" + "gensync" "^1.0.0-beta.2" + "json5" "^2.2.2" + "semver" "^6.3.0" + +"@babel/core@^7.11.6", "@babel/core@7.12.9": + "integrity" "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==" + "resolved" "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz" + "version" "7.12.9" dependencies: "@babel/code-frame" "^7.10.4" "@babel/generator" "^7.12.5" @@ -162,74 +184,55 @@ "@babel/template" "^7.12.7" "@babel/traverse" "^7.12.9" "@babel/types" "^7.12.7" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" + "convert-source-map" "^1.7.0" + "debug" "^4.1.0" + "gensync" "^1.0.0-beta.1" + "json5" "^2.1.2" + "lodash" "^4.17.19" + "resolve" "^1.3.2" + "semver" "^5.4.1" + "source-map" "^0.5.0" -"@babel/core@^7.15.5", "@babel/core@^7.16.0": - version "7.17.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.5.tgz#6cd2e836058c28f06a4ca8ee7ed955bbf37c8225" - integrity sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA== +"@babel/generator@^7.12.5", "@babel/generator@^7.16.0", "@babel/generator@^7.21.3": + "integrity" "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==" + "resolved" "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz" + "version" "7.21.3" dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.3" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helpers" "^7.17.2" - "@babel/parser" "^7.17.3" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - -"@babel/generator@^7.12.5", "@babel/generator@^7.16.0", "@babel/generator@^7.17.3": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.3.tgz#a2c30b0c4f89858cb87050c3ffdfd36bdf443200" - integrity sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg== - dependencies: - "@babel/types" "^7.17.0" - jsesc "^2.5.1" - source-map "^0.5.0" + "@babel/types" "^7.21.3" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + "jsesc" "^2.5.1" "@babel/helper-annotate-as-pure@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" - integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== + "integrity" "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==" + "resolved" "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/types" "^7.16.7" "@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" - integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== + "integrity" "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==" + "resolved" "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-explode-assignable-expression" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" - integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.20.7": + "integrity" "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz" + "version" "7.20.7" dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" - semver "^6.3.0" + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + "browserslist" "^4.21.3" + "lru-cache" "^5.1.1" + "semver" "^6.3.0" "@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7": - version "7.17.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz#9699f14a88833a7e055ce57dcd3ffdcd25186b21" - integrity sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ== + "integrity" "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz" + "version" "7.17.1" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" @@ -240,122 +243,112 @@ "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-create-regexp-features-plugin@^7.16.7": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" - integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== + "integrity" "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==" + "resolved" "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz" + "version" "7.17.0" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" - regexpu-core "^5.0.1" + "regexpu-core" "^5.0.1" "@babel/helper-define-polyfill-provider@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" - integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== + "integrity" "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==" + "resolved" "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz" + "version" "0.3.1" dependencies: "@babel/helper-compilation-targets" "^7.13.0" "@babel/helper-module-imports" "^7.12.13" "@babel/helper-plugin-utils" "^7.13.0" "@babel/traverse" "^7.13.0" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" + "debug" "^4.1.1" + "lodash.debounce" "^4.0.8" + "resolve" "^1.14.2" + "semver" "^6.1.2" -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" - integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== - dependencies: - "@babel/types" "^7.16.7" +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.9": + "integrity" "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + "resolved" "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + "version" "7.18.9" "@babel/helper-explode-assignable-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" - integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== + "integrity" "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" - integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== +"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.21.0": + "integrity" "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==" + "resolved" "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz" + "version" "7.21.0" dependencies: - "@babel/helper-get-function-arity" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" -"@babel/helper-get-function-arity@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" - integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== +"@babel/helper-hoist-variables@^7.16.7", "@babel/helper-hoist-variables@^7.18.6": + "integrity" "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==" + "resolved" "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + "version" "7.18.6" dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" - integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== - dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" "@babel/helper-member-expression-to-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz#42b9ca4b2b200123c3b7e726b0ae5153924905b0" - integrity sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q== + "integrity" "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==" + "resolved" "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" - integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6": + "integrity" "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==" + "resolved" "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + "version" "7.18.6" dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" - integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.21.2": + "integrity" "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz" + "version" "7.21.2" dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" "@babel/helper-optimise-call-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" - integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== + "integrity" "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==" + "resolved" "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/types" "^7.16.7" -"@babel/helper-plugin-utils@7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" - integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + "integrity" "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "resolved" "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz" + "version" "7.16.7" + +"@babel/helper-plugin-utils@7.10.4": + "integrity" "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + "resolved" "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz" + "version" "7.10.4" "@babel/helper-remap-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" - integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== + "integrity" "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==" + "resolved" "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-wrap-function" "^7.16.8" "@babel/types" "^7.16.8" "@babel/helper-replace-supers@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" - integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== + "integrity" "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==" + "resolved" "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-member-expression-to-functions" "^7.16.7" @@ -363,173 +356,169 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-simple-access@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" - integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== +"@babel/helper-simple-access@^7.16.7", "@babel/helper-simple-access@^7.20.2": + "integrity" "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==" + "resolved" "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz" + "version" "7.20.2" dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" - integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== + "integrity" "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==" + "resolved" "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz" + "version" "7.16.0" dependencies: "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" - integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== +"@babel/helper-split-export-declaration@^7.16.7", "@babel/helper-split-export-declaration@^7.18.6": + "integrity" "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==" + "resolved" "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + "version" "7.18.6" dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.18.6" -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-string-parser@^7.19.4": + "integrity" "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + "resolved" "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" + "version" "7.19.4" -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" - integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== +"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + "integrity" "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" + "version" "7.19.1" + +"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.18.6": + "integrity" "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==" + "resolved" "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz" + "version" "7.21.0" "@babel/helper-wrap-function@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" - integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== + "integrity" "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==" + "resolved" "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-function-name" "^7.16.7" "@babel/template" "^7.16.7" "@babel/traverse" "^7.16.8" "@babel/types" "^7.16.8" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.17.2": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417" - integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.21.0": + "integrity" "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==" + "resolved" "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz" + "version" "7.21.0" dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.0" - "@babel/types" "^7.17.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" -"@babel/highlight@^7.16.7": - version "7.16.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" - integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== +"@babel/highlight@^7.18.6": + "integrity" "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==" + "resolved" "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + "version" "7.18.6" dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" + "@babel/helper-validator-identifier" "^7.18.6" + "chalk" "^2.0.0" + "js-tokens" "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.16.4", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0" - integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA== +"@babel/parser@^7.12.7", "@babel/parser@^7.16.4", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3": + "integrity" "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==" + "resolved" "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz" + "version" "7.21.3" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" - integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== + "integrity" "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" - integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== + "integrity" "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-proposal-optional-chaining" "^7.16.7" "@babel/plugin-proposal-async-generator-functions@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" - integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== + "integrity" "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-proposal-class-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" - integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== + "integrity" "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-create-class-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-proposal-class-static-block@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz#712357570b612106ef5426d13dc433ce0f200c2a" - integrity sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw== + "integrity" "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-create-class-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-proposal-dynamic-import@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" - integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== + "integrity" "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-proposal-export-namespace-from@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" - integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== + "integrity" "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-proposal-json-strings@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" - integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== + "integrity" "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-proposal-logical-assignment-operators@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" - integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== + "integrity" "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" - integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== + "integrity" "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-numeric-separator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" - integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== + "integrity" "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread@^7.16.7": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" - integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== + "integrity" "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz" + "version" "7.17.3" dependencies: "@babel/compat-data" "^7.17.0" "@babel/helper-compilation-targets" "^7.16.7" @@ -537,35 +526,44 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.16.7" +"@babel/plugin-proposal-object-rest-spread@7.12.1": + "integrity" "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz" + "version" "7.12.1" + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-proposal-optional-catch-binding@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" - integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== + "integrity" "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" - integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== + "integrity" "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-proposal-private-methods@^7.16.11": - version "7.16.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" - integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== + "integrity" "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz" + "version" "7.16.11" dependencies: "@babel/helper-create-class-features-plugin" "^7.16.10" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-proposal-private-property-in-object@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" - integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== + "integrity" "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-create-class-features-plugin" "^7.16.7" @@ -573,166 +571,166 @@ "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" - integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== + "integrity" "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + "integrity" "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + "version" "7.8.4" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + "integrity" "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + "version" "7.12.13" dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + "integrity" "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + "version" "7.14.5" dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + "integrity" "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + "integrity" "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + "integrity" "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" - integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== + "integrity" "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-syntax-jsx@7.12.1": + "integrity" "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz" + "version" "7.12.1" + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + "integrity" "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + "version" "7.10.4" dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + "integrity" "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + "integrity" "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + "version" "7.10.4" dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3", "@babel/plugin-syntax-object-rest-spread@7.8.3": + "integrity" "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + "integrity" "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + "integrity" "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + "version" "7.8.3" dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + "integrity" "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + "version" "7.14.5" dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + "integrity" "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + "version" "7.14.5" dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" - integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== + "integrity" "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==" + "resolved" "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-arrow-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" - integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== + "integrity" "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" - integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== + "integrity" "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-module-imports" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-transform-block-scoped-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" - integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== + "integrity" "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-block-scoping@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" - integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== + "integrity" "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-classes@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" - integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== + "integrity" "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" @@ -741,174 +739,174 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - globals "^11.1.0" + "globals" "^11.1.0" "@babel/plugin-transform-computed-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" - integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== + "integrity" "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-destructuring@^7.16.7": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz#c445f75819641788a27a0a3a759d9df911df6abc" - integrity sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg== + "integrity" "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz" + "version" "7.17.3" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" - integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== + "integrity" "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-duplicate-keys@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" - integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== + "integrity" "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-exponentiation-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" - integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== + "integrity" "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-for-of@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" - integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== + "integrity" "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" - integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== + "integrity" "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-compilation-targets" "^7.16.7" "@babel/helper-function-name" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" - integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== + "integrity" "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-member-expression-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" - integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== + "integrity" "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-modules-amd@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" - integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== + "integrity" "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-module-transforms" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" + "babel-plugin-dynamic-import-node" "^2.3.3" "@babel/plugin-transform-modules-commonjs@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz#cdee19aae887b16b9d331009aa9a219af7c86afe" - integrity sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA== + "integrity" "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-module-transforms" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-simple-access" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" + "babel-plugin-dynamic-import-node" "^2.3.3" "@babel/plugin-transform-modules-systemjs@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz#887cefaef88e684d29558c2b13ee0563e287c2d7" - integrity sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw== + "integrity" "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-module-transforms" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" + "babel-plugin-dynamic-import-node" "^2.3.3" "@babel/plugin-transform-modules-umd@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" - integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== + "integrity" "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-module-transforms" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" - integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== + "integrity" "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/plugin-transform-new-target@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" - integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== + "integrity" "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-object-super@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" - integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== + "integrity" "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" "@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" - integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== + "integrity" "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-property-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" - integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== + "integrity" "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-constant-elements@^7.14.5": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.7.tgz#19e9e4c2df2f6c3e6b3aea11778297d81db8df62" - integrity sha512-lF+cfsyTgwWkcw715J88JhMYJ5GpysYNLhLP1PkvkhTRN7B3e74R/1KsDxFxhRpSn0UUD3IWM4GvdBR2PEbbQQ== + "integrity" "sha512-lF+cfsyTgwWkcw715J88JhMYJ5GpysYNLhLP1PkvkhTRN7B3e74R/1KsDxFxhRpSn0UUD3IWM4GvdBR2PEbbQQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-display-name@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" - integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== + "integrity" "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-jsx-development@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" - integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== + "integrity" "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/plugin-transform-react-jsx" "^7.16.7" "@babel/plugin-transform-react-jsx@^7.16.7": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" - integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== + "integrity" "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz" + "version" "7.17.3" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" @@ -917,103 +915,103 @@ "@babel/types" "^7.17.0" "@babel/plugin-transform-react-pure-annotations@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" - integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== + "integrity" "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-regenerator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" - integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== + "integrity" "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz" + "version" "7.16.7" dependencies: - regenerator-transform "^0.14.2" + "regenerator-transform" "^0.14.2" "@babel/plugin-transform-reserved-words@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" - integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== + "integrity" "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-runtime@^7.16.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" - integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== + "integrity" "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz" + "version" "7.17.0" dependencies: "@babel/helper-module-imports" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" - babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.5.0" - babel-plugin-polyfill-regenerator "^0.3.0" - semver "^6.3.0" + "babel-plugin-polyfill-corejs2" "^0.3.0" + "babel-plugin-polyfill-corejs3" "^0.5.0" + "babel-plugin-polyfill-regenerator" "^0.3.0" + "semver" "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" - integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== + "integrity" "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-spread@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" - integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== + "integrity" "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-transform-sticky-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" - integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + "integrity" "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-template-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" - integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + "integrity" "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-typeof-symbol@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" - integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + "integrity" "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-typescript@^7.16.7": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" - integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + "integrity" "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz" + "version" "7.16.8" dependencies: "@babel/helper-create-class-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-typescript" "^7.16.7" "@babel/plugin-transform-unicode-escapes@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" - integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + "integrity" "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-unicode-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" - integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + "integrity" "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==" + "resolved" "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" "@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.4": - version "7.16.11" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" - integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + "integrity" "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==" + "resolved" "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz" + "version" "7.16.11" dependencies: "@babel/compat-data" "^7.16.8" "@babel/helper-compilation-targets" "^7.16.7" @@ -1084,27 +1082,27 @@ "@babel/plugin-transform-unicode-regex" "^7.16.7" "@babel/preset-modules" "^0.1.5" "@babel/types" "^7.16.8" - babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.5.0" - babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.20.2" - semver "^6.3.0" + "babel-plugin-polyfill-corejs2" "^0.3.0" + "babel-plugin-polyfill-corejs3" "^0.5.0" + "babel-plugin-polyfill-regenerator" "^0.3.0" + "core-js-compat" "^3.20.2" + "semver" "^6.3.0" "@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + "integrity" "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==" + "resolved" "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + "version" "0.1.5" dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" "@babel/plugin-transform-dotall-regex" "^7.4.4" "@babel/types" "^7.4.4" - esutils "^2.0.2" + "esutils" "^2.0.2" "@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" - integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + "integrity" "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==" + "resolved" "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-validator-option" "^7.16.7" @@ -1114,81 +1112,82 @@ "@babel/plugin-transform-react-pure-annotations" "^7.16.7" "@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" - integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + "integrity" "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==" + "resolved" "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz" + "version" "7.16.7" dependencies: "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-validator-option" "^7.16.7" "@babel/plugin-transform-typescript" "^7.16.7" "@babel/runtime-corejs3@^7.16.3": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz#fdca2cd05fba63388babe85d349b6801b008fd13" - integrity sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg== + "integrity" "sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg==" + "resolved" "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz" + "version" "7.17.2" dependencies: - core-js-pure "^3.20.2" - regenerator-runtime "^0.13.4" + "core-js-pure" "^3.20.2" + "regenerator-runtime" "^0.13.4" "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4": - version "7.17.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" - integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== + "integrity" "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==" + "resolved" "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz" + "version" "7.17.2" dependencies: - regenerator-runtime "^0.13.4" + "regenerator-runtime" "^0.13.4" -"@babel/template@^7.12.7", "@babel/template@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" - integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== +"@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.20.7": + "integrity" "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==" + "resolved" "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz" + "version" "7.20.7" dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" -"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.0", "@babel/traverse@^7.17.3": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" - integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== +"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": + "integrity" "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==" + "resolved" "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz" + "version" "7.21.3" dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.3" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.3" - "@babel/types" "^7.17.0" - debug "^4.1.0" - globals "^11.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.3" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.3" + "@babel/types" "^7.21.3" + "debug" "^4.1.0" + "globals" "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== +"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.18.6", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.3", "@babel/types@^7.4.4": + "integrity" "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==" + "resolved" "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz" + "version" "7.21.3" dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + "to-fast-properties" "^2.0.0" "@docsearch/css@3.0.0-alpha.50": - version "3.0.0-alpha.50" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.50.tgz#794c6a8d301840a49b55f5b331c7be84b9723643" - integrity sha512-QeWFCQOtS9D+Fi20liKsPXF2j/xWKh52e+P2Z1UATIdPMqmH6zoB2lcUz+cgv6PPVgWUtECeR6VSSUm71LT94w== + "integrity" "sha512-QeWFCQOtS9D+Fi20liKsPXF2j/xWKh52e+P2Z1UATIdPMqmH6zoB2lcUz+cgv6PPVgWUtECeR6VSSUm71LT94w==" + "resolved" "https://registry.npmjs.org/@docsearch/css/-/css-3.0.0-alpha.50.tgz" + "version" "3.0.0-alpha.50" "@docsearch/react@^3.0.0-alpha.39": - version "3.0.0-alpha.50" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.50.tgz#a7dc547836c2b221fd3aa8eb87bfb47a579ef141" - integrity sha512-oDGV1zZCRYv7MWsh6CyQVthYTRc3b4q+6kKwNYb1/g/Wf/4nJHutpxolFLHdEUDhrJ4Xi8wxwQG+lEwAVBTHPg== + "integrity" "sha512-oDGV1zZCRYv7MWsh6CyQVthYTRc3b4q+6kKwNYb1/g/Wf/4nJHutpxolFLHdEUDhrJ4Xi8wxwQG+lEwAVBTHPg==" + "resolved" "https://registry.npmjs.org/@docsearch/react/-/react-3.0.0-alpha.50.tgz" + "version" "3.0.0-alpha.50" dependencies: "@algolia/autocomplete-core" "1.5.2" "@algolia/autocomplete-preset-algolia" "1.5.2" "@docsearch/css" "3.0.0-alpha.50" - algoliasearch "^4.0.0" + "algoliasearch" "^4.0.0" "@docusaurus/core@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.15.tgz#1a3f8361803767072e56c77d60332c87e59f1ad0" - integrity sha512-zXhhD0fApMSvq/9Pkm9DQxa//hGOXVCq9yMHiXOkI5D1tLec7PxtnaC5cLfGHljkN9cKIfRDYUVcG1gHymVfpA== + "integrity" "sha512-zXhhD0fApMSvq/9Pkm9DQxa//hGOXVCq9yMHiXOkI5D1tLec7PxtnaC5cLfGHljkN9cKIfRDYUVcG1gHymVfpA==" + "resolved" "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@babel/core" "^7.16.0" "@babel/generator" "^7.16.0" @@ -1209,103 +1208,103 @@ "@docusaurus/utils-validation" "2.0.0-beta.15" "@slorber/static-site-generator-webpack-plugin" "^4.0.0" "@svgr/webpack" "^6.0.0" - autoprefixer "^10.3.5" - babel-loader "^8.2.2" - babel-plugin-dynamic-import-node "2.3.0" - boxen "^5.0.1" - chokidar "^3.5.2" - clean-css "^5.1.5" - commander "^5.1.0" - copy-webpack-plugin "^10.2.0" - core-js "^3.18.0" - css-loader "^6.5.1" - css-minimizer-webpack-plugin "^3.3.1" - cssnano "^5.0.8" - del "^6.0.0" - detect-port "^1.3.0" - escape-html "^1.0.3" - eta "^1.12.3" - file-loader "^6.2.0" - fs-extra "^10.0.0" - html-minifier-terser "^6.0.2" - html-tags "^3.1.0" - html-webpack-plugin "^5.4.0" - import-fresh "^3.3.0" - is-root "^2.1.0" - leven "^3.1.0" - lodash "^4.17.20" - mini-css-extract-plugin "^1.6.0" - nprogress "^0.2.0" - postcss "^8.3.7" - postcss-loader "^6.1.1" - prompts "^2.4.1" - react-dev-utils "^12.0.0" - react-helmet "^6.1.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" - react-loadable-ssr-addon-v5-slorber "^1.0.1" - react-router "^5.2.0" - react-router-config "^5.1.1" - react-router-dom "^5.2.0" - remark-admonitions "^1.2.1" - rtl-detect "^1.0.4" - semver "^7.3.4" - serve-handler "^6.1.3" - shelljs "^0.8.4" - strip-ansi "^6.0.0" - terser-webpack-plugin "^5.2.4" - tslib "^2.3.1" - update-notifier "^5.1.0" - url-loader "^4.1.1" - wait-on "^6.0.0" - webpack "^5.61.0" - webpack-bundle-analyzer "^4.4.2" - webpack-dev-server "^4.7.1" - webpack-merge "^5.8.0" - webpackbar "^5.0.2" + "autoprefixer" "^10.3.5" + "babel-loader" "^8.2.2" + "babel-plugin-dynamic-import-node" "2.3.0" + "boxen" "^5.0.1" + "chokidar" "^3.5.2" + "clean-css" "^5.1.5" + "commander" "^5.1.0" + "copy-webpack-plugin" "^10.2.0" + "core-js" "^3.18.0" + "css-loader" "^6.5.1" + "css-minimizer-webpack-plugin" "^3.3.1" + "cssnano" "^5.0.8" + "del" "^6.0.0" + "detect-port" "^1.3.0" + "escape-html" "^1.0.3" + "eta" "^1.12.3" + "file-loader" "^6.2.0" + "fs-extra" "^10.0.0" + "html-minifier-terser" "^6.0.2" + "html-tags" "^3.1.0" + "html-webpack-plugin" "^5.4.0" + "import-fresh" "^3.3.0" + "is-root" "^2.1.0" + "leven" "^3.1.0" + "lodash" "^4.17.20" + "mini-css-extract-plugin" "^1.6.0" + "nprogress" "^0.2.0" + "postcss" "^8.3.7" + "postcss-loader" "^6.1.1" + "prompts" "^2.4.1" + "react-dev-utils" "^12.0.0" + "react-helmet" "^6.1.0" + "react-loadable" "npm:@docusaurus/react-loadable@5.5.2" + "react-loadable-ssr-addon-v5-slorber" "^1.0.1" + "react-router" "^5.2.0" + "react-router-config" "^5.1.1" + "react-router-dom" "^5.2.0" + "remark-admonitions" "^1.2.1" + "rtl-detect" "^1.0.4" + "semver" "^7.3.4" + "serve-handler" "^6.1.3" + "shelljs" "^0.8.4" + "strip-ansi" "^6.0.0" + "terser-webpack-plugin" "^5.2.4" + "tslib" "^2.3.1" + "update-notifier" "^5.1.0" + "url-loader" "^4.1.1" + "wait-on" "^6.0.0" + "webpack" "^5.61.0" + "webpack-bundle-analyzer" "^4.4.2" + "webpack-dev-server" "^4.7.1" + "webpack-merge" "^5.8.0" + "webpackbar" "^5.0.2" "@docusaurus/cssnano-preset@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.15.tgz#033c52815c428f0f66c87eaff93ea12554ea89df" - integrity sha512-55aYURbB5dqrx64lStNcZxDx5R6bKkAawlCB7mDKx3r+Qnp3ofGW7UExLQSCbTu3axT1vJCF5D7H6ljTRYJLtA== + "integrity" "sha512-55aYURbB5dqrx64lStNcZxDx5R6bKkAawlCB7mDKx3r+Qnp3ofGW7UExLQSCbTu3axT1vJCF5D7H6ljTRYJLtA==" + "resolved" "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: - cssnano-preset-advanced "^5.1.4" - postcss "^8.3.7" - postcss-sort-media-queries "^4.1.0" + "cssnano-preset-advanced" "^5.1.4" + "postcss" "^8.3.7" + "postcss-sort-media-queries" "^4.1.0" "@docusaurus/logger@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.15.tgz#6d17a05fb292d15fdc43b5fa90fd2a49ad5d40ce" - integrity sha512-5bDSHCyLfMtz6QnFfICdL5mgxbGfC7DW1V+/Q17nRdpZSPZgsNKK/Esp0zdDi1oxAyEpXMXx64nLaHL7joJxIg== + "integrity" "sha512-5bDSHCyLfMtz6QnFfICdL5mgxbGfC7DW1V+/Q17nRdpZSPZgsNKK/Esp0zdDi1oxAyEpXMXx64nLaHL7joJxIg==" + "resolved" "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: - chalk "^4.1.2" - tslib "^2.3.1" + "chalk" "^4.1.2" + "tslib" "^2.3.1" "@docusaurus/mdx-loader@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.15.tgz#da23745bc73c93338dd330dad6bbc9d9fe325553" - integrity sha512-MVpytjDDao7hmPF1QSs9B5zoTgevZjiqjnX3FM1yjqdCv+chyUo0gnmYHjeG/4Gqu7jucp+dDdp6yQpzs4g09A== + "integrity" "sha512-MVpytjDDao7hmPF1QSs9B5zoTgevZjiqjnX3FM1yjqdCv+chyUo0gnmYHjeG/4Gqu7jucp+dDdp6yQpzs4g09A==" + "resolved" "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@babel/parser" "^7.16.4" "@babel/traverse" "^7.16.3" "@docusaurus/logger" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" "@mdx-js/mdx" "^1.6.21" - escape-html "^1.0.3" - file-loader "^6.2.0" - fs-extra "^10.0.0" - image-size "^1.0.1" - mdast-util-to-string "^2.0.0" - remark-emoji "^2.1.0" - stringify-object "^3.3.0" - tslib "^2.3.1" - unist-util-visit "^2.0.2" - url-loader "^4.1.1" - webpack "^5.61.0" + "escape-html" "^1.0.3" + "file-loader" "^6.2.0" + "fs-extra" "^10.0.0" + "image-size" "^1.0.1" + "mdast-util-to-string" "^2.0.0" + "remark-emoji" "^2.1.0" + "stringify-object" "^3.3.0" + "tslib" "^2.3.1" + "unist-util-visit" "^2.0.2" + "url-loader" "^4.1.1" + "webpack" "^5.61.0" "@docusaurus/plugin-content-blog@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.15.tgz#6d4bf532ad3dedb4f9fd6398b0fbe481af5b77a9" - integrity sha512-VtEwkgkoNIS8JFPe+huBeBuJ8HG8Lq1JNYM/ItwQg/cwGAgP8EgwbEuKDn428oZKEI2PpgAuf5Gv4AzJWIes9A== + "integrity" "sha512-VtEwkgkoNIS8JFPe+huBeBuJ8HG8Lq1JNYM/ItwQg/cwGAgP8EgwbEuKDn428oZKEI2PpgAuf5Gv4AzJWIes9A==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/logger" "2.0.0-beta.15" @@ -1313,98 +1312,98 @@ "@docusaurus/utils" "2.0.0-beta.15" "@docusaurus/utils-common" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - cheerio "^1.0.0-rc.10" - feed "^4.2.2" - fs-extra "^10.0.0" - lodash "^4.17.20" - reading-time "^1.5.0" - remark-admonitions "^1.2.1" - tslib "^2.3.1" - utility-types "^3.10.0" - webpack "^5.61.0" + "cheerio" "^1.0.0-rc.10" + "feed" "^4.2.2" + "fs-extra" "^10.0.0" + "lodash" "^4.17.20" + "reading-time" "^1.5.0" + "remark-admonitions" "^1.2.1" + "tslib" "^2.3.1" + "utility-types" "^3.10.0" + "webpack" "^5.61.0" "@docusaurus/plugin-content-docs@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.15.tgz#9486bba8abd2a6284e749718bf56743d8e4446f1" - integrity sha512-HSwNZdUKz4rpJiGbFjl/OFhSleeZUSZ6E6lk98i4iL1A5u6fIm4CHsT53yp4UUOse+lFrePTFZsyqwMA4nZZYA== + "integrity" "sha512-HSwNZdUKz4rpJiGbFjl/OFhSleeZUSZ6E6lk98i4iL1A5u6fIm4CHsT53yp4UUOse+lFrePTFZsyqwMA4nZZYA==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/logger" "2.0.0-beta.15" "@docusaurus/mdx-loader" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - combine-promises "^1.1.0" - fs-extra "^10.0.0" - import-fresh "^3.2.2" - js-yaml "^4.0.0" - lodash "^4.17.20" - remark-admonitions "^1.2.1" - shelljs "^0.8.4" - tslib "^2.3.1" - utility-types "^3.10.0" - webpack "^5.61.0" + "combine-promises" "^1.1.0" + "fs-extra" "^10.0.0" + "import-fresh" "^3.2.2" + "js-yaml" "^4.0.0" + "lodash" "^4.17.20" + "remark-admonitions" "^1.2.1" + "shelljs" "^0.8.4" + "tslib" "^2.3.1" + "utility-types" "^3.10.0" + "webpack" "^5.61.0" "@docusaurus/plugin-content-pages@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.15.tgz#e488f7dcdd45cd1d46e8c2c5ff5275327a6a3c65" - integrity sha512-N7YhW5RiOY6J228z4lOoP//qX0Q48cRtxDONZ/Ohd9C5OI2vS6TD8iQuDqOIYHxH+BshjNSsKvbJ+SMIQDwysg== + "integrity" "sha512-N7YhW5RiOY6J228z4lOoP//qX0Q48cRtxDONZ/Ohd9C5OI2vS6TD8iQuDqOIYHxH+BshjNSsKvbJ+SMIQDwysg==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/mdx-loader" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - fs-extra "^10.0.0" - globby "^11.0.2" - remark-admonitions "^1.2.1" - tslib "^2.3.1" - webpack "^5.61.0" + "fs-extra" "^10.0.0" + "globby" "^11.0.2" + "remark-admonitions" "^1.2.1" + "tslib" "^2.3.1" + "webpack" "^5.61.0" "@docusaurus/plugin-debug@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.15.tgz#b75d706d4f9fc4146f84015097bd837d1afb7c6b" - integrity sha512-Jth11jB/rVqPwCGdkVKSUWeXZPAr/NyPn+yeknTBk2LgQKBJ3YU5dNG0uyt0Ay+UYT01TkousPJkXhLuy4Qrsw== + "integrity" "sha512-Jth11jB/rVqPwCGdkVKSUWeXZPAr/NyPn+yeknTBk2LgQKBJ3YU5dNG0uyt0Ay+UYT01TkousPJkXhLuy4Qrsw==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" - fs-extra "^10.0.0" - react-json-view "^1.21.3" - tslib "^2.3.1" + "fs-extra" "^10.0.0" + "react-json-view" "^1.21.3" + "tslib" "^2.3.1" "@docusaurus/plugin-google-analytics@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.15.tgz#6ffebe76d9caac5383cfb78d2baa5883c9c2df6c" - integrity sha512-ELAnxNYiC2i7gfu/ViurNIdm1/DdnbEfVDmpffS9niQhOREM1U3jpxkz/ff1GIC6heOLyHTtini/CZBDoroVGw== + "integrity" "sha512-ELAnxNYiC2i7gfu/ViurNIdm1/DdnbEfVDmpffS9niQhOREM1U3jpxkz/ff1GIC6heOLyHTtini/CZBDoroVGw==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - tslib "^2.3.1" + "tslib" "^2.3.1" "@docusaurus/plugin-google-gtag@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.15.tgz#4db3330d302653e8541dc3cb86a4dbfef0cc96f8" - integrity sha512-E5Rm3+dN7i3A9V5uq5sl9xTNA3aXsLwTZEA2SpOkY571dCpd+sfVvz1lR+KRY9Fy6ZHk8PqrNImgCWfIerRuZQ== + "integrity" "sha512-E5Rm3+dN7i3A9V5uq5sl9xTNA3aXsLwTZEA2SpOkY571dCpd+sfVvz1lR+KRY9Fy6ZHk8PqrNImgCWfIerRuZQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - tslib "^2.3.1" + "tslib" "^2.3.1" "@docusaurus/plugin-sitemap@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.15.tgz#0cc083d9e76041897e81b4b82bcd0ccbfa65d6e5" - integrity sha512-PBjeQb2Qpe4uPdRefWL/eXCeYjrgNB/UArExYeUuP4wiY1dpw2unGNCvFUxv4hzJGmARoTLsnRkeYkUim809LQ== + "integrity" "sha512-PBjeQb2Qpe4uPdRefWL/eXCeYjrgNB/UArExYeUuP4wiY1dpw2unGNCvFUxv4hzJGmARoTLsnRkeYkUim809LQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" "@docusaurus/utils-common" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - fs-extra "^10.0.0" - sitemap "^7.0.0" - tslib "^2.3.1" + "fs-extra" "^10.0.0" + "sitemap" "^7.0.0" + "tslib" "^2.3.1" "@docusaurus/preset-classic@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.15.tgz#13d2f3c4fa7c055af35541ae5e93453450efb208" - integrity sha512-3NZIXWTAzk+kOgiB8uAbD+FZv3VFR1qkU6+TW24DRenjRnXof3CkRuldhI1QI0hILm1fuJ319QRkakV8FFtXyA== + "integrity" "sha512-3NZIXWTAzk+kOgiB8uAbD+FZv3VFR1qkU6+TW24DRenjRnXof3CkRuldhI1QI0hILm1fuJ319QRkakV8FFtXyA==" + "resolved" "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/plugin-content-blog" "2.0.0-beta.15" @@ -1418,18 +1417,18 @@ "@docusaurus/theme-common" "2.0.0-beta.15" "@docusaurus/theme-search-algolia" "2.0.0-beta.15" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" - integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== +"@docusaurus/react-loadable@5.5.2": + "integrity" "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz" + "version" "5.5.2" dependencies: "@types/react" "*" - prop-types "^15.6.2" + "prop-types" "^15.6.2" "@docusaurus/theme-classic@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.15.tgz#35d04232f2d5fcb2007675339b0e6d0e8681be95" - integrity sha512-WwNRcQvMtQ7KDhOEHFKFHxXCdoZwLg66hT3vhqNIFMfGQuPzOP91MX5LUSo1QWHhlrD3H3Og+r7Ik/fy2bf5lQ== + "integrity" "sha512-WwNRcQvMtQ7KDhOEHFKFHxXCdoZwLg66hT3vhqNIFMfGQuPzOP91MX5LUSo1QWHhlrD3H3Og+r7Ik/fy2bf5lQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/core" "2.0.0-beta.15" "@docusaurus/plugin-content-blog" "2.0.0-beta.15" @@ -1441,33 +1440,33 @@ "@docusaurus/utils-common" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" "@mdx-js/react" "^1.6.21" - clsx "^1.1.1" - copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.37" - lodash "^4.17.20" - postcss "^8.3.7" - prism-react-renderer "^1.2.1" - prismjs "^1.23.0" - react-router-dom "^5.2.0" - rtlcss "^3.3.0" + "clsx" "^1.1.1" + "copy-text-to-clipboard" "^3.0.1" + "infima" "0.2.0-alpha.37" + "lodash" "^4.17.20" + "postcss" "^8.3.7" + "prism-react-renderer" "^1.2.1" + "prismjs" "^1.23.0" + "react-router-dom" "^5.2.0" + "rtlcss" "^3.3.0" "@docusaurus/theme-common@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.15.tgz#5bd338d483e2c19d6d74d133572988241518398a" - integrity sha512-+pvarmzcyECE4nWxw+dCMKRIoes0NegrRuM9+nRsUrS/E5ywsF539kpupKIEqaMjq6AuM0CJtDoHxHHPNe0KaQ== + "integrity" "sha512-+pvarmzcyECE4nWxw+dCMKRIoes0NegrRuM9+nRsUrS/E5ywsF539kpupKIEqaMjq6AuM0CJtDoHxHHPNe0KaQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/plugin-content-blog" "2.0.0-beta.15" "@docusaurus/plugin-content-docs" "2.0.0-beta.15" "@docusaurus/plugin-content-pages" "2.0.0-beta.15" - clsx "^1.1.1" - parse-numeric-range "^1.3.0" - tslib "^2.3.1" - utility-types "^3.10.0" + "clsx" "^1.1.1" + "parse-numeric-range" "^1.3.0" + "tslib" "^2.3.1" + "utility-types" "^3.10.0" "@docusaurus/theme-search-algolia@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.15.tgz#c3ad7fd8e27fcb3e072990031c08768c602cb9a4" - integrity sha512-XrrQKyjOPzmEuOcdsaAn1tzNJkNMA3PC86PwPZUaah0cYPpBGptcJYDlIW4VHIrCBfkQvhvmg/B3qKF6bMMi8g== + "integrity" "sha512-XrrQKyjOPzmEuOcdsaAn1tzNJkNMA3PC86PwPZUaah0cYPpBGptcJYDlIW4VHIrCBfkQvhvmg/B3qKF6bMMi8g==" + "resolved" "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docsearch/react" "^3.0.0-alpha.39" "@docusaurus/core" "2.0.0-beta.15" @@ -1476,268 +1475,268 @@ "@docusaurus/theme-translations" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" "@docusaurus/utils-validation" "2.0.0-beta.15" - algoliasearch "^4.10.5" - algoliasearch-helper "^3.5.5" - clsx "^1.1.1" - eta "^1.12.3" - lodash "^4.17.20" - tslib "^2.3.1" - utility-types "^3.10.0" + "algoliasearch" "^4.10.5" + "algoliasearch-helper" "^3.5.5" + "clsx" "^1.1.1" + "eta" "^1.12.3" + "lodash" "^4.17.20" + "tslib" "^2.3.1" + "utility-types" "^3.10.0" "@docusaurus/theme-translations@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.15.tgz#658397ab4c0d7784043e3cec52cef7ae09d2fb59" - integrity sha512-Lu2JDsnZaB2BcJe8Hpq5nrbS7+7bd09jT08b9vztQyvzR8PgzsthnzlLN4ilOeamRIuYJKo1pUGm0EsQBOP6Nw== + "integrity" "sha512-Lu2JDsnZaB2BcJe8Hpq5nrbS7+7bd09jT08b9vztQyvzR8PgzsthnzlLN4ilOeamRIuYJKo1pUGm0EsQBOP6Nw==" + "resolved" "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: - fs-extra "^10.0.0" - tslib "^2.3.1" + "fs-extra" "^10.0.0" + "tslib" "^2.3.1" "@docusaurus/utils-common@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.15.tgz#5549b329fc750bd5e9f24952c9e3ff7cf1f63e08" - integrity sha512-kIGlSIvbE/oniUpUjI8GOkSpH8o4NXbYqAh9dqPn+TJ0KbEFY3fc80gzZQU+9SunCwJMJbIxIGevX9Ry+nackw== + "integrity" "sha512-kIGlSIvbE/oniUpUjI8GOkSpH8o4NXbYqAh9dqPn+TJ0KbEFY3fc80gzZQU+9SunCwJMJbIxIGevX9Ry+nackw==" + "resolved" "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: - tslib "^2.3.1" + "tslib" "^2.3.1" "@docusaurus/utils-validation@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.15.tgz#c664bc021194db9254eb45e6b48cb7c2af269041" - integrity sha512-1oOVBCkRrsTXSYrBTsMdnj3a/R56zrx11rjF4xo0+dmm8C01Xw4msFtc3uA7VLX0HQvgHsk8xPzU5GERNdsNpg== + "integrity" "sha512-1oOVBCkRrsTXSYrBTsMdnj3a/R56zrx11rjF4xo0+dmm8C01Xw4msFtc3uA7VLX0HQvgHsk8xPzU5GERNdsNpg==" + "resolved" "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/logger" "2.0.0-beta.15" "@docusaurus/utils" "2.0.0-beta.15" - joi "^17.4.2" - tslib "^2.3.1" + "joi" "^17.4.2" + "tslib" "^2.3.1" "@docusaurus/utils@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.15.tgz#60868046700d5585cfa6ffc57c5f3fbed00b61fc" - integrity sha512-xkoPmFxCBkDqbZR4U3SE752OcXtWTGgZnc/pZWxItzb1IYRGNZHrzdIr7CnI7rppriuZzsyivDGiC4Ud9MWhkA== + "integrity" "sha512-xkoPmFxCBkDqbZR4U3SE752OcXtWTGgZnc/pZWxItzb1IYRGNZHrzdIr7CnI7rppriuZzsyivDGiC4Ud9MWhkA==" + "resolved" "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-beta.15.tgz" + "version" "2.0.0-beta.15" dependencies: "@docusaurus/logger" "2.0.0-beta.15" "@mdx-js/runtime" "^1.6.22" "@svgr/webpack" "^6.0.0" - file-loader "^6.2.0" - fs-extra "^10.0.0" - github-slugger "^1.4.0" - globby "^11.0.4" - gray-matter "^4.0.3" - js-yaml "^4.0.0" - lodash "^4.17.20" - micromatch "^4.0.4" - remark-mdx-remove-exports "^1.6.22" - remark-mdx-remove-imports "^1.6.22" - resolve-pathname "^3.0.0" - tslib "^2.3.1" - url-loader "^4.1.1" + "file-loader" "^6.2.0" + "fs-extra" "^10.0.0" + "github-slugger" "^1.4.0" + "globby" "^11.0.4" + "gray-matter" "^4.0.3" + "js-yaml" "^4.0.0" + "lodash" "^4.17.20" + "micromatch" "^4.0.4" + "remark-mdx-remove-exports" "^1.6.22" + "remark-mdx-remove-imports" "^1.6.22" + "resolve-pathname" "^3.0.0" + "tslib" "^2.3.1" + "url-loader" "^4.1.1" "@hapi/hoek@^9.0.0": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" - integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + "integrity" "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + "resolved" "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz" + "version" "9.2.1" "@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + "integrity" "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==" + "resolved" "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz" + "version" "5.1.0" dependencies: "@hapi/hoek" "^9.0.0" -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.1.0": + "integrity" "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==" + "resolved" "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + "version" "0.1.1" + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + "integrity" "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==" + "resolved" "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + "version" "0.3.2" dependencies: "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@3.1.0": + "integrity" "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + "resolved" "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + "version" "3.1.0" -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + "integrity" "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "resolved" "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + "version" "1.1.2" "@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + "integrity" "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==" + "resolved" "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" + "version" "0.3.2" dependencies: "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14": + "integrity" "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "resolved" "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + "version" "1.4.14" -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + "integrity" "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==" + "resolved" "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" + "version" "0.3.17" dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" - integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== +"@mdx-js/mdx@^1.6.21", "@mdx-js/mdx@1.6.22": + "integrity" "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==" + "resolved" "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz" + "version" "1.6.22" dependencies: "@babel/core" "7.12.9" "@babel/plugin-syntax-jsx" "7.12.1" "@babel/plugin-syntax-object-rest-spread" "7.8.3" "@mdx-js/util" "1.6.22" - babel-plugin-apply-mdx-type-prop "1.6.22" - babel-plugin-extract-import-names "1.6.22" - camelcase-css "2.0.1" - detab "2.0.4" - hast-util-raw "6.0.1" - lodash.uniq "4.5.0" - mdast-util-to-hast "10.0.1" - remark-footnotes "2.0.0" - remark-mdx "1.6.22" - remark-parse "8.0.3" - remark-squeeze-paragraphs "4.0.0" - style-to-object "0.3.0" - unified "9.2.0" - unist-builder "2.0.3" - unist-util-visit "2.0.3" + "babel-plugin-apply-mdx-type-prop" "1.6.22" + "babel-plugin-extract-import-names" "1.6.22" + "camelcase-css" "2.0.1" + "detab" "2.0.4" + "hast-util-raw" "6.0.1" + "lodash.uniq" "4.5.0" + "mdast-util-to-hast" "10.0.1" + "remark-footnotes" "2.0.0" + "remark-mdx" "1.6.22" + "remark-parse" "8.0.3" + "remark-squeeze-paragraphs" "4.0.0" + "style-to-object" "0.3.0" + "unified" "9.2.0" + "unist-builder" "2.0.3" + "unist-util-visit" "2.0.3" -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.21": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" - integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== +"@mdx-js/react@^1.6.21", "@mdx-js/react@1.6.22": + "integrity" "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==" + "resolved" "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz" + "version" "1.6.22" "@mdx-js/runtime@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/runtime/-/runtime-1.6.22.tgz#3edd388bf68a519ffa1aaf9c446b548165102345" - integrity sha512-p17spaO2+55VLCuxXA3LVHC4phRx60NR2XMdZ+qgVU1lKvEX4y88dmFNOzGDCPLJ03IZyKrJ/rPWWRiBrd9JrQ== + "integrity" "sha512-p17spaO2+55VLCuxXA3LVHC4phRx60NR2XMdZ+qgVU1lKvEX4y88dmFNOzGDCPLJ03IZyKrJ/rPWWRiBrd9JrQ==" + "resolved" "https://registry.npmjs.org/@mdx-js/runtime/-/runtime-1.6.22.tgz" + "version" "1.6.22" dependencies: "@mdx-js/mdx" "1.6.22" "@mdx-js/react" "1.6.22" - buble-jsx-only "^0.19.8" + "buble-jsx-only" "^0.19.8" "@mdx-js/util@1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" - integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== + "integrity" "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==" + "resolved" "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz" + "version" "1.6.22" "@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + "integrity" "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + "version" "2.1.5" dependencies: "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" + "run-parallel" "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": + "integrity" "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + "version" "2.0.5" "@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + "integrity" "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==" + "resolved" "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + "version" "1.2.8" dependencies: "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" + "fastq" "^1.6.0" "@polka/url@^1.0.0-next.20": - version "1.0.0-next.21" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" - integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + "integrity" "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==" + "resolved" "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz" + "version" "1.0.0-next.21" "@sideway/address@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" - integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + "integrity" "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==" + "resolved" "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz" + "version" "4.1.3" dependencies: "@hapi/hoek" "^9.0.0" "@sideway/formula@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" - integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + "integrity" "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + "resolved" "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz" + "version" "3.0.1" "@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "integrity" "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + "resolved" "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" + "version" "2.0.0" "@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "integrity" "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + "resolved" "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" + "version" "0.14.0" "@slorber/static-site-generator-webpack-plugin@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841" - integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw== + "integrity" "sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw==" + "resolved" "https://registry.npmjs.org/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz" + "version" "4.0.1" dependencies: - bluebird "^3.7.1" - cheerio "^0.22.0" - eval "^0.1.4" - url "^0.11.0" - webpack-sources "^1.4.3" + "bluebird" "^3.7.1" + "cheerio" "^0.22.0" + "eval" "^0.1.4" + "url" "^0.11.0" + "webpack-sources" "^1.4.3" "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18" - integrity sha512-MdPdhdWLtQsjd29Wa4pABdhWbaRMACdM1h31BY+c6FghTZqNGT7pEYdBoaGeKtdTOBC/XNFQaKVj+r/Ei2ryWA== + "integrity" "sha512-MdPdhdWLtQsjd29Wa4pABdhWbaRMACdM1h31BY+c6FghTZqNGT7pEYdBoaGeKtdTOBC/XNFQaKVj+r/Ei2ryWA==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-remove-jsx-attribute@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.0.0.tgz#58654908beebfa069681a83332544b17e5237e89" - integrity sha512-aVdtfx9jlaaxc3unA6l+M9YRnKIZjOhQPthLKqmTXC8UVkBLDRGwPKo+r8n3VZN8B34+yVajzPTZ+ptTSuZZCw== + "integrity" "sha512-aVdtfx9jlaaxc3unA6l+M9YRnKIZjOhQPthLKqmTXC8UVkBLDRGwPKo+r8n3VZN8B34+yVajzPTZ+ptTSuZZCw==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-remove-jsx-empty-expression@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.0.0.tgz#d06dd6e8a8f603f92f9979bb9990a1f85a4f57ba" - integrity sha512-Ccj42ApsePD451AZJJf1QzTD1B/BOU392URJTeXFxSK709i0KUsGtbwyiqsKu7vsYxpTM0IA5clAKDyf9RCZyA== + "integrity" "sha512-Ccj42ApsePD451AZJJf1QzTD1B/BOU392URJTeXFxSK709i0KUsGtbwyiqsKu7vsYxpTM0IA5clAKDyf9RCZyA==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-replace-jsx-attribute-value@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.0.0.tgz#0b85837577b02c31c09c758a12932820f5245cee" - integrity sha512-88V26WGyt1Sfd1emBYmBJRWMmgarrExpKNVmI9vVozha4kqs6FzQJ/Kp5+EYli1apgX44518/0+t9+NU36lThQ== + "integrity" "sha512-88V26WGyt1Sfd1emBYmBJRWMmgarrExpKNVmI9vVozha4kqs6FzQJ/Kp5+EYli1apgX44518/0+t9+NU36lThQ==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-svg-dynamic-title@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.0.0.tgz#28236ec26f7ab9d486a487d36ae52d58ba15676f" - integrity sha512-F7YXNLfGze+xv0KMQxrl2vkNbI9kzT9oDK55/kUuymh1ACyXkMV+VZWX1zEhSTfEKh7VkHVZGmVtHg8eTZ6PRg== + "integrity" "sha512-F7YXNLfGze+xv0KMQxrl2vkNbI9kzT9oDK55/kUuymh1ACyXkMV+VZWX1zEhSTfEKh7VkHVZGmVtHg8eTZ6PRg==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-svg-em-dimensions@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.0.0.tgz#40267c5dea1b43c4f83a0eb6169e08b43d8bafce" - integrity sha512-+rghFXxdIqJNLQK08kwPBD3Z22/0b2tEZ9lKiL/yTfuyj1wW8HUXu4bo/XkogATIYuXSghVQOOCwURXzHGKyZA== + "integrity" "sha512-+rghFXxdIqJNLQK08kwPBD3Z22/0b2tEZ9lKiL/yTfuyj1wW8HUXu4bo/XkogATIYuXSghVQOOCwURXzHGKyZA==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-transform-react-native-svg@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" - integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== + "integrity" "sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz" + "version" "6.0.0" "@svgr/babel-plugin-transform-svg-component@^6.2.0": - version "6.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" - integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== + "integrity" "sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg==" + "resolved" "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz" + "version" "6.2.0" "@svgr/babel-preset@^6.2.0": - version "6.2.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" - integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== + "integrity" "sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ==" + "resolved" "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.2.0.tgz" + "version" "6.2.0" dependencies: "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" @@ -1748,46 +1747,46 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" "@svgr/babel-plugin-transform-svg-component" "^6.2.0" -"@svgr/core@^6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" - integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== +"@svgr/core@^6.0.0", "@svgr/core@^6.2.1": + "integrity" "sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA==" + "resolved" "https://registry.npmjs.org/@svgr/core/-/core-6.2.1.tgz" + "version" "6.2.1" dependencies: "@svgr/plugin-jsx" "^6.2.1" - camelcase "^6.2.0" - cosmiconfig "^7.0.1" + "camelcase" "^6.2.0" + "cosmiconfig" "^7.0.1" "@svgr/hast-util-to-babel-ast@^6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" - integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== + "integrity" "sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ==" + "resolved" "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz" + "version" "6.2.1" dependencies: "@babel/types" "^7.15.6" - entities "^3.0.1" + "entities" "^3.0.1" "@svgr/plugin-jsx@^6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" - integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== + "integrity" "sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g==" + "resolved" "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz" + "version" "6.2.1" dependencies: "@babel/core" "^7.15.5" "@svgr/babel-preset" "^6.2.0" "@svgr/hast-util-to-babel-ast" "^6.2.1" - svg-parser "^2.0.2" + "svg-parser" "^2.0.2" "@svgr/plugin-svgo@^6.2.0": - version "6.2.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz#4cbe6a33ccccdcae4e3b63ded64cc1cbe1faf48c" - integrity sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q== + "integrity" "sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q==" + "resolved" "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz" + "version" "6.2.0" dependencies: - cosmiconfig "^7.0.1" - deepmerge "^4.2.2" - svgo "^2.5.0" + "cosmiconfig" "^7.0.1" + "deepmerge" "^4.2.2" + "svgo" "^2.5.0" "@svgr/webpack@^6.0.0": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.2.1.tgz#ef5d51c1b6be4e7537fb9f76b3f2b2e22b63c58d" - integrity sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw== + "integrity" "sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw==" + "resolved" "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.2.1.tgz" + "version" "6.2.1" dependencies: "@babel/core" "^7.15.5" "@babel/plugin-transform-react-constant-elements" "^7.14.5" @@ -1799,81 +1798,81 @@ "@svgr/plugin-svgo" "^6.2.0" "@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + "integrity" "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==" + "resolved" "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" + "version" "1.1.2" dependencies: - defer-to-connect "^1.0.1" + "defer-to-connect" "^1.0.1" "@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + "integrity" "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" + "resolved" "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" + "version" "0.2.0" "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + "integrity" "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==" + "resolved" "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" + "version" "1.19.2" dependencies: "@types/connect" "*" "@types/node" "*" "@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + "integrity" "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==" + "resolved" "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz" + "version" "3.5.10" dependencies: "@types/node" "*" "@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + "integrity" "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==" + "resolved" "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz" + "version" "1.3.5" dependencies: "@types/express-serve-static-core" "*" "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + "integrity" "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==" + "resolved" "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" + "version" "3.4.35" dependencies: "@types/node" "*" "@types/eslint-scope@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + "integrity" "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==" + "resolved" "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz" + "version" "3.7.3" dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.4.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" - integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== + "integrity" "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==" + "resolved" "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz" + "version" "8.4.1" dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*", "@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "integrity" "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + "resolved" "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz" + "version" "0.0.51" "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": - version "4.17.28" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" - integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + "integrity" "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==" + "resolved" "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz" + "version" "4.17.28" dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/express@*", "@types/express@^4.17.13": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + "integrity" "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==" + "resolved" "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" + "version" "4.17.13" dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.18" @@ -1881,172 +1880,172 @@ "@types/serve-static" "*" "@types/hast@^2.0.0": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" - integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + "integrity" "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==" + "resolved" "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz" + "version" "2.3.4" dependencies: "@types/unist" "*" "@types/html-minifier-terser@^6.0.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" - integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + "integrity" "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + "resolved" "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz" + "version" "6.1.0" "@types/http-proxy@^1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" - integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== + "integrity" "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==" + "resolved" "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz" + "version" "1.17.8" dependencies: "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + "integrity" "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" + "version" "7.0.9" "@types/mdast@^3.0.0": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" - integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== + "integrity" "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==" + "resolved" "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz" + "version" "3.0.10" dependencies: "@types/unist" "*" "@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "integrity" "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + "resolved" "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" + "version" "1.3.2" "@types/node@*", "@types/node@^17.0.5": - version "17.0.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" - integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA== + "integrity" "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==" + "resolved" "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz" + "version" "17.0.18" "@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "integrity" "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "resolved" "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" + "version" "4.0.0" "@types/parse5@^5.0.0": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" - integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + "integrity" "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + "resolved" "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz" + "version" "5.0.3" "@types/prop-types@*": - version "15.7.4" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" - integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + "integrity" "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" + "resolved" "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz" + "version" "15.7.4" "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + "integrity" "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "resolved" "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" + "version" "6.9.7" "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "integrity" "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + "resolved" "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" + "version" "1.2.4" -"@types/react@*": - version "17.0.39" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" - integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug== +"@types/react@*", "@types/react@>= 16.8.0 < 18.0.0": + "integrity" "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==" + "resolved" "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz" + "version" "17.0.39" dependencies: "@types/prop-types" "*" "@types/scheduler" "*" - csstype "^3.0.2" + "csstype" "^3.0.2" "@types/retry@^0.12.0": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" - integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + "integrity" "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==" + "resolved" "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz" + "version" "0.12.1" "@types/sax@^1.2.1": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.4.tgz#8221affa7f4f3cb21abd22f244cfabfa63e6a69e" - integrity sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw== + "integrity" "sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==" + "resolved" "https://registry.npmjs.org/@types/sax/-/sax-1.2.4.tgz" + "version" "1.2.4" dependencies: "@types/node" "*" "@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "integrity" "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "resolved" "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" + "version" "0.16.2" "@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + "integrity" "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==" + "resolved" "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz" + "version" "1.9.1" dependencies: "@types/express" "*" "@types/serve-static@*": - version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" - integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + "integrity" "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==" + "resolved" "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz" + "version" "1.13.10" dependencies: "@types/mime" "^1" "@types/node" "*" "@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + "integrity" "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==" + "resolved" "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz" + "version" "0.3.33" dependencies: "@types/node" "*" "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" - integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + "integrity" "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + "resolved" "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz" + "version" "2.0.6" "@types/ws@^8.2.2": - version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" - integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== + "integrity" "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==" + "resolved" "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz" + "version" "8.2.2" dependencies: "@types/node" "*" "@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + "integrity" "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/helper-numbers" "1.11.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.1" "@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + "integrity" "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz" + "version" "1.11.1" "@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + "integrity" "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz" + "version" "1.11.1" "@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + "integrity" "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz" + "version" "1.11.1" "@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + "integrity" "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/floating-point-hex-parser" "1.11.1" "@webassemblyjs/helper-api-error" "1.11.1" "@xtuc/long" "4.2.2" "@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + "integrity" "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz" + "version" "1.11.1" "@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + "integrity" "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/helper-buffer" "1.11.1" @@ -2054,28 +2053,28 @@ "@webassemblyjs/wasm-gen" "1.11.1" "@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + "integrity" "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz" + "version" "1.11.1" dependencies: "@xtuc/ieee754" "^1.2.0" "@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + "integrity" "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz" + "version" "1.11.1" dependencies: "@xtuc/long" "4.2.2" "@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + "integrity" "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz" + "version" "1.11.1" "@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + "integrity" "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/helper-buffer" "1.11.1" @@ -2087,9 +2086,9 @@ "@webassemblyjs/wast-printer" "1.11.1" "@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + "integrity" "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.1" @@ -2098,9 +2097,9 @@ "@webassemblyjs/utf8" "1.11.1" "@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + "integrity" "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/helper-buffer" "1.11.1" @@ -2108,9 +2107,9 @@ "@webassemblyjs/wasm-parser" "1.11.1" "@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + "integrity" "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/helper-api-error" "1.11.1" @@ -2120,124 +2119,124 @@ "@webassemblyjs/utf8" "1.11.1" "@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + "integrity" "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==" + "resolved" "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz" + "version" "1.11.1" dependencies: "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + "integrity" "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "resolved" "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + "version" "1.2.0" "@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + "integrity" "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "resolved" "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + "version" "4.2.2" -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== +"accepts@~1.3.4", "accepts@~1.3.5", "accepts@~1.3.8": + "integrity" "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==" + "resolved" "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" + "version" "1.3.8" dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" + "mime-types" "~2.1.34" + "negotiator" "0.6.3" -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== +"acorn-dynamic-import@^4.0.0": + "integrity" "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" + "resolved" "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz" + "version" "4.0.0" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +"acorn-import-assertions@^1.7.6": + "integrity" "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==" + "resolved" "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" + "version" "1.8.0" -acorn-jsx@^5.0.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +"acorn-jsx@^5.0.1": + "integrity" "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" + "resolved" "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + "version" "5.3.2" -acorn-walk@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +"acorn-walk@^8.0.0": + "integrity" "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "resolved" "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" + "version" "8.2.0" -acorn@^6.1.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== +"acorn@^6.0.0", "acorn@^6.0.0 || ^7.0.0 || ^8.0.0", "acorn@^6.1.1": + "integrity" "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + "resolved" "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz" + "version" "6.4.2" -acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +"acorn@^8", "acorn@^8.0.4", "acorn@^8.4.1", "acorn@^8.5.0": + "integrity" "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==" + "resolved" "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz" + "version" "8.7.1" -address@^1.0.1, address@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" - integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== +"address@^1.0.1", "address@^1.1.2": + "integrity" "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" + "resolved" "https://registry.npmjs.org/address/-/address-1.1.2.tgz" + "version" "1.1.2" -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== +"aggregate-error@^3.0.0": + "integrity" "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==" + "resolved" "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" + "version" "3.1.0" dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" + "clean-stack" "^2.0.0" + "indent-string" "^4.0.0" -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== +"ajv-formats@^2.1.1": + "integrity" "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==" + "resolved" "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + "version" "2.1.1" dependencies: - ajv "^8.0.0" + "ajv" "^8.0.0" -ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +"ajv-keywords@^3.4.1", "ajv-keywords@^3.5.2": + "integrity" "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "resolved" "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + "version" "3.5.2" -ajv-keywords@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== +"ajv-keywords@^5.0.0": + "integrity" "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==" + "resolved" "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz" + "version" "5.1.0" dependencies: - fast-deep-equal "^3.1.3" + "fast-deep-equal" "^3.1.3" -ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +"ajv@^6.12.2", "ajv@^6.12.4", "ajv@^6.12.5", "ajv@^6.9.1": + "integrity" "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + "version" "6.12.6" dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + "fast-deep-equal" "^3.1.1" + "fast-json-stable-stringify" "^2.0.0" + "json-schema-traverse" "^0.4.1" + "uri-js" "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" - integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== +"ajv@^8.0.0", "ajv@^8.8.0", "ajv@^8.8.2": + "integrity" "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==" + "resolved" "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz" + "version" "8.10.0" dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" + "fast-deep-equal" "^3.1.1" + "json-schema-traverse" "^1.0.0" + "require-from-string" "^2.0.2" + "uri-js" "^4.2.2" -algoliasearch-helper@^3.5.5: - version "3.7.0" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.0.tgz#c0a0493df84d850360f664ad7a9d4fc78a94fd78" - integrity sha512-XJ3QfERBLfeVCyTVx80gon7r3/rgm/CE8Ha1H7cbablRe/X7SfYQ14g/eO+MhjVKIQp+gy9oC6G5ilmLwS1k6w== +"algoliasearch-helper@^3.5.5": + "integrity" "sha512-XJ3QfERBLfeVCyTVx80gon7r3/rgm/CE8Ha1H7cbablRe/X7SfYQ14g/eO+MhjVKIQp+gy9oC6G5ilmLwS1k6w==" + "resolved" "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.7.0.tgz" + "version" "3.7.0" dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.10.5: - version "4.12.1" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.12.1.tgz#574a2c5424c4b6681c026928fb810be2d2ec3924" - integrity sha512-c0dM1g3zZBJrkzE5GA/Nu1y3fFxx3LCzxKzcmp2dgGS8P4CjszB/l3lsSh2MSrrK1Hn/KV4BlbBMXtYgG1Bfrw== +"algoliasearch@^4.0.0", "algoliasearch@^4.10.5", "algoliasearch@^4.9.1", "algoliasearch@>= 3.1 < 5": + "integrity" "sha512-c0dM1g3zZBJrkzE5GA/Nu1y3fFxx3LCzxKzcmp2dgGS8P4CjszB/l3lsSh2MSrrK1Hn/KV4BlbBMXtYgG1Bfrw==" + "resolved" "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.12.1.tgz" + "version" "4.12.1" dependencies: "@algolia/cache-browser-local-storage" "4.12.1" "@algolia/cache-common" "4.12.1" @@ -2254,2429 +2253,2448 @@ algoliasearch@^4.0.0, algoliasearch@^4.10.5: "@algolia/requester-node-http" "4.12.1" "@algolia/transporter" "4.12.1" -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== +"ansi-align@^3.0.0": + "integrity" "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==" + "resolved" "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz" + "version" "3.0.1" dependencies: - string-width "^4.1.0" + "string-width" "^4.1.0" -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== +"ansi-html-community@^0.0.8": + "integrity" "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==" + "resolved" "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz" + "version" "0.0.8" -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +"ansi-regex@^5.0.1": + "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + "version" "5.0.1" -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== +"ansi-regex@^6.0.1": + "integrity" "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" + "version" "6.0.1" -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== +"ansi-styles@^3.2.1": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" dependencies: - color-convert "^1.9.0" + "color-convert" "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== +"ansi-styles@^4.0.0", "ansi-styles@^4.1.0": + "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + "version" "4.3.0" dependencies: - color-convert "^2.0.1" + "color-convert" "^2.0.1" -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== +"anymatch@~3.1.2": + "integrity" "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==" + "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + "version" "3.1.2" dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" + "normalize-path" "^3.0.0" + "picomatch" "^2.0.4" -arg@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" - integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== +"arg@^5.0.0": + "integrity" "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==" + "resolved" "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz" + "version" "5.0.1" -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== +"argparse@^1.0.7": + "integrity" "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + "version" "1.0.10" dependencies: - sprintf-js "~1.0.2" + "sprintf-js" "~1.0.2" -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +"argparse@^2.0.1": + "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + "version" "2.0.1" -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +"array-flatten@^2.1.0": + "integrity" "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + "resolved" "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" + "version" "2.1.2" -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== +"array-flatten@1.1.1": + "integrity" "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "resolved" "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + "version" "1.1.1" -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +"array-union@^2.1.0": + "integrity" "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "resolved" "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + "version" "2.1.0" -array-union@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" - integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== +"array-union@^3.0.1": + "integrity" "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==" + "resolved" "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz" + "version" "3.0.1" -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +"asap@~2.0.3": + "integrity" "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "resolved" "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" + "version" "2.0.6" -async@^2.6.2: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== +"async@^2.6.2": + "integrity" "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==" + "resolved" "https://registry.npmjs.org/async/-/async-2.6.4.tgz" + "version" "2.6.4" dependencies: - lodash "^4.17.14" + "lodash" "^4.17.14" -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +"at-least-node@^1.0.0": + "integrity" "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + "resolved" "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + "version" "1.0.0" -autoprefixer@^10.3.5, autoprefixer@^10.3.7: - version "10.4.2" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b" - integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ== +"autoprefixer@^10.3.5", "autoprefixer@^10.3.7": + "integrity" "sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==" + "resolved" "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz" + "version" "10.4.2" dependencies: - browserslist "^4.19.1" - caniuse-lite "^1.0.30001297" - fraction.js "^4.1.2" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" + "browserslist" "^4.19.1" + "caniuse-lite" "^1.0.30001297" + "fraction.js" "^4.1.2" + "normalize-range" "^0.1.2" + "picocolors" "^1.0.0" + "postcss-value-parser" "^4.2.0" -axios@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" - integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== +"axios@^0.25.0": + "integrity" "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==" + "resolved" "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz" + "version" "0.25.0" dependencies: - follow-redirects "^1.14.7" + "follow-redirects" "^1.14.7" -babel-loader@^8.2.2: - version "8.2.3" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" - integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== +"babel-loader@^8.2.2": + "integrity" "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==" + "resolved" "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz" + "version" "8.2.3" dependencies: - find-cache-dir "^3.3.1" - loader-utils "^1.4.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" + "find-cache-dir" "^3.3.1" + "loader-utils" "^1.4.0" + "make-dir" "^3.1.0" + "schema-utils" "^2.6.5" -babel-plugin-apply-mdx-type-prop@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" - integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== +"babel-plugin-apply-mdx-type-prop@1.6.22": + "integrity" "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==" + "resolved" "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz" + "version" "1.6.22" dependencies: "@babel/helper-plugin-utils" "7.10.4" "@mdx-js/util" "1.6.22" -babel-plugin-dynamic-import-node@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== +"babel-plugin-dynamic-import-node@^2.3.3": + "integrity" "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==" + "resolved" "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" + "version" "2.3.3" dependencies: - object.assign "^4.1.0" + "object.assign" "^4.1.0" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== +"babel-plugin-dynamic-import-node@2.3.0": + "integrity" "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==" + "resolved" "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz" + "version" "2.3.0" dependencies: - object.assign "^4.1.0" + "object.assign" "^4.1.0" -babel-plugin-extract-import-names@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" - integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== +"babel-plugin-extract-import-names@1.6.22": + "integrity" "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==" + "resolved" "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz" + "version" "1.6.22" dependencies: "@babel/helper-plugin-utils" "7.10.4" -babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" - integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== +"babel-plugin-polyfill-corejs2@^0.3.0": + "integrity" "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==" + "resolved" "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz" + "version" "0.3.1" dependencies: "@babel/compat-data" "^7.13.11" "@babel/helper-define-polyfill-provider" "^0.3.1" - semver "^6.1.1" + "semver" "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" - integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== +"babel-plugin-polyfill-corejs3@^0.5.0": + "integrity" "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==" + "resolved" "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz" + "version" "0.5.2" dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" - core-js-compat "^3.21.0" + "core-js-compat" "^3.21.0" -babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" - integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== +"babel-plugin-polyfill-regenerator@^0.3.0": + "integrity" "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==" + "resolved" "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz" + "version" "0.3.1" dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== +"bail@^1.0.0": + "integrity" "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" + "resolved" "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz" + "version" "1.0.5" -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +"balanced-match@^1.0.0": + "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + "version" "1.0.2" -base16@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" - integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= +"base16@^1.0.0": + "integrity" "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" + "resolved" "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz" + "version" "1.0.0" -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= +"batch@0.6.1": + "integrity" "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + "resolved" "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" + "version" "0.6.1" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +"big.js@^5.2.2": + "integrity" "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + "resolved" "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" + "version" "5.2.2" -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +"binary-extensions@^2.0.0": + "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + "version" "2.2.0" -bluebird@^3.7.1: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +"bluebird@^3.7.1": + "integrity" "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" + "version" "3.7.2" -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" - integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== +"body-parser@1.19.2": + "integrity" "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==" + "resolved" "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz" + "version" "1.19.2" dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" - type-is "~1.6.18" + "bytes" "3.1.2" + "content-type" "~1.0.4" + "debug" "2.6.9" + "depd" "~1.1.2" + "http-errors" "1.8.1" + "iconv-lite" "0.4.24" + "on-finished" "~2.3.0" + "qs" "6.9.7" + "raw-body" "2.4.3" + "type-is" "~1.6.18" -bonjour@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= +"bonjour@^3.5.0": + "integrity" "sha1-jokKGD2O6aI5OzhExpGkK897yfU=" + "resolved" "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz" + "version" "3.5.0" dependencies: - array-flatten "^2.1.0" - deep-equal "^1.0.1" - dns-equal "^1.0.0" - dns-txt "^2.0.2" - multicast-dns "^6.0.1" - multicast-dns-service-types "^1.1.0" + "array-flatten" "^2.1.0" + "deep-equal" "^1.0.1" + "dns-equal" "^1.0.0" + "dns-txt" "^2.0.2" + "multicast-dns" "^6.0.1" + "multicast-dns-service-types" "^1.1.0" -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +"boolbase@^1.0.0", "boolbase@~1.0.0": + "integrity" "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "resolved" "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + "version" "1.0.0" -boxen@^5.0.0, boxen@^5.0.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== +"boxen@^5.0.0", "boxen@^5.0.1": + "integrity" "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==" + "resolved" "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz" + "version" "5.1.2" dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" + "ansi-align" "^3.0.0" + "camelcase" "^6.2.0" + "chalk" "^4.1.0" + "cli-boxes" "^2.2.1" + "string-width" "^4.2.2" + "type-fest" "^0.20.2" + "widest-line" "^3.1.0" + "wrap-ansi" "^7.0.0" -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== +"brace-expansion@^1.1.7": + "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + "version" "1.1.11" dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" + "balanced-match" "^1.0.0" + "concat-map" "0.0.1" -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +"braces@^3.0.1", "braces@~3.0.2": + "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + "version" "3.0.2" dependencies: - fill-range "^7.0.1" + "fill-range" "^7.0.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== +"browserslist@^4.0.0", "browserslist@^4.14.5", "browserslist@^4.16.6", "browserslist@^4.18.1", "browserslist@^4.19.1", "browserslist@^4.21.3", "browserslist@>= 4.21.0": + "integrity" "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==" + "resolved" "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" + "version" "4.21.5" dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" - escalade "^3.1.1" - node-releases "^2.0.1" - picocolors "^1.0.0" + "caniuse-lite" "^1.0.30001449" + "electron-to-chromium" "^1.4.284" + "node-releases" "^2.0.8" + "update-browserslist-db" "^1.0.10" -buble-jsx-only@^0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867" - integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA== +"buble-jsx-only@^0.19.8": + "integrity" "sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA==" + "resolved" "https://registry.npmjs.org/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz" + "version" "0.19.8" dependencies: - acorn "^6.1.1" - acorn-dynamic-import "^4.0.0" - acorn-jsx "^5.0.1" - chalk "^2.4.2" - magic-string "^0.25.3" - minimist "^1.2.0" - regexpu-core "^4.5.4" + "acorn" "^6.1.1" + "acorn-dynamic-import" "^4.0.0" + "acorn-jsx" "^5.0.1" + "chalk" "^2.4.2" + "magic-string" "^0.25.3" + "minimist" "^1.2.0" + "regexpu-core" "^4.5.4" -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +"buffer-from@^1.0.0": + "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + "version" "1.1.2" -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== +"buffer-indexof@^1.0.0": + "integrity" "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + "resolved" "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz" + "version" "1.1.1" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= +"bytes@3.0.0": + "integrity" "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "resolved" "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + "version" "3.0.0" -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +"bytes@3.1.2": + "integrity" "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + "resolved" "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + "version" "3.1.2" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== +"cacheable-request@^6.0.0": + "integrity" "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==" + "resolved" "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz" + "version" "6.1.0" dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" + "clone-response" "^1.0.2" + "get-stream" "^5.1.0" + "http-cache-semantics" "^4.0.0" + "keyv" "^3.0.0" + "lowercase-keys" "^2.0.0" + "normalize-url" "^4.1.0" + "responselike" "^1.0.2" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== +"call-bind@^1.0.0", "call-bind@^1.0.2": + "integrity" "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==" + "resolved" "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + "version" "1.0.2" dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" + "function-bind" "^1.1.1" + "get-intrinsic" "^1.0.2" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +"callsites@^3.0.0": + "integrity" "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "resolved" "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + "version" "3.1.0" -camel-case@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== +"camel-case@^4.1.2": + "integrity" "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==" + "resolved" "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz" + "version" "4.1.2" dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" + "pascal-case" "^3.1.2" + "tslib" "^2.0.3" -camelcase-css@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +"camelcase-css@2.0.1": + "integrity" "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + "resolved" "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" + "version" "2.0.1" -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +"camelcase@^6.2.0": + "integrity" "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + "version" "6.3.0" -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== +"caniuse-api@^3.0.0": + "integrity" "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==" + "resolved" "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz" + "version" "3.0.0" dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" + "browserslist" "^4.0.0" + "caniuse-lite" "^1.0.0" + "lodash.memoize" "^4.1.2" + "lodash.uniq" "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297: - version "1.0.30001312" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" - integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== +"caniuse-lite@^1.0.0", "caniuse-lite@^1.0.30001297", "caniuse-lite@^1.0.30001449": + "integrity" "sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==" + "resolved" "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz" + "version" "1.0.30001466" -ccount@^1.0.0, ccount@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" - integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +"ccount@^1.0.0", "ccount@^1.0.3": + "integrity" "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" + "resolved" "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz" + "version" "1.1.0" -chalk@^2.0.0, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== +"chalk@^2.0.0": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" -chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== +"chalk@^2.4.2": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" -character-entities-legacy@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" - integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== - -character-entities@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" - integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== - -character-reference-invalid@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" - integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== - -cheerio-select@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" - integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== +"chalk@^4.1.0", "chalk@^4.1.2": + "integrity" "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + "version" "4.1.2" dependencies: - css-select "^4.1.3" - css-what "^5.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - domutils "^2.7.0" + "ansi-styles" "^4.1.0" + "supports-color" "^7.1.0" -cheerio@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" +"character-entities-legacy@^1.0.0": + "integrity" "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + "resolved" "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz" + "version" "1.1.4" -cheerio@^1.0.0-rc.10: - version "1.0.0-rc.10" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" - integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== - dependencies: - cheerio-select "^1.5.0" - dom-serializer "^1.3.2" - domhandler "^4.2.0" - htmlparser2 "^6.1.0" - parse5 "^6.0.1" - parse5-htmlparser2-tree-adapter "^6.0.1" - tslib "^2.2.0" +"character-entities@^1.0.0": + "integrity" "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + "resolved" "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz" + "version" "1.2.4" -chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== +"character-reference-invalid@^1.0.0": + "integrity" "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + "resolved" "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz" + "version" "1.1.4" + +"cheerio-select@^1.5.0": + "integrity" "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==" + "resolved" "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz" + "version" "1.5.0" dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" + "css-select" "^4.1.3" + "css-what" "^5.0.1" + "domelementtype" "^2.2.0" + "domhandler" "^4.2.0" + "domutils" "^2.7.0" + +"cheerio@^0.22.0": + "integrity" "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=" + "resolved" "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz" + "version" "0.22.0" + dependencies: + "css-select" "~1.2.0" + "dom-serializer" "~0.1.0" + "entities" "~1.1.1" + "htmlparser2" "^3.9.1" + "lodash.assignin" "^4.0.9" + "lodash.bind" "^4.1.4" + "lodash.defaults" "^4.0.1" + "lodash.filter" "^4.4.0" + "lodash.flatten" "^4.2.0" + "lodash.foreach" "^4.3.0" + "lodash.map" "^4.4.0" + "lodash.merge" "^4.4.0" + "lodash.pick" "^4.2.1" + "lodash.reduce" "^4.4.0" + "lodash.reject" "^4.4.0" + "lodash.some" "^4.4.0" + +"cheerio@^1.0.0-rc.10": + "integrity" "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==" + "resolved" "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz" + "version" "1.0.0-rc.10" + dependencies: + "cheerio-select" "^1.5.0" + "dom-serializer" "^1.3.2" + "domhandler" "^4.2.0" + "htmlparser2" "^6.1.0" + "parse5" "^6.0.1" + "parse5-htmlparser2-tree-adapter" "^6.0.1" + "tslib" "^2.2.0" + +"chokidar@^3.4.2", "chokidar@^3.5.2", "chokidar@^3.5.3": + "integrity" "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==" + "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + "version" "3.5.3" + dependencies: + "anymatch" "~3.1.2" + "braces" "~3.0.2" + "glob-parent" "~5.1.2" + "is-binary-path" "~2.1.0" + "is-glob" "~4.0.1" + "normalize-path" "~3.0.0" + "readdirp" "~3.6.0" optionalDependencies: - fsevents "~2.3.2" + "fsevents" "~2.3.2" -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== +"chrome-trace-event@^1.0.2": + "integrity" "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" + "resolved" "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + "version" "1.0.3" -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +"ci-info@^2.0.0": + "integrity" "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "resolved" "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" + "version" "2.0.0" -classnames@^2.2.6: - version "2.3.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" - integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== +"classnames@^2.2.6": + "integrity" "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + "resolved" "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz" + "version" "2.3.1" -clean-css@^5.1.5, clean-css@^5.2.2: - version "5.2.4" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" - integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== +"clean-css@^5.1.5", "clean-css@^5.2.2": + "integrity" "sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==" + "resolved" "https://registry.npmjs.org/clean-css/-/clean-css-5.2.4.tgz" + "version" "5.2.4" dependencies: - source-map "~0.6.0" + "source-map" "~0.6.0" -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +"clean-stack@^2.0.0": + "integrity" "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + "resolved" "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" + "version" "2.2.0" -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +"cli-boxes@^2.2.1": + "integrity" "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + "resolved" "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz" + "version" "2.2.1" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== +"clone-deep@^4.0.1": + "integrity" "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==" + "resolved" "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" + "version" "4.0.1" dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" + "is-plain-object" "^2.0.4" + "kind-of" "^6.0.2" + "shallow-clone" "^3.0.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= +"clone-response@^1.0.2": + "integrity" "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=" + "resolved" "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" + "version" "1.0.2" dependencies: - mimic-response "^1.0.0" + "mimic-response" "^1.0.0" -clsx@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" - integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +"clsx@^1.1.1": + "integrity" "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + "resolved" "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz" + "version" "1.1.1" -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== +"collapse-white-space@^1.0.2": + "integrity" "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" + "resolved" "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz" + "version" "1.0.6" -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== +"color-convert@^1.9.0": + "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + "version" "1.9.3" dependencies: - color-name "1.1.3" + "color-name" "1.1.3" -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== +"color-convert@^2.0.1": + "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + "version" "2.0.1" dependencies: - color-name "~1.1.4" + "color-name" "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +"color-name@~1.1.4": + "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + "version" "1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +"color-name@1.1.3": + "integrity" "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + "version" "1.1.3" -colord@^2.9.1: - version "2.9.2" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" - integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== +"colord@^2.9.1": + "integrity" "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==" + "resolved" "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz" + "version" "2.9.2" -colorette@^2.0.10: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +"colorette@^2.0.10": + "integrity" "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + "resolved" "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz" + "version" "2.0.16" -combine-promises@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" - integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== +"combine-promises@^1.1.0": + "integrity" "sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==" + "resolved" "https://registry.npmjs.org/combine-promises/-/combine-promises-1.1.0.tgz" + "version" "1.1.0" -comma-separated-tokens@^1.0.0: - version "1.0.8" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" - integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +"comma-separated-tokens@^1.0.0": + "integrity" "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + "resolved" "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz" + "version" "1.0.8" -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +"commander@^2.20.0": + "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + "version" "2.20.3" -commander@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +"commander@^5.1.0": + "integrity" "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + "resolved" "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + "version" "5.1.0" -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +"commander@^7.2.0": + "integrity" "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + "resolved" "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + "version" "7.2.0" -commander@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +"commander@^8.3.0": + "integrity" "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + "resolved" "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" + "version" "8.3.0" -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +"commondir@^1.0.1": + "integrity" "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "resolved" "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + "version" "1.0.1" -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== +"compressible@~2.0.16": + "integrity" "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==" + "resolved" "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" + "version" "2.0.18" dependencies: - mime-db ">= 1.43.0 < 2" + "mime-db" ">= 1.43.0 < 2" -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== +"compression@^1.7.4": + "integrity" "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==" + "resolved" "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" + "version" "1.7.4" dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" + "accepts" "~1.3.5" + "bytes" "3.0.0" + "compressible" "~2.0.16" + "debug" "2.6.9" + "on-headers" "~1.0.2" + "safe-buffer" "5.1.2" + "vary" "~1.1.2" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +"concat-map@0.0.1": + "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + "version" "0.0.1" -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== +"configstore@^5.0.1": + "integrity" "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==" + "resolved" "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz" + "version" "5.0.1" dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" + "dot-prop" "^5.2.0" + "graceful-fs" "^4.1.2" + "make-dir" "^3.0.0" + "unique-string" "^2.0.0" + "write-file-atomic" "^3.0.0" + "xdg-basedir" "^4.0.0" -connect-history-api-fallback@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" - integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== +"connect-history-api-fallback@^1.6.0": + "integrity" "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + "resolved" "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz" + "version" "1.6.0" -consola@^2.15.3: - version "2.15.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" - integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== +"consola@^2.15.3": + "integrity" "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "resolved" "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz" + "version" "2.15.3" -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= +"content-disposition@0.5.2": + "integrity" "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "resolved" "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" + "version" "0.5.2" -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== +"content-disposition@0.5.4": + "integrity" "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==" + "resolved" "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + "version" "0.5.4" dependencies: - safe-buffer "5.2.1" + "safe-buffer" "5.2.1" -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +"content-type@~1.0.4": + "integrity" "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "resolved" "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + "version" "1.0.4" -convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== +"convert-source-map@^1.7.0": + "integrity" "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==" + "resolved" "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + "version" "1.8.0" dependencies: - safe-buffer "~5.1.1" + "safe-buffer" "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= +"cookie-signature@1.0.6": + "integrity" "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "resolved" "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + "version" "1.0.6" -cookie@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +"cookie@0.4.2": + "integrity" "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "resolved" "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" + "version" "0.4.2" -copy-text-to-clipboard@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" - integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== +"copy-text-to-clipboard@^3.0.1": + "integrity" "sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==" + "resolved" "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz" + "version" "3.0.1" -copy-webpack-plugin@^10.2.0: - version "10.2.4" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" - integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== +"copy-webpack-plugin@^10.2.0": + "integrity" "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==" + "resolved" "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz" + "version" "10.2.4" dependencies: - fast-glob "^3.2.7" - glob-parent "^6.0.1" - globby "^12.0.2" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" + "fast-glob" "^3.2.7" + "glob-parent" "^6.0.1" + "globby" "^12.0.2" + "normalize-path" "^3.0.0" + "schema-utils" "^4.0.0" + "serialize-javascript" "^6.0.0" -core-js-compat@^3.20.2, core-js-compat@^3.21.0: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" - integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== +"core-js-compat@^3.20.2", "core-js-compat@^3.21.0": + "integrity" "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==" + "resolved" "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz" + "version" "3.21.1" dependencies: - browserslist "^4.19.1" - semver "7.0.0" + "browserslist" "^4.19.1" + "semver" "7.0.0" -core-js-pure@^3.20.2: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" - integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== +"core-js-pure@^3.20.2": + "integrity" "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==" + "resolved" "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz" + "version" "3.21.1" -core-js@^3.18.0: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" - integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== +"core-js@^3.18.0": + "integrity" "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==" + "resolved" "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz" + "version" "3.21.1" -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +"core-util-is@~1.0.0": + "integrity" "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "resolved" "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + "version" "1.0.3" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== +"cosmiconfig@^6.0.0": + "integrity" "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==" + "resolved" "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz" + "version" "6.0.0" dependencies: "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" + "import-fresh" "^3.1.0" + "parse-json" "^5.0.0" + "path-type" "^4.0.0" + "yaml" "^1.7.2" -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== +"cosmiconfig@^7.0.0", "cosmiconfig@^7.0.1": + "integrity" "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==" + "resolved" "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz" + "version" "7.0.1" dependencies: "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" + "import-fresh" "^3.2.1" + "parse-json" "^5.0.0" + "path-type" "^4.0.0" + "yaml" "^1.10.0" -cross-fetch@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== +"cross-fetch@^3.1.5": + "integrity" "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==" + "resolved" "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" + "version" "3.1.5" dependencies: - node-fetch "2.6.7" + "node-fetch" "2.6.7" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== +"cross-spawn@^7.0.3": + "integrity" "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==" + "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + "version" "7.0.3" dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" + "path-key" "^3.1.0" + "shebang-command" "^2.0.0" + "which" "^2.0.1" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +"crypto-random-string@^2.0.0": + "integrity" "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + "resolved" "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" + "version" "2.0.0" -css-declaration-sorter@^6.0.3: - version "6.1.4" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" - integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== +"css-declaration-sorter@^6.0.3": + "integrity" "sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw==" + "resolved" "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz" + "version" "6.1.4" dependencies: - timsort "^0.3.0" + "timsort" "^0.3.0" -css-loader@^6.5.1: - version "6.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.6.0.tgz#c792ad5510bd1712618b49381bd0310574fafbd3" - integrity sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg== +"css-loader@^6.5.1": + "integrity" "sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg==" + "resolved" "https://registry.npmjs.org/css-loader/-/css-loader-6.6.0.tgz" + "version" "6.6.0" dependencies: - icss-utils "^5.1.0" - postcss "^8.4.5" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.5" + "icss-utils" "^5.1.0" + "postcss" "^8.4.5" + "postcss-modules-extract-imports" "^3.0.0" + "postcss-modules-local-by-default" "^4.0.0" + "postcss-modules-scope" "^3.0.0" + "postcss-modules-values" "^4.0.0" + "postcss-value-parser" "^4.2.0" + "semver" "^7.3.5" -css-minimizer-webpack-plugin@^3.3.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" - integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== +"css-minimizer-webpack-plugin@^3.3.1": + "integrity" "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==" + "resolved" "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz" + "version" "3.4.1" dependencies: - cssnano "^5.0.6" - jest-worker "^27.0.2" - postcss "^8.3.5" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" + "cssnano" "^5.0.6" + "jest-worker" "^27.0.2" + "postcss" "^8.3.5" + "schema-utils" "^4.0.0" + "serialize-javascript" "^6.0.0" + "source-map" "^0.6.1" -css-select@^4.1.3: - version "4.2.1" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" - integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== +"css-select@^4.1.3": + "integrity" "sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==" + "resolved" "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz" + "version" "4.2.1" dependencies: - boolbase "^1.0.0" - css-what "^5.1.0" - domhandler "^4.3.0" - domutils "^2.8.0" - nth-check "^2.0.1" + "boolbase" "^1.0.0" + "css-what" "^5.1.0" + "domhandler" "^4.3.0" + "domutils" "^2.8.0" + "nth-check" "^2.0.1" -css-select@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= +"css-select@~1.2.0": + "integrity" "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=" + "resolved" "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz" + "version" "1.2.0" dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + "boolbase" "~1.0.0" + "css-what" "2.1" + "domutils" "1.5.1" + "nth-check" "~1.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== +"css-tree@^1.1.2", "css-tree@^1.1.3": + "integrity" "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==" + "resolved" "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" + "version" "1.1.3" dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" + "mdn-data" "2.0.14" + "source-map" "^0.6.1" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +"css-what@^5.0.1", "css-what@^5.1.0": + "integrity" "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==" + "resolved" "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz" + "version" "5.1.0" -css-what@^5.0.1, css-what@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== +"css-what@2.1": + "integrity" "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + "resolved" "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz" + "version" "2.1.3" -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +"cssesc@^3.0.0": + "integrity" "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + "resolved" "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + "version" "3.0.0" -cssnano-preset-advanced@^5.1.4: - version "5.1.12" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.12.tgz#11f5b0c4e3c32bcfd475465a283fa14dec8df972" - integrity sha512-5WWV9mbqVNwH4nRjs5UbhNl7eKo+16eYNzGogmz0Sa6iqWUeLdN8oo83WuTTqz5vjEKhTbRM5oX6WV1i6ees6g== +"cssnano-preset-advanced@^5.1.4": + "integrity" "sha512-5WWV9mbqVNwH4nRjs5UbhNl7eKo+16eYNzGogmz0Sa6iqWUeLdN8oo83WuTTqz5vjEKhTbRM5oX6WV1i6ees6g==" + "resolved" "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.12.tgz" + "version" "5.1.12" dependencies: - autoprefixer "^10.3.7" - cssnano-preset-default "^5.1.12" - postcss-discard-unused "^5.0.3" - postcss-merge-idents "^5.0.3" - postcss-reduce-idents "^5.0.3" - postcss-zindex "^5.0.2" + "autoprefixer" "^10.3.7" + "cssnano-preset-default" "^5.1.12" + "postcss-discard-unused" "^5.0.3" + "postcss-merge-idents" "^5.0.3" + "postcss-reduce-idents" "^5.0.3" + "postcss-zindex" "^5.0.2" -cssnano-preset-default@^5.1.12: - version "5.1.12" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.12.tgz#64e2ad8e27a279e1413d2d2383ef89a41c909be9" - integrity sha512-rO/JZYyjW1QNkWBxMGV28DW7d98UDLaF759frhli58QFehZ+D/LSmwQ2z/ylBAe2hUlsIWTq6NYGfQPq65EF9w== +"cssnano-preset-default@^5.1.12": + "integrity" "sha512-rO/JZYyjW1QNkWBxMGV28DW7d98UDLaF759frhli58QFehZ+D/LSmwQ2z/ylBAe2hUlsIWTq6NYGfQPq65EF9w==" + "resolved" "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.12.tgz" + "version" "5.1.12" dependencies: - css-declaration-sorter "^6.0.3" - cssnano-utils "^3.0.2" - postcss-calc "^8.2.0" - postcss-colormin "^5.2.5" - postcss-convert-values "^5.0.4" - postcss-discard-comments "^5.0.3" - postcss-discard-duplicates "^5.0.3" - postcss-discard-empty "^5.0.3" - postcss-discard-overridden "^5.0.4" - postcss-merge-longhand "^5.0.6" - postcss-merge-rules "^5.0.6" - postcss-minify-font-values "^5.0.4" - postcss-minify-gradients "^5.0.6" - postcss-minify-params "^5.0.5" - postcss-minify-selectors "^5.1.3" - postcss-normalize-charset "^5.0.3" - postcss-normalize-display-values "^5.0.3" - postcss-normalize-positions "^5.0.4" - postcss-normalize-repeat-style "^5.0.4" - postcss-normalize-string "^5.0.4" - postcss-normalize-timing-functions "^5.0.3" - postcss-normalize-unicode "^5.0.4" - postcss-normalize-url "^5.0.5" - postcss-normalize-whitespace "^5.0.4" - postcss-ordered-values "^5.0.5" - postcss-reduce-initial "^5.0.3" - postcss-reduce-transforms "^5.0.4" - postcss-svgo "^5.0.4" - postcss-unique-selectors "^5.0.4" + "css-declaration-sorter" "^6.0.3" + "cssnano-utils" "^3.0.2" + "postcss-calc" "^8.2.0" + "postcss-colormin" "^5.2.5" + "postcss-convert-values" "^5.0.4" + "postcss-discard-comments" "^5.0.3" + "postcss-discard-duplicates" "^5.0.3" + "postcss-discard-empty" "^5.0.3" + "postcss-discard-overridden" "^5.0.4" + "postcss-merge-longhand" "^5.0.6" + "postcss-merge-rules" "^5.0.6" + "postcss-minify-font-values" "^5.0.4" + "postcss-minify-gradients" "^5.0.6" + "postcss-minify-params" "^5.0.5" + "postcss-minify-selectors" "^5.1.3" + "postcss-normalize-charset" "^5.0.3" + "postcss-normalize-display-values" "^5.0.3" + "postcss-normalize-positions" "^5.0.4" + "postcss-normalize-repeat-style" "^5.0.4" + "postcss-normalize-string" "^5.0.4" + "postcss-normalize-timing-functions" "^5.0.3" + "postcss-normalize-unicode" "^5.0.4" + "postcss-normalize-url" "^5.0.5" + "postcss-normalize-whitespace" "^5.0.4" + "postcss-ordered-values" "^5.0.5" + "postcss-reduce-initial" "^5.0.3" + "postcss-reduce-transforms" "^5.0.4" + "postcss-svgo" "^5.0.4" + "postcss-unique-selectors" "^5.0.4" -cssnano-utils@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.2.tgz#d82b4991a27ba6fec644b39bab35fe027137f516" - integrity sha512-KhprijuQv2sP4kT92sSQwhlK3SJTbDIsxcfIEySB0O+3m9esFOai7dP9bMx5enHAh2MwarVIcnwiWoOm01RIbQ== +"cssnano-utils@^3.0.2": + "integrity" "sha512-KhprijuQv2sP4kT92sSQwhlK3SJTbDIsxcfIEySB0O+3m9esFOai7dP9bMx5enHAh2MwarVIcnwiWoOm01RIbQ==" + "resolved" "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.0.2.tgz" + "version" "3.0.2" -cssnano@^5.0.6, cssnano@^5.0.8: - version "5.0.17" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.17.tgz#ff45713c05cfc780a1aeb3e663b6f224d091cabf" - integrity sha512-fmjLP7k8kL18xSspeXTzRhaFtRI7DL9b8IcXR80JgtnWBpvAzHT7sCR/6qdn0tnxIaINUN6OEQu83wF57Gs3Xw== +"cssnano@^5.0.6", "cssnano@^5.0.8": + "integrity" "sha512-fmjLP7k8kL18xSspeXTzRhaFtRI7DL9b8IcXR80JgtnWBpvAzHT7sCR/6qdn0tnxIaINUN6OEQu83wF57Gs3Xw==" + "resolved" "https://registry.npmjs.org/cssnano/-/cssnano-5.0.17.tgz" + "version" "5.0.17" dependencies: - cssnano-preset-default "^5.1.12" - lilconfig "^2.0.3" - yaml "^1.10.2" + "cssnano-preset-default" "^5.1.12" + "lilconfig" "^2.0.3" + "yaml" "^1.10.2" -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== +"csso@^4.2.0": + "integrity" "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==" + "resolved" "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz" + "version" "4.2.0" dependencies: - css-tree "^1.1.2" + "css-tree" "^1.1.2" -csstype@^3.0.2: - version "3.0.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" - integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== +"csstype@^3.0.2": + "integrity" "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "resolved" "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz" + "version" "3.0.10" -debug@2.6.9, debug@^2.6.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== +"debug@^2.6.0", "debug@2.6.9": + "integrity" "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" + "resolved" "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + "version" "2.6.9" dependencies: - ms "2.0.0" + "ms" "2.0.0" -debug@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== +"debug@^3.1.1": + "integrity" "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==" + "resolved" "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + "version" "3.2.7" dependencies: - ms "^2.1.1" + "ms" "^2.1.1" -debug@^4.1.0, debug@^4.1.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +"debug@^4.1.0": + "integrity" "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" + "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" + "version" "4.3.3" dependencies: - ms "2.1.2" + "ms" "2.1.2" -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= +"debug@^4.1.1": + "integrity" "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==" + "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" + "version" "4.3.3" dependencies: - mimic-response "^1.0.0" + "ms" "2.1.2" -deep-equal@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== +"decompress-response@^3.3.0": + "integrity" "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=" + "resolved" "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz" + "version" "3.3.0" dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" + "mimic-response" "^1.0.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deepmerge@^1.3.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" - integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== +"deep-equal@^1.0.1": + "integrity" "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==" + "resolved" "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz" + "version" "1.1.1" dependencies: - execa "^5.0.0" + "is-arguments" "^1.0.4" + "is-date-object" "^1.0.1" + "is-regex" "^1.0.4" + "object-is" "^1.0.1" + "object-keys" "^1.1.1" + "regexp.prototype.flags" "^1.2.0" -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +"deep-extend@^0.6.0": + "integrity" "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "resolved" "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + "version" "0.6.0" -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== +"deepmerge@^1.3.2": + "integrity" "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==" + "resolved" "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz" + "version" "1.5.2" -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +"deepmerge@^4.2.2": + "integrity" "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "resolved" "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + "version" "4.2.2" + +"default-gateway@^6.0.3": + "integrity" "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==" + "resolved" "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz" + "version" "6.0.3" dependencies: - object-keys "^1.0.12" + "execa" "^5.0.0" -del@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" - integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== +"defer-to-connect@^1.0.1": + "integrity" "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "resolved" "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz" + "version" "1.1.3" + +"define-lazy-prop@^2.0.0": + "integrity" "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + "resolved" "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" + "version" "2.0.0" + +"define-properties@^1.1.3": + "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" + "version" "1.1.3" dependencies: - globby "^11.0.1" - graceful-fs "^4.2.4" - is-glob "^4.0.1" - is-path-cwd "^2.2.0" - is-path-inside "^3.0.2" - p-map "^4.0.0" - rimraf "^3.0.2" - slash "^3.0.0" + "object-keys" "^1.0.12" -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detab@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" - integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== +"del@^6.0.0": + "integrity" "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==" + "resolved" "https://registry.npmjs.org/del/-/del-6.0.0.tgz" + "version" "6.0.0" dependencies: - repeat-string "^1.5.4" + "globby" "^11.0.1" + "graceful-fs" "^4.2.4" + "is-glob" "^4.0.1" + "is-path-cwd" "^2.2.0" + "is-path-inside" "^3.0.2" + "p-map" "^4.0.0" + "rimraf" "^3.0.2" + "slash" "^3.0.0" -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +"depd@~1.1.2": + "integrity" "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "resolved" "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + "version" "1.1.2" -detect-port-alt@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== +"destroy@~1.0.4": + "integrity" "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "resolved" "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + "version" "1.0.4" + +"detab@2.0.4": + "integrity" "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==" + "resolved" "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz" + "version" "2.0.4" dependencies: - address "^1.0.1" - debug "^2.6.0" + "repeat-string" "^1.5.4" -detect-port@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1" - integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ== +"detect-node@^2.0.4": + "integrity" "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "resolved" "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" + "version" "2.1.0" + +"detect-port-alt@^1.1.6": + "integrity" "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==" + "resolved" "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz" + "version" "1.1.6" dependencies: - address "^1.0.1" - debug "^2.6.0" + "address" "^1.0.1" + "debug" "^2.6.0" -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== +"detect-port@^1.3.0": + "integrity" "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==" + "resolved" "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz" + "version" "1.3.0" dependencies: - path-type "^4.0.0" + "address" "^1.0.1" + "debug" "^2.6.0" -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" - integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== +"dir-glob@^3.0.1": + "integrity" "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==" + "resolved" "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + "version" "3.0.1" dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" + "path-type" "^4.0.0" -dns-txt@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= +"dns-equal@^1.0.0": + "integrity" "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + "resolved" "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" + "version" "1.0.0" + +"dns-packet@^1.3.1": + "integrity" "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==" + "resolved" "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz" + "version" "1.3.4" dependencies: - buffer-indexof "^1.0.0" + "ip" "^1.1.0" + "safe-buffer" "^5.0.1" -dom-converter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== +"dns-txt@^2.0.2": + "integrity" "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=" + "resolved" "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz" + "version" "2.0.2" dependencies: - utila "~0.4" + "buffer-indexof" "^1.0.0" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== +"dom-converter@^0.2.0": + "integrity" "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==" + "resolved" "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" + "version" "0.2.0" dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" + "utila" "~0.4" -dom-serializer@^1.0.1, dom-serializer@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" - integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== +"dom-serializer@^1.0.1": + "integrity" "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==" + "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" + "version" "1.3.2" dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" + "domelementtype" "^2.0.1" + "domhandler" "^4.2.0" + "entities" "^2.0.0" -dom-serializer@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== +"dom-serializer@^1.3.2": + "integrity" "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==" + "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" + "version" "1.3.2" dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" + "domelementtype" "^2.0.1" + "domhandler" "^4.2.0" + "entities" "^2.0.0" -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== - -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== +"dom-serializer@~0.1.0": + "integrity" "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==" + "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz" + "version" "0.1.1" dependencies: - domelementtype "1" + "domelementtype" "^1.3.0" + "entities" "^1.1.1" -domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" - integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== +"dom-serializer@0": + "integrity" "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==" + "resolved" "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz" + "version" "0.2.2" dependencies: - domelementtype "^2.2.0" + "domelementtype" "^2.0.1" + "entities" "^2.0.0" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= +"domelementtype@^1.3.0", "domelementtype@^1.3.1", "domelementtype@1": + "integrity" "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + "resolved" "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" + "version" "1.3.1" + +"domelementtype@^2.0.1", "domelementtype@^2.2.0": + "integrity" "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + "resolved" "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" + "version" "2.2.0" + +"domhandler@^2.3.0": + "integrity" "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==" + "resolved" "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz" + "version" "2.4.2" dependencies: - dom-serializer "0" - domelementtype "1" + "domelementtype" "1" -domutils@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== +"domhandler@^4.0.0", "domhandler@^4.2.0", "domhandler@^4.3.0": + "integrity" "sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==" + "resolved" "https://registry.npmjs.org/domhandler/-/domhandler-4.3.0.tgz" + "version" "4.3.0" dependencies: - dom-serializer "0" - domelementtype "1" + "domelementtype" "^2.2.0" -domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== +"domutils@^1.5.1": + "integrity" "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==" + "resolved" "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz" + "version" "1.7.0" dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" + "dom-serializer" "0" + "domelementtype" "1" -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== +"domutils@^2.5.2", "domutils@^2.7.0", "domutils@^2.8.0": + "integrity" "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==" + "resolved" "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + "version" "2.8.0" dependencies: - no-case "^3.0.4" - tslib "^2.0.3" + "dom-serializer" "^1.0.1" + "domelementtype" "^2.2.0" + "domhandler" "^4.2.0" -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== +"domutils@1.5.1": + "integrity" "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=" + "resolved" "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz" + "version" "1.5.1" dependencies: - is-obj "^2.0.0" + "dom-serializer" "0" + "domelementtype" "1" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -duplexer@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.4.17: - version "1.4.71" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz#17056914465da0890ce00351a3b946fd4cd51ff6" - integrity sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -emoticon@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f" - integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== +"dot-case@^3.0.4": + "integrity" "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==" + "resolved" "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" + "version" "3.0.4" dependencies: - once "^1.4.0" + "no-case" "^3.0.4" + "tslib" "^2.0.3" -enhanced-resolve@^5.8.3: - version "5.9.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee" - integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA== +"dot-prop@^5.2.0": + "integrity" "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==" + "resolved" "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" + "version" "5.3.0" dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" + "is-obj" "^2.0.0" -entities@^1.1.1, entities@~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +"duplexer@^0.1.2": + "integrity" "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + "resolved" "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" + "version" "0.1.2" -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +"duplexer3@^0.1.4": + "integrity" "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + "resolved" "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" + "version" "0.1.4" -entities@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" - integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== +"ee-first@1.1.1": + "integrity" "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "resolved" "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + "version" "1.1.1" -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== +"electron-to-chromium@^1.4.284": + "integrity" "sha512-DE9tTy2PNmy1v55AZAO542ui+MLC2cvINMK4P2LXGsJdput/ThVG9t+QGecPuAZZSgC8XoI+Jh9M1OG9IoNSCw==" + "resolved" "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.328.tgz" + "version" "1.4.328" + +"emoji-regex@^8.0.0": + "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + "version" "8.0.0" + +"emojis-list@^3.0.0": + "integrity" "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + "resolved" "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" + "version" "3.0.0" + +"emoticon@^3.2.0": + "integrity" "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" + "resolved" "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz" + "version" "3.2.0" + +"encodeurl@~1.0.2": + "integrity" "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "resolved" "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + "version" "1.0.2" + +"end-of-stream@^1.1.0": + "integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==" + "resolved" "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + "version" "1.4.4" dependencies: - is-arrayish "^0.2.1" + "once" "^1.4.0" -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-html@^1.0.3, escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== +"enhanced-resolve@^5.8.3": + "integrity" "sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA==" + "resolved" "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz" + "version" "5.9.0" dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" + "graceful-fs" "^4.2.4" + "tapable" "^2.2.0" -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +"entities@^1.1.1", "entities@~1.1.1": + "integrity" "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "resolved" "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz" + "version" "1.1.2" -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== +"entities@^2.0.0": + "integrity" "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + "resolved" "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + "version" "2.2.0" + +"entities@^3.0.1": + "integrity" "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + "resolved" "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz" + "version" "3.0.1" + +"error-ex@^1.3.1": + "integrity" "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==" + "resolved" "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + "version" "1.3.2" dependencies: - estraverse "^5.2.0" + "is-arrayish" "^0.2.1" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== +"es-module-lexer@^0.9.0": + "integrity" "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + "resolved" "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz" + "version" "0.9.3" -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +"escalade@^3.1.1": + "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + "version" "3.1.1" -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +"escape-goat@^2.0.0": + "integrity" "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + "resolved" "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" + "version" "2.1.1" -eta@^1.12.3: - version "1.12.3" - resolved "https://registry.yarnpkg.com/eta/-/eta-1.12.3.tgz#2982d08adfbef39f9fa50e2fbd42d7337e7338b1" - integrity sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg== +"escape-html@^1.0.3", "escape-html@~1.0.3": + "integrity" "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "resolved" "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + "version" "1.0.3" -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +"escape-string-regexp@^1.0.5": + "integrity" "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "version" "1.0.5" -eval@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc" - integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ== +"escape-string-regexp@^4.0.0": + "integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + "version" "4.0.0" + +"eslint-scope@5.1.1": + "integrity" "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==" + "resolved" "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + "version" "5.1.1" dependencies: - require-like ">= 0.1.1" + "esrecurse" "^4.3.0" + "estraverse" "^4.1.1" -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +"esprima@^4.0.0": + "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + "version" "4.0.1" -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== +"esrecurse@^4.3.0": + "integrity" "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==" + "resolved" "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + "version" "4.3.0" dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" + "estraverse" "^5.2.0" -express@^4.17.1: - version "4.17.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" - integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== +"estraverse@^4.1.1": + "integrity" "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + "version" "4.3.0" + +"estraverse@^5.2.0": + "integrity" "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "resolved" "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + "version" "5.3.0" + +"esutils@^2.0.2": + "integrity" "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "resolved" "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + "version" "2.0.3" + +"eta@^1.12.3": + "integrity" "sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg==" + "resolved" "https://registry.npmjs.org/eta/-/eta-1.12.3.tgz" + "version" "1.12.3" + +"etag@~1.8.1": + "integrity" "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "resolved" "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + "version" "1.8.1" + +"eval@^0.1.4": + "integrity" "sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ==" + "resolved" "https://registry.npmjs.org/eval/-/eval-0.1.6.tgz" + "version" "0.1.6" dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.19.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.4.2" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.9.7" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" - setprototypeof "1.2.0" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" + "require-like" ">= 0.1.1" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= +"eventemitter3@^4.0.0": + "integrity" "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "resolved" "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" + "version" "4.0.7" + +"events@^3.2.0": + "integrity" "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + "resolved" "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + "version" "3.3.0" + +"execa@^5.0.0": + "integrity" "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==" + "resolved" "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + "version" "5.1.1" dependencies: - is-extendable "^0.1.0" + "cross-spawn" "^7.0.3" + "get-stream" "^6.0.0" + "human-signals" "^2.1.0" + "is-stream" "^2.0.0" + "merge-stream" "^2.0.0" + "npm-run-path" "^4.0.1" + "onetime" "^5.1.2" + "signal-exit" "^3.0.3" + "strip-final-newline" "^2.0.0" -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +"express@^4.17.1": + "integrity" "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==" + "resolved" "https://registry.npmjs.org/express/-/express-4.17.3.tgz" + "version" "4.17.3" + dependencies: + "accepts" "~1.3.8" + "array-flatten" "1.1.1" + "body-parser" "1.19.2" + "content-disposition" "0.5.4" + "content-type" "~1.0.4" + "cookie" "0.4.2" + "cookie-signature" "1.0.6" + "debug" "2.6.9" + "depd" "~1.1.2" + "encodeurl" "~1.0.2" + "escape-html" "~1.0.3" + "etag" "~1.8.1" + "finalhandler" "~1.1.2" + "fresh" "0.5.2" + "merge-descriptors" "1.0.1" + "methods" "~1.1.2" + "on-finished" "~2.3.0" + "parseurl" "~1.3.3" + "path-to-regexp" "0.1.7" + "proxy-addr" "~2.0.7" + "qs" "6.9.7" + "range-parser" "~1.2.1" + "safe-buffer" "5.2.1" + "send" "0.17.2" + "serve-static" "1.14.2" + "setprototypeof" "1.2.0" + "statuses" "~1.5.0" + "type-is" "~1.6.18" + "utils-merge" "1.0.1" + "vary" "~1.1.2" -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +"extend-shallow@^2.0.1": + "integrity" "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=" + "resolved" "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" + "version" "2.0.1" + dependencies: + "is-extendable" "^0.1.0" -fast-glob@^3.2.7, fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== +"extend@^3.0.0": + "integrity" "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "resolved" "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" + "version" "3.0.2" + +"fast-deep-equal@^3.1.1", "fast-deep-equal@^3.1.3": + "integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + "version" "3.1.3" + +"fast-glob@^3.2.7", "fast-glob@^3.2.9": + "integrity" "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==" + "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz" + "version" "3.2.11" dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" + "glob-parent" "^5.1.2" + "merge2" "^1.3.0" + "micromatch" "^4.0.4" -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +"fast-json-stable-stringify@^2.0.0": + "integrity" "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "resolved" "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + "version" "2.1.0" -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= +"fast-url-parser@1.1.3": + "integrity" "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=" + "resolved" "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz" + "version" "1.1.3" dependencies: - punycode "^1.3.2" + "punycode" "^1.3.2" -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== +"fastq@^1.6.0": + "integrity" "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==" + "resolved" "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz" + "version" "1.13.0" dependencies: - reusify "^1.0.4" + "reusify" "^1.0.4" -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== +"faye-websocket@^0.11.3": + "integrity" "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==" + "resolved" "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" + "version" "0.11.4" dependencies: - websocket-driver ">=0.5.1" + "websocket-driver" ">=0.5.1" -fbemitter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" - integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== +"fbemitter@^3.0.0": + "integrity" "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==" + "resolved" "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz" + "version" "3.0.0" dependencies: - fbjs "^3.0.0" + "fbjs" "^3.0.0" -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== +"fbjs-css-vars@^1.0.0": + "integrity" "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + "resolved" "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz" + "version" "1.0.2" -fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" - integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== +"fbjs@^3.0.0", "fbjs@^3.0.1": + "integrity" "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==" + "resolved" "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz" + "version" "3.0.4" dependencies: - cross-fetch "^3.1.5" - fbjs-css-vars "^1.0.0" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.30" + "cross-fetch" "^3.1.5" + "fbjs-css-vars" "^1.0.0" + "loose-envify" "^1.0.0" + "object-assign" "^4.1.0" + "promise" "^7.1.1" + "setimmediate" "^1.0.5" + "ua-parser-js" "^0.7.30" -feed@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" - integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== +"feed@^4.2.2": + "integrity" "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==" + "resolved" "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz" + "version" "4.2.2" dependencies: - xml-js "^1.6.11" + "xml-js" "^1.6.11" -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== +"file-loader@*", "file-loader@^6.2.0": + "integrity" "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==" + "resolved" "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz" + "version" "6.2.0" dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" + "loader-utils" "^2.0.0" + "schema-utils" "^3.0.0" -filesize@^8.0.6: - version "8.0.7" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" - integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== +"filesize@^8.0.6": + "integrity" "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" + "resolved" "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz" + "version" "8.0.7" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +"fill-range@^7.0.1": + "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + "version" "7.0.1" dependencies: - to-regex-range "^5.0.1" + "to-regex-range" "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== +"finalhandler@~1.1.2": + "integrity" "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==" + "resolved" "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" + "version" "1.1.2" dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" + "debug" "2.6.9" + "encodeurl" "~1.0.2" + "escape-html" "~1.0.3" + "on-finished" "~2.3.0" + "parseurl" "~1.3.3" + "statuses" "~1.5.0" + "unpipe" "~1.0.0" -find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== +"find-cache-dir@^3.3.1": + "integrity" "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==" + "resolved" "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz" + "version" "3.3.2" dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" + "commondir" "^1.0.1" + "make-dir" "^3.0.2" + "pkg-dir" "^4.1.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +"find-up@^3.0.0": + "integrity" "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" + "version" "3.0.0" dependencies: - locate-path "^3.0.0" + "locate-path" "^3.0.0" -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== +"find-up@^4.0.0": + "integrity" "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + "version" "4.1.0" dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" + "locate-path" "^5.0.0" + "path-exists" "^4.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== +"find-up@^5.0.0": + "integrity" "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + "version" "5.0.0" dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" + "locate-path" "^6.0.0" + "path-exists" "^4.0.0" -flux@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" - integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== +"flux@^4.0.1": + "integrity" "sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==" + "resolved" "https://registry.npmjs.org/flux/-/flux-4.0.3.tgz" + "version" "4.0.3" dependencies: - fbemitter "^3.0.0" - fbjs "^3.0.1" + "fbemitter" "^3.0.0" + "fbjs" "^3.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.14.7: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +"follow-redirects@^1.0.0", "follow-redirects@^1.14.7": + "integrity" "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz" + "version" "1.14.9" -fork-ts-checker-webpack-plugin@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" - integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== +"fork-ts-checker-webpack-plugin@^6.5.0": + "integrity" "sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw==" + "resolved" "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz" + "version" "6.5.0" dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" + "chalk" "^4.1.0" + "chokidar" "^3.4.2" + "cosmiconfig" "^6.0.0" + "deepmerge" "^4.2.2" + "fs-extra" "^9.0.0" + "glob" "^7.1.6" + "memfs" "^3.1.2" + "minimatch" "^3.0.4" + "schema-utils" "2.7.0" + "semver" "^7.3.2" + "tapable" "^1.0.0" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +"forwarded@0.2.0": + "integrity" "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + "resolved" "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + "version" "0.2.0" -fraction.js@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.3.tgz#be65b0f20762ef27e1e793860bc2dfb716e99e65" - integrity sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg== +"fraction.js@^4.1.2": + "integrity" "sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg==" + "resolved" "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.3.tgz" + "version" "4.1.3" -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +"fresh@0.5.2": + "integrity" "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "resolved" "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + "version" "0.5.2" -fs-extra@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== +"fs-extra@^10.0.0": + "integrity" "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==" + "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz" + "version" "10.0.0" dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" + "graceful-fs" "^4.2.0" + "jsonfile" "^6.0.1" + "universalify" "^2.0.0" -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== +"fs-extra@^9.0.0": + "integrity" "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==" + "resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + "version" "9.1.0" dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" + "at-least-node" "^1.0.0" + "graceful-fs" "^4.2.0" + "jsonfile" "^6.0.1" + "universalify" "^2.0.0" -fs-monkey@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +"fs-monkey@1.0.3": + "integrity" "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==" + "resolved" "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz" + "version" "1.0.3" -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +"fs.realpath@^1.0.0": + "integrity" "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + "version" "1.0.0" -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +"function-bind@^1.1.1": + "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + "version" "1.1.1" -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +"gensync@^1.0.0-beta.1", "gensync@^1.0.0-beta.2": + "integrity" "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + "resolved" "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + "version" "1.0.0-beta.2" -gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== +"get-intrinsic@^1.0.2": + "integrity" "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==" + "resolved" "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" + "version" "1.1.1" dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" + "function-bind" "^1.1.1" + "has" "^1.0.3" + "has-symbols" "^1.0.1" -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +"get-own-enumerable-property-symbols@^3.0.0": + "integrity" "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + "resolved" "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" + "version" "3.0.2" -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== +"get-stream@^4.1.0": + "integrity" "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==" + "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" + "version" "4.1.0" dependencies: - pump "^3.0.0" + "pump" "^3.0.0" -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== +"get-stream@^5.1.0": + "integrity" "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==" + "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + "version" "5.2.0" dependencies: - pump "^3.0.0" + "pump" "^3.0.0" -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +"get-stream@^6.0.0": + "integrity" "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + "version" "6.0.1" -github-slugger@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e" - integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ== +"github-slugger@^1.4.0": + "integrity" "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==" + "resolved" "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz" + "version" "1.4.0" -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== +"glob-parent@^5.1.2", "glob-parent@~5.1.2": + "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + "version" "5.1.2" dependencies: - is-glob "^4.0.1" + "is-glob" "^4.0.1" -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== +"glob-parent@^6.0.1": + "integrity" "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + "version" "6.0.2" dependencies: - is-glob "^4.0.3" + "is-glob" "^4.0.3" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +"glob-to-regexp@^0.4.1": + "integrity" "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "resolved" "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + "version" "0.4.1" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +"glob@^7.0.0", "glob@^7.1.3", "glob@^7.1.6": + "integrity" "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==" + "resolved" "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + "version" "7.2.0" dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" + "fs.realpath" "^1.0.0" + "inflight" "^1.0.4" + "inherits" "2" + "minimatch" "^3.0.4" + "once" "^1.3.0" + "path-is-absolute" "^1.0.0" -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== +"global-dirs@^3.0.0": + "integrity" "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==" + "resolved" "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz" + "version" "3.0.0" dependencies: - ini "2.0.0" + "ini" "2.0.0" -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== +"global-modules@^2.0.0": + "integrity" "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==" + "resolved" "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz" + "version" "2.0.0" dependencies: - global-prefix "^3.0.0" + "global-prefix" "^3.0.0" -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== +"global-prefix@^3.0.0": + "integrity" "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==" + "resolved" "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz" + "version" "3.0.0" dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" + "ini" "^1.3.5" + "kind-of" "^6.0.2" + "which" "^1.3.1" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +"globals@^11.1.0": + "integrity" "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "resolved" "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + "version" "11.12.0" -globby@^11.0.1, globby@^11.0.2, globby@^11.0.4: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== +"globby@^11.0.1", "globby@^11.0.2", "globby@^11.0.4": + "integrity" "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==" + "resolved" "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + "version" "11.1.0" dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" + "array-union" "^2.1.0" + "dir-glob" "^3.0.1" + "fast-glob" "^3.2.9" + "ignore" "^5.2.0" + "merge2" "^1.4.1" + "slash" "^3.0.0" -globby@^12.0.2: - version "12.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" - integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== +"globby@^12.0.2": + "integrity" "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==" + "resolved" "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz" + "version" "12.2.0" dependencies: - array-union "^3.0.1" - dir-glob "^3.0.1" - fast-glob "^3.2.7" - ignore "^5.1.9" - merge2 "^1.4.1" - slash "^4.0.0" + "array-union" "^3.0.1" + "dir-glob" "^3.0.1" + "fast-glob" "^3.2.7" + "ignore" "^5.1.9" + "merge2" "^1.4.1" + "slash" "^4.0.0" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== +"got@^9.6.0": + "integrity" "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==" + "resolved" "https://registry.npmjs.org/got/-/got-9.6.0.tgz" + "version" "9.6.0" dependencies: "@sindresorhus/is" "^0.14.0" "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" + "cacheable-request" "^6.0.0" + "decompress-response" "^3.3.0" + "duplexer3" "^0.1.4" + "get-stream" "^4.1.0" + "lowercase-keys" "^1.0.1" + "mimic-response" "^1.0.1" + "p-cancelable" "^1.0.0" + "to-readable-stream" "^1.0.0" + "url-parse-lax" "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +"graceful-fs@^4.1.2", "graceful-fs@^4.1.6", "graceful-fs@^4.2.0", "graceful-fs@^4.2.4", "graceful-fs@^4.2.6", "graceful-fs@^4.2.9": + "integrity" "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + "resolved" "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" + "version" "4.2.9" -gray-matter@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" - integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== +"gray-matter@^4.0.3": + "integrity" "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==" + "resolved" "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz" + "version" "4.0.3" dependencies: - js-yaml "^3.13.1" - kind-of "^6.0.2" - section-matter "^1.0.0" - strip-bom-string "^1.0.0" + "js-yaml" "^3.13.1" + "kind-of" "^6.0.2" + "section-matter" "^1.0.0" + "strip-bom-string" "^1.0.0" -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== +"gzip-size@^6.0.0": + "integrity" "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==" + "resolved" "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz" + "version" "6.0.0" dependencies: - duplexer "^0.1.2" + "duplexer" "^0.1.2" -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== +"handle-thing@^2.0.0": + "integrity" "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + "resolved" "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" + "version" "2.0.1" -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +"has-flag@^3.0.0": + "integrity" "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "version" "3.0.0" -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +"has-flag@^4.0.0": + "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + "version" "4.0.0" -has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +"has-symbols@^1.0.1", "has-symbols@^1.0.2": + "integrity" "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" + "version" "1.0.2" -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== +"has-tostringtag@^1.0.0": + "integrity" "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==" + "resolved" "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + "version" "1.0.0" dependencies: - has-symbols "^1.0.2" + "has-symbols" "^1.0.2" -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== +"has-yarn@^2.1.0": + "integrity" "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + "resolved" "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz" + "version" "2.1.0" -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== +"has@^1.0.3": + "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" + "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + "version" "1.0.3" dependencies: - function-bind "^1.1.1" + "function-bind" "^1.1.1" -hast-to-hyperscript@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" - integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== +"hast-to-hyperscript@^9.0.0": + "integrity" "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==" + "resolved" "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz" + "version" "9.0.1" dependencies: "@types/unist" "^2.0.3" - comma-separated-tokens "^1.0.0" - property-information "^5.3.0" - space-separated-tokens "^1.0.0" - style-to-object "^0.3.0" - unist-util-is "^4.0.0" - web-namespaces "^1.0.0" + "comma-separated-tokens" "^1.0.0" + "property-information" "^5.3.0" + "space-separated-tokens" "^1.0.0" + "style-to-object" "^0.3.0" + "unist-util-is" "^4.0.0" + "web-namespaces" "^1.0.0" -hast-util-from-parse5@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz#3089dc0ee2ccf6ec8bc416919b51a54a589e097c" - integrity sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA== +"hast-util-from-parse5@^5.0.0": + "integrity" "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==" + "resolved" "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz" + "version" "5.0.3" dependencies: - ccount "^1.0.3" - hastscript "^5.0.0" - property-information "^5.0.0" - web-namespaces "^1.1.2" - xtend "^4.0.1" + "ccount" "^1.0.3" + "hastscript" "^5.0.0" + "property-information" "^5.0.0" + "web-namespaces" "^1.1.2" + "xtend" "^4.0.1" -hast-util-from-parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" - integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== +"hast-util-from-parse5@^6.0.0": + "integrity" "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==" + "resolved" "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz" + "version" "6.0.1" dependencies: "@types/parse5" "^5.0.0" - hastscript "^6.0.0" - property-information "^5.0.0" - vfile "^4.0.0" - vfile-location "^3.2.0" - web-namespaces "^1.0.0" + "hastscript" "^6.0.0" + "property-information" "^5.0.0" + "vfile" "^4.0.0" + "vfile-location" "^3.2.0" + "web-namespaces" "^1.0.0" -hast-util-parse-selector@^2.0.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" - integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== +"hast-util-parse-selector@^2.0.0": + "integrity" "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" + "resolved" "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz" + "version" "2.2.5" -hast-util-raw@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" - integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== +"hast-util-raw@6.0.1": + "integrity" "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==" + "resolved" "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz" + "version" "6.0.1" dependencies: "@types/hast" "^2.0.0" - hast-util-from-parse5 "^6.0.0" - hast-util-to-parse5 "^6.0.0" - html-void-elements "^1.0.0" - parse5 "^6.0.0" - unist-util-position "^3.0.0" - vfile "^4.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" + "hast-util-from-parse5" "^6.0.0" + "hast-util-to-parse5" "^6.0.0" + "html-void-elements" "^1.0.0" + "parse5" "^6.0.0" + "unist-util-position" "^3.0.0" + "vfile" "^4.0.0" + "web-namespaces" "^1.0.0" + "xtend" "^4.0.0" + "zwitch" "^1.0.0" -hast-util-to-parse5@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" - integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== +"hast-util-to-parse5@^6.0.0": + "integrity" "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==" + "resolved" "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz" + "version" "6.0.0" dependencies: - hast-to-hyperscript "^9.0.0" - property-information "^5.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" + "hast-to-hyperscript" "^9.0.0" + "property-information" "^5.0.0" + "web-namespaces" "^1.0.0" + "xtend" "^4.0.0" + "zwitch" "^1.0.0" -hastscript@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.1.2.tgz#bde2c2e56d04c62dd24e8c5df288d050a355fb8a" - integrity sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ== +"hastscript@^5.0.0": + "integrity" "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==" + "resolved" "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz" + "version" "5.1.2" dependencies: - comma-separated-tokens "^1.0.0" - hast-util-parse-selector "^2.0.0" - property-information "^5.0.0" - space-separated-tokens "^1.0.0" + "comma-separated-tokens" "^1.0.0" + "hast-util-parse-selector" "^2.0.0" + "property-information" "^5.0.0" + "space-separated-tokens" "^1.0.0" -hastscript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" - integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== +"hastscript@^6.0.0": + "integrity" "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==" + "resolved" "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz" + "version" "6.0.0" dependencies: "@types/hast" "^2.0.0" - comma-separated-tokens "^1.0.0" - hast-util-parse-selector "^2.0.0" - property-information "^5.0.0" - space-separated-tokens "^1.0.0" + "comma-separated-tokens" "^1.0.0" + "hast-util-parse-selector" "^2.0.0" + "property-information" "^5.0.0" + "space-separated-tokens" "^1.0.0" -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +"he@^1.2.0": + "integrity" "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "resolved" "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + "version" "1.2.0" -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== +"history@^4.9.0": + "integrity" "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==" + "resolved" "https://registry.npmjs.org/history/-/history-4.10.1.tgz" + "version" "4.10.1" dependencies: "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" + "loose-envify" "^1.2.0" + "resolve-pathname" "^3.0.0" + "tiny-invariant" "^1.0.2" + "tiny-warning" "^1.0.0" + "value-equal" "^1.0.1" -hoist-non-react-statics@^3.1.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== +"hoist-non-react-statics@^3.1.0": + "integrity" "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==" + "resolved" "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" + "version" "3.3.2" dependencies: - react-is "^16.7.0" + "react-is" "^16.7.0" -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= +"hpack.js@^2.1.6": + "integrity" "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=" + "resolved" "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz" + "version" "2.1.6" dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" + "inherits" "^2.0.1" + "obuf" "^1.0.0" + "readable-stream" "^2.0.1" + "wbuf" "^1.1.0" -html-entities@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" - integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== +"html-entities@^2.3.2": + "integrity" "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==" + "resolved" "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz" + "version" "2.3.2" -html-minifier-terser@^6.0.2: - version "6.1.0" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" - integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== +"html-minifier-terser@^6.0.2": + "integrity" "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==" + "resolved" "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz" + "version" "6.1.0" dependencies: - camel-case "^4.1.2" - clean-css "^5.2.2" - commander "^8.3.0" - he "^1.2.0" - param-case "^3.0.4" - relateurl "^0.2.7" - terser "^5.10.0" + "camel-case" "^4.1.2" + "clean-css" "^5.2.2" + "commander" "^8.3.0" + "he" "^1.2.0" + "param-case" "^3.0.4" + "relateurl" "^0.2.7" + "terser" "^5.10.0" -html-tags@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" - integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== +"html-tags@^3.1.0": + "integrity" "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" + "resolved" "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz" + "version" "3.1.0" -html-void-elements@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" - integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== +"html-void-elements@^1.0.0": + "integrity" "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" + "resolved" "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz" + "version" "1.0.5" -html-webpack-plugin@^5.4.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" - integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== +"html-webpack-plugin@^5.4.0": + "integrity" "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==" + "resolved" "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz" + "version" "5.5.0" dependencies: "@types/html-minifier-terser" "^6.0.0" - html-minifier-terser "^6.0.2" - lodash "^4.17.21" - pretty-error "^4.0.0" - tapable "^2.0.0" + "html-minifier-terser" "^6.0.2" + "lodash" "^4.17.21" + "pretty-error" "^4.0.0" + "tapable" "^2.0.0" -htmlparser2@^3.9.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== +"htmlparser2@^3.9.1": + "integrity" "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==" + "resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz" + "version" "3.10.1" dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" + "domelementtype" "^1.3.1" + "domhandler" "^2.3.0" + "domutils" "^1.5.1" + "entities" "^1.1.1" + "inherits" "^2.0.1" + "readable-stream" "^3.1.1" -htmlparser2@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" - integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== +"htmlparser2@^6.1.0": + "integrity" "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==" + "resolved" "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" + "version" "6.1.0" dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - domutils "^2.5.2" - entities "^2.0.0" + "domelementtype" "^2.0.1" + "domhandler" "^4.0.0" + "domutils" "^2.5.2" + "entities" "^2.0.0" -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== +"http-cache-semantics@^4.0.0": + "integrity" "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "resolved" "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" + "version" "4.1.1" -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= +"http-deceiver@^1.2.7": + "integrity" "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" + "resolved" "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" + "version" "1.2.7" -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== +"http-errors@~1.6.2": + "integrity" "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=" + "resolved" "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + "version" "1.6.3" dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" + "depd" "~1.1.2" + "inherits" "2.0.3" + "setprototypeof" "1.1.0" + "statuses" ">= 1.4.0 < 2" -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= +"http-errors@1.8.1": + "integrity" "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==" + "resolved" "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" + "version" "1.8.1" dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" + "depd" "~1.1.2" + "inherits" "2.0.4" + "setprototypeof" "1.2.0" + "statuses" ">= 1.5.0 < 2" + "toidentifier" "1.0.1" -http-parser-js@>=0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" - integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== +"http-parser-js@>=0.5.1": + "integrity" "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==" + "resolved" "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz" + "version" "0.5.5" -http-proxy-middleware@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz#5df04f69a89f530c2284cd71eeaa51ba52243289" - integrity sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA== +"http-proxy-middleware@^2.0.0": + "integrity" "sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA==" + "resolved" "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz" + "version" "2.0.3" dependencies: "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" + "http-proxy" "^1.18.1" + "is-glob" "^4.0.1" + "is-plain-obj" "^3.0.0" + "micromatch" "^4.0.2" -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== +"http-proxy@^1.18.1": + "integrity" "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==" + "resolved" "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz" + "version" "1.18.1" dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" + "eventemitter3" "^4.0.0" + "follow-redirects" "^1.0.0" + "requires-port" "^1.0.0" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +"human-signals@^2.1.0": + "integrity" "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + "resolved" "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + "version" "2.1.0" -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== +"iconv-lite@0.4.24": + "integrity" "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==" + "resolved" "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + "version" "0.4.24" dependencies: - safer-buffer ">= 2.1.2 < 3" + "safer-buffer" ">= 2.1.2 < 3" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +"icss-utils@^5.0.0", "icss-utils@^5.1.0": + "integrity" "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" + "resolved" "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" + "version" "5.1.0" -ignore@^5.1.9, ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +"ignore@^5.1.9", "ignore@^5.2.0": + "integrity" "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + "resolved" "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + "version" "5.2.0" -image-size@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.1.tgz#86d6cfc2b1d19eab5d2b368d4b9194d9e48541c5" - integrity sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ== +"image-size@^1.0.1": + "integrity" "sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ==" + "resolved" "https://registry.npmjs.org/image-size/-/image-size-1.0.1.tgz" + "version" "1.0.1" dependencies: - queue "6.0.2" + "queue" "6.0.2" -immer@^9.0.7: - version "9.0.12" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" - integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== +"immer@^9.0.7": + "integrity" "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==" + "resolved" "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz" + "version" "9.0.12" -import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== +"import-fresh@^3.1.0", "import-fresh@^3.2.1", "import-fresh@^3.2.2", "import-fresh@^3.3.0": + "integrity" "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==" + "resolved" "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + "version" "3.3.0" dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" + "parent-module" "^1.0.0" + "resolve-from" "^4.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= +"import-lazy@^2.1.0": + "integrity" "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + "resolved" "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz" + "version" "2.1.0" -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +"imurmurhash@^0.1.4": + "integrity" "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "resolved" "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + "version" "0.1.4" -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +"indent-string@^4.0.0": + "integrity" "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + "resolved" "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + "version" "4.0.0" -infima@0.2.0-alpha.37: - version "0.2.0-alpha.37" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.37.tgz#b87ff42d528d6d050098a560f0294fbdd12adb78" - integrity sha512-4GX7Baw+/lwS4PPW/UJNY89tWSvYG1DL6baKVdpK6mC593iRgMssxNtORMTFArLPJ/A/lzsGhRmx+z6MaMxj0Q== +"infima@0.2.0-alpha.37": + "integrity" "sha512-4GX7Baw+/lwS4PPW/UJNY89tWSvYG1DL6baKVdpK6mC593iRgMssxNtORMTFArLPJ/A/lzsGhRmx+z6MaMxj0Q==" + "resolved" "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.37.tgz" + "version" "0.2.0-alpha.37" -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= +"inflight@^1.0.4": + "integrity" "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + "version" "1.0.6" dependencies: - once "^1.3.0" - wrappy "1" + "once" "^1.3.0" + "wrappy" "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +"inherits@^2.0.0", "inherits@^2.0.1", "inherits@^2.0.3", "inherits@~2.0.3", "inherits@2", "inherits@2.0.4": + "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + "version" "2.0.4" -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +"inherits@2.0.3": + "integrity" "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + "version" "2.0.3" -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +"ini@^1.3.5", "ini@~1.3.0": + "integrity" "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "resolved" "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + "version" "1.3.8" -ini@^1.3.5, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +"ini@2.0.0": + "integrity" "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" + "resolved" "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" + "version" "2.0.0" -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +"inline-style-parser@0.1.1": + "integrity" "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + "resolved" "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" + "version" "0.1.1" -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +"interpret@^1.0.0": + "integrity" "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + "resolved" "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz" + "version" "1.4.0" -ip@^1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +"ip@^1.1.0": + "integrity" "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "resolved" "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz" + "version" "1.1.5" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +"ipaddr.js@^2.0.1": + "integrity" "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" + "resolved" "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz" + "version" "2.0.1" -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== +"ipaddr.js@1.9.1": + "integrity" "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + "resolved" "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + "version" "1.9.1" -is-alphabetical@1.0.4, is-alphabetical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" - integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== +"is-alphabetical@^1.0.0", "is-alphabetical@1.0.4": + "integrity" "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + "resolved" "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz" + "version" "1.0.4" -is-alphanumerical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" - integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== +"is-alphanumerical@^1.0.0": + "integrity" "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==" + "resolved" "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz" + "version" "1.0.4" dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" + "is-alphabetical" "^1.0.0" + "is-decimal" "^1.0.0" -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== +"is-arguments@^1.0.4": + "integrity" "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==" + "resolved" "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" + "version" "1.1.1" dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +"is-arrayish@^0.2.1": + "integrity" "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "resolved" "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + "version" "0.2.1" -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== +"is-binary-path@~2.1.0": + "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" + "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + "version" "2.1.0" dependencies: - binary-extensions "^2.0.0" + "binary-extensions" "^2.0.0" -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +"is-buffer@^2.0.0": + "integrity" "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + "resolved" "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" + "version" "2.0.5" -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== +"is-ci@^2.0.0": + "integrity" "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==" + "resolved" "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" + "version" "2.0.0" dependencies: - ci-info "^2.0.0" + "ci-info" "^2.0.0" -is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== +"is-core-module@^2.8.1": + "integrity" "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==" + "resolved" "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" + "version" "2.8.1" dependencies: - has "^1.0.3" + "has" "^1.0.3" -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== +"is-date-object@^1.0.1": + "integrity" "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==" + "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + "version" "1.0.5" dependencies: - has-tostringtag "^1.0.0" + "has-tostringtag" "^1.0.0" -is-decimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" - integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== +"is-decimal@^1.0.0": + "integrity" "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + "resolved" "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz" + "version" "1.0.4" -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +"is-docker@^2.0.0", "is-docker@^2.1.1": + "integrity" "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + "resolved" "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" + "version" "2.2.1" -is-extendable@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= +"is-extendable@^0.1.0": + "integrity" "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + "resolved" "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + "version" "0.1.1" -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +"is-extglob@^2.1.1": + "integrity" "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + "version" "2.1.1" -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +"is-fullwidth-code-point@^3.0.0": + "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + "version" "3.0.0" -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== +"is-glob@^4.0.1", "is-glob@^4.0.3", "is-glob@~4.0.1": + "integrity" "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==" + "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + "version" "4.0.3" dependencies: - is-extglob "^2.1.1" + "is-extglob" "^2.1.1" -is-hexadecimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" - integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +"is-hexadecimal@^1.0.0": + "integrity" "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + "resolved" "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz" + "version" "1.0.4" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== +"is-installed-globally@^0.4.0": + "integrity" "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==" + "resolved" "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" + "version" "0.4.0" dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" + "global-dirs" "^3.0.0" + "is-path-inside" "^3.0.2" -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== +"is-npm@^5.0.0": + "integrity" "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" + "resolved" "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz" + "version" "5.0.0" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +"is-number@^7.0.0": + "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + "version" "7.0.0" -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= +"is-obj@^1.0.1": + "integrity" "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + "resolved" "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz" + "version" "1.0.1" -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== +"is-obj@^2.0.0": + "integrity" "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + "resolved" "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" + "version" "2.0.0" -is-path-cwd@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" - integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== +"is-path-cwd@^2.2.0": + "integrity" "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" + "resolved" "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz" + "version" "2.2.0" -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +"is-path-inside@^3.0.2": + "integrity" "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + "resolved" "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" + "version" "3.0.3" -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +"is-plain-obj@^2.0.0": + "integrity" "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" + "version" "2.1.0" -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== +"is-plain-obj@^3.0.0": + "integrity" "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + "resolved" "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" + "version" "3.0.0" -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== +"is-plain-object@^2.0.4": + "integrity" "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==" + "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + "version" "2.0.4" dependencies: - isobject "^3.0.1" + "isobject" "^3.0.1" -is-regex@^1.0.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== +"is-regex@^1.0.4": + "integrity" "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==" + "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + "version" "1.1.4" dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + "call-bind" "^1.0.2" + "has-tostringtag" "^1.0.0" -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= +"is-regexp@^1.0.0": + "integrity" "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" + "resolved" "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz" + "version" "1.0.0" -is-root@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" - integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== +"is-root@^2.1.0": + "integrity" "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" + "resolved" "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz" + "version" "2.1.0" -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +"is-stream@^2.0.0": + "integrity" "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + "version" "2.0.1" -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +"is-typedarray@^1.0.0": + "integrity" "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "resolved" "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + "version" "1.0.0" -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== +"is-whitespace-character@^1.0.0": + "integrity" "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" + "resolved" "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz" + "version" "1.0.4" -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== +"is-word-character@^1.0.0": + "integrity" "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" + "resolved" "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz" + "version" "1.0.4" -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== +"is-wsl@^2.2.0": + "integrity" "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==" + "resolved" "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + "version" "2.2.0" dependencies: - is-docker "^2.0.0" + "is-docker" "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== +"is-yarn-global@^0.3.0": + "integrity" "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + "resolved" "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" + "version" "0.3.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +"isarray@~1.0.0": + "integrity" "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "resolved" "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + "version" "1.0.0" -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +"isarray@0.0.1": + "integrity" "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "resolved" "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + "version" "0.0.1" -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +"isexe@^2.0.0": + "integrity" "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + "version" "2.0.0" -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +"isobject@^3.0.1": + "integrity" "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "resolved" "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + "version" "3.0.1" -jest-worker@^27.0.2, jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== +"jest-worker@^27.0.2", "jest-worker@^27.4.5": + "integrity" "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==" + "resolved" "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + "version" "27.5.1" dependencies: "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" + "merge-stream" "^2.0.0" + "supports-color" "^8.0.0" -joi@^17.4.2, joi@^17.6.0: - version "17.6.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" - integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== +"joi@^17.4.2", "joi@^17.6.0": + "integrity" "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==" + "resolved" "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz" + "version" "17.6.0" dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -4684,2834 +4702,2879 @@ joi@^17.4.2, joi@^17.6.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +"js-tokens@^3.0.0 || ^4.0.0", "js-tokens@^4.0.0": + "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + "version" "4.0.0" -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== +"js-yaml@^3.13.1": + "integrity" "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + "version" "3.14.1" dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + "argparse" "^1.0.7" + "esprima" "^4.0.0" -js-yaml@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +"js-yaml@^4.0.0": + "integrity" "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + "version" "4.1.0" dependencies: - argparse "^2.0.1" + "argparse" "^2.0.1" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +"jsesc@^2.5.1": + "integrity" "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "resolved" "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + "version" "2.5.2" -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +"jsesc@~0.5.0": + "integrity" "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "resolved" "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + "version" "0.5.0" -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +"json-buffer@3.0.0": + "integrity" "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "resolved" "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz" + "version" "3.0.0" -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +"json-parse-better-errors@^1.0.2": + "integrity" "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "resolved" "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" + "version" "1.0.2" -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +"json-parse-even-better-errors@^2.3.0": + "integrity" "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "resolved" "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + "version" "2.3.1" -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +"json-schema-traverse@^0.4.1": + "integrity" "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + "version" "0.4.1" -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +"json-schema-traverse@^1.0.0": + "integrity" "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + "version" "1.0.0" -json5@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== +"json5@^1.0.1": + "integrity" "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==" + "resolved" "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" + "version" "1.0.2" dependencies: - minimist "^1.2.0" + "minimist" "^1.2.0" -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" +"json5@^2.1.2", "json5@^2.2.2": + "integrity" "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + "resolved" "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + "version" "2.2.3" -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== +"jsonfile@^6.0.1": + "integrity" "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==" + "resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + "version" "6.1.0" dependencies: - universalify "^2.0.0" + "universalify" "^2.0.0" optionalDependencies: - graceful-fs "^4.1.6" + "graceful-fs" "^4.1.6" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== +"keyv@^3.0.0": + "integrity" "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==" + "resolved" "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" + "version" "3.1.0" dependencies: - json-buffer "3.0.0" + "json-buffer" "3.0.0" -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +"kind-of@^6.0.0", "kind-of@^6.0.2": + "integrity" "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + "resolved" "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + "version" "6.0.3" -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +"kleur@^3.0.3": + "integrity" "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + "resolved" "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + "version" "3.0.3" -klona@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +"klona@^2.0.5": + "integrity" "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==" + "resolved" "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz" + "version" "2.0.5" -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== +"latest-version@^5.1.0": + "integrity" "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==" + "resolved" "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz" + "version" "5.1.0" dependencies: - package-json "^6.3.0" + "package-json" "^6.3.0" -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +"leven@^3.1.0": + "integrity" "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + "resolved" "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + "version" "3.1.0" -lilconfig@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" - integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== +"lilconfig@^2.0.3": + "integrity" "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==" + "resolved" "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz" + "version" "2.0.4" -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +"lines-and-columns@^1.1.6": + "integrity" "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "resolved" "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + "version" "1.2.4" -loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== +"loader-runner@^4.2.0": + "integrity" "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" + "resolved" "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz" + "version" "4.2.0" -loader-utils@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== +"loader-utils@^1.4.0": + "integrity" "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==" + "resolved" "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz" + "version" "1.4.2" dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" + "big.js" "^5.2.2" + "emojis-list" "^3.0.0" + "json5" "^1.0.1" -loader-utils@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" - integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== +"loader-utils@^2.0.0": + "integrity" "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==" + "resolved" "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz" + "version" "2.0.2" dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" + "big.js" "^5.2.2" + "emojis-list" "^3.0.0" + "json5" "^2.1.2" -loader-utils@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" - integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== +"loader-utils@^3.2.0": + "integrity" "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==" + "resolved" "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz" + "version" "3.2.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +"locate-path@^3.0.0": + "integrity" "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" + "version" "3.0.0" dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + "p-locate" "^3.0.0" + "path-exists" "^3.0.0" -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== +"locate-path@^5.0.0": + "integrity" "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + "version" "5.0.0" dependencies: - p-locate "^4.1.0" + "p-locate" "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== +"locate-path@^6.0.0": + "integrity" "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + "version" "6.0.0" dependencies: - p-locate "^5.0.0" + "p-locate" "^5.0.0" -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= +"lodash.assignin@^4.0.9": + "integrity" "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + "resolved" "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz" + "version" "4.2.0" -lodash.bind@^4.1.4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" - integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= +"lodash.bind@^4.1.4": + "integrity" "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + "resolved" "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz" + "version" "4.2.1" -lodash.curry@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" - integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= +"lodash.curry@^4.0.1": + "integrity" "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" + "resolved" "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz" + "version" "4.1.1" -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +"lodash.debounce@^4.0.8": + "integrity" "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + "resolved" "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + "version" "4.0.8" -lodash.defaults@^4.0.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= +"lodash.defaults@^4.0.1": + "integrity" "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + "resolved" "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" + "version" "4.2.0" -lodash.filter@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= +"lodash.filter@^4.4.0": + "integrity" "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + "resolved" "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz" + "version" "4.6.0" -lodash.flatten@^4.2.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +"lodash.flatten@^4.2.0": + "integrity" "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + "resolved" "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz" + "version" "4.4.0" -lodash.flow@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" - integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= +"lodash.flow@^3.3.0": + "integrity" "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" + "resolved" "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz" + "version" "3.5.0" -lodash.foreach@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= +"lodash.foreach@^4.3.0": + "integrity" "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "resolved" "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz" + "version" "4.5.0" -lodash.map@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= +"lodash.map@^4.4.0": + "integrity" "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + "resolved" "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz" + "version" "4.6.0" -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +"lodash.memoize@^4.1.2": + "integrity" "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + "resolved" "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + "version" "4.1.2" -lodash.merge@^4.4.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +"lodash.merge@^4.4.0": + "integrity" "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "resolved" "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + "version" "4.6.2" -lodash.pick@^4.2.1: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= +"lodash.pick@^4.2.1": + "integrity" "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + "resolved" "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz" + "version" "4.4.0" -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= +"lodash.reduce@^4.4.0": + "integrity" "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + "resolved" "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz" + "version" "4.6.0" -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" - integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= +"lodash.reject@^4.4.0": + "integrity" "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + "resolved" "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz" + "version" "4.6.0" -lodash.some@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" - integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= +"lodash.some@^4.4.0": + "integrity" "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + "resolved" "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz" + "version" "4.6.0" -lodash.uniq@4.5.0, lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +"lodash.uniq@^4.5.0", "lodash.uniq@4.5.0": + "integrity" "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + "resolved" "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" + "version" "4.5.0" -lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +"lodash@^4.17.14", "lodash@^4.17.19", "lodash@^4.17.20", "lodash@^4.17.21": + "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + "version" "4.17.21" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== +"loose-envify@^1.0.0", "loose-envify@^1.1.0", "loose-envify@^1.2.0", "loose-envify@^1.3.1", "loose-envify@^1.4.0": + "integrity" "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==" + "resolved" "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + "version" "1.4.0" dependencies: - js-tokens "^3.0.0 || ^4.0.0" + "js-tokens" "^3.0.0 || ^4.0.0" -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== +"lower-case@^2.0.2": + "integrity" "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==" + "resolved" "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz" + "version" "2.0.2" dependencies: - tslib "^2.0.3" + "tslib" "^2.0.3" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +"lowercase-keys@^1.0.0", "lowercase-keys@^1.0.1": + "integrity" "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + "resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz" + "version" "1.0.1" -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +"lowercase-keys@^2.0.0": + "integrity" "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + "resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" + "version" "2.0.0" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== +"lru-cache@^5.1.1": + "integrity" "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==" + "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + "version" "5.1.1" dependencies: - yallist "^4.0.0" + "yallist" "^3.0.2" -magic-string@^0.25.3: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== +"lru-cache@^6.0.0": + "integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" + "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + "version" "6.0.0" dependencies: - sourcemap-codec "^1.4.4" + "yallist" "^4.0.0" -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +"magic-string@^0.25.3": + "integrity" "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==" + "resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz" + "version" "0.25.7" dependencies: - semver "^6.0.0" + "sourcemap-codec" "^1.4.4" -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -mdast-squeeze-paragraphs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" - integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== +"make-dir@^3.0.0", "make-dir@^3.0.2", "make-dir@^3.1.0": + "integrity" "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==" + "resolved" "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + "version" "3.1.0" dependencies: - unist-util-remove "^2.0.0" + "semver" "^6.0.0" -mdast-util-definitions@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" - integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== +"markdown-escapes@^1.0.0": + "integrity" "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" + "resolved" "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz" + "version" "1.0.4" + +"mdast-squeeze-paragraphs@^4.0.0": + "integrity" "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==" + "resolved" "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz" + "version" "4.0.0" dependencies: - unist-util-visit "^2.0.0" + "unist-util-remove" "^2.0.0" -mdast-util-to-hast@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" - integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== +"mdast-util-definitions@^4.0.0": + "integrity" "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==" + "resolved" "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "unist-util-visit" "^2.0.0" + +"mdast-util-to-hast@10.0.1": + "integrity" "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==" + "resolved" "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz" + "version" "10.0.1" dependencies: "@types/mdast" "^3.0.0" "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" + "mdast-util-definitions" "^4.0.0" + "mdurl" "^1.0.0" + "unist-builder" "^2.0.0" + "unist-util-generated" "^1.0.0" + "unist-util-position" "^3.0.0" + "unist-util-visit" "^2.0.0" -mdast-util-to-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" - integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w== +"mdast-util-to-string@^2.0.0": + "integrity" "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==" + "resolved" "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz" + "version" "2.0.0" -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +"mdn-data@2.0.14": + "integrity" "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "resolved" "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" + "version" "2.0.14" -mdurl@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +"mdurl@^1.0.0": + "integrity" "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + "resolved" "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" + "version" "1.0.1" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +"media-typer@0.3.0": + "integrity" "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "resolved" "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + "version" "0.3.0" -memfs@^3.1.2, memfs@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" - integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== +"memfs@^3.1.2", "memfs@^3.4.1": + "integrity" "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==" + "resolved" "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz" + "version" "3.4.1" dependencies: - fs-monkey "1.0.3" + "fs-monkey" "1.0.3" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= +"merge-descriptors@1.0.1": + "integrity" "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "resolved" "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + "version" "1.0.1" -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +"merge-stream@^2.0.0": + "integrity" "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "resolved" "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + "version" "2.0.0" -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +"merge2@^1.3.0", "merge2@^1.4.1": + "integrity" "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + "resolved" "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + "version" "1.4.1" -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +"methods@~1.1.2": + "integrity" "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "resolved" "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + "version" "1.1.2" -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +"micromatch@^4.0.2", "micromatch@^4.0.4": + "integrity" "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==" + "resolved" "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz" + "version" "4.0.4" dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + "braces" "^3.0.1" + "picomatch" "^2.2.3" -mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +"mime-db@>= 1.43.0 < 2", "mime-db@1.51.0": + "integrity" "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" + "version" "1.51.0" -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== +"mime-db@~1.33.0": + "integrity" "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" + "version" "1.33.0" -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== +"mime-types@^2.1.27", "mime-types@^2.1.31", "mime-types@~2.1.17", "mime-types@~2.1.24", "mime-types@~2.1.34": + "integrity" "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==" + "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz" + "version" "2.1.34" dependencies: - mime-db "~1.33.0" + "mime-db" "1.51.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== +"mime-types@2.1.18": + "integrity" "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==" + "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" + "version" "2.1.18" dependencies: - mime-db "1.51.0" + "mime-db" "~1.33.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +"mime@1.6.0": + "integrity" "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "resolved" "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + "version" "1.6.0" -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +"mimic-fn@^2.1.0": + "integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + "version" "2.1.0" -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +"mimic-response@^1.0.0", "mimic-response@^1.0.1": + "integrity" "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "resolved" "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" + "version" "1.0.1" -mini-create-react-context@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" - integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== +"mini-create-react-context@^0.4.0": + "integrity" "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==" + "resolved" "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz" + "version" "0.4.1" dependencies: "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" + "tiny-warning" "^1.0.3" -mini-css-extract-plugin@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== +"mini-css-extract-plugin@^1.6.0": + "integrity" "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==" + "resolved" "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz" + "version" "1.6.2" dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + "loader-utils" "^2.0.0" + "schema-utils" "^3.0.0" + "webpack-sources" "^1.1.0" -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== +"minimalistic-assert@^1.0.0": + "integrity" "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "resolved" "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + "version" "1.0.1" -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +"minimatch@^3.0.4": + "integrity" "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + "version" "3.1.2" dependencies: - brace-expansion "^1.1.7" + "brace-expansion" "^1.1.7" -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +"minimatch@3.0.4": + "integrity" "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + "version" "3.0.4" dependencies: - brace-expansion "^1.1.7" + "brace-expansion" "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +"minimist@^1.2.0", "minimist@^1.2.5": + "integrity" "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz" + "version" "1.2.7" -mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== +"mkdirp@^0.5.5": + "integrity" "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==" + "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" + "version" "0.5.5" dependencies: - minimist "^1.2.5" + "minimist" "^1.2.5" -mrmime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" - integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ== +"mrmime@^1.0.0": + "integrity" "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==" + "resolved" "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz" + "version" "1.0.0" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +"ms@^2.1.1", "ms@2.1.3": + "integrity" "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + "version" "2.1.3" -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +"ms@2.0.0": + "integrity" "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + "version" "2.0.0" -ms@2.1.3, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +"ms@2.1.2": + "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + "version" "2.1.2" -multicast-dns-service-types@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= +"multicast-dns-service-types@^1.1.0": + "integrity" "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" + "resolved" "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz" + "version" "1.1.0" -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== +"multicast-dns@^6.0.1": + "integrity" "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==" + "resolved" "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz" + "version" "6.2.3" dependencies: - dns-packet "^1.3.1" - thunky "^1.0.2" + "dns-packet" "^1.3.1" + "thunky" "^1.0.2" -nanoid@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" - integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== +"nanoid@^3.2.0": + "integrity" "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" + "resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz" + "version" "3.3.1" -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +"negotiator@0.6.3": + "integrity" "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + "resolved" "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + "version" "0.6.3" -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +"neo-async@^2.6.2": + "integrity" "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + "resolved" "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + "version" "2.6.2" -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== +"no-case@^3.0.4": + "integrity" "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==" + "resolved" "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz" + "version" "3.0.4" dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" + "lower-case" "^2.0.2" + "tslib" "^2.0.3" -node-emoji@^1.10.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" - integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== +"node-emoji@^1.10.0": + "integrity" "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==" + "resolved" "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz" + "version" "1.11.0" dependencies: - lodash "^4.17.21" + "lodash" "^4.17.21" -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== +"node-fetch@2.6.7": + "integrity" "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==" + "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" + "version" "2.6.7" dependencies: - whatwg-url "^5.0.0" + "whatwg-url" "^5.0.0" -node-forge@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" - integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== +"node-forge@^1.2.0": + "integrity" "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" + "resolved" "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz" + "version" "1.3.0" -node-releases@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== +"node-releases@^2.0.8": + "integrity" "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + "resolved" "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz" + "version" "2.0.10" -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +"normalize-path@^3.0.0", "normalize-path@~3.0.0": + "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + "version" "3.0.0" -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= +"normalize-range@^0.1.2": + "integrity" "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + "resolved" "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + "version" "0.1.2" -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +"normalize-url@^4.1.0": + "integrity" "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + "resolved" "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" + "version" "4.5.1" -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +"normalize-url@^6.0.1": + "integrity" "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + "resolved" "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" + "version" "6.1.0" -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== +"npm-run-path@^4.0.1": + "integrity" "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==" + "resolved" "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + "version" "4.0.1" dependencies: - path-key "^3.0.0" + "path-key" "^3.0.0" -nprogress@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" - integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= +"nprogress@^0.2.0": + "integrity" "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" + "resolved" "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz" + "version" "0.2.0" -nth-check@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" - integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== +"nth-check@^2.0.1": + "integrity" "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==" + "resolved" "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz" + "version" "2.0.1" dependencies: - boolbase "^1.0.0" + "boolbase" "^1.0.0" -nth-check@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== +"nth-check@~1.0.1": + "integrity" "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==" + "resolved" "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz" + "version" "1.0.2" dependencies: - boolbase "~1.0.0" + "boolbase" "~1.0.0" -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +"object-assign@^4.1.0", "object-assign@^4.1.1": + "integrity" "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "resolved" "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + "version" "4.1.1" -object-is@^1.0.1: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" - integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== +"object-is@^1.0.1": + "integrity" "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==" + "resolved" "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" + "version" "1.1.5" dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + "call-bind" "^1.0.2" + "define-properties" "^1.1.3" -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +"object-keys@^1.0.12", "object-keys@^1.1.1": + "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + "version" "1.1.1" -object.assign@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== +"object.assign@^4.1.0": + "integrity" "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==" + "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" + "version" "4.1.2" dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" + "call-bind" "^1.0.0" + "define-properties" "^1.1.3" + "has-symbols" "^1.0.1" + "object-keys" "^1.1.1" -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +"obuf@^1.0.0", "obuf@^1.1.2": + "integrity" "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + "resolved" "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" + "version" "1.1.2" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +"on-finished@~2.3.0": + "integrity" "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" + "resolved" "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + "version" "2.3.0" dependencies: - ee-first "1.1.1" + "ee-first" "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +"on-headers@~1.0.2": + "integrity" "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + "resolved" "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" + "version" "1.0.2" -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= +"once@^1.3.0", "once@^1.3.1", "once@^1.4.0": + "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + "version" "1.4.0" dependencies: - wrappy "1" + "wrappy" "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== +"onetime@^5.1.2": + "integrity" "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==" + "resolved" "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + "version" "5.1.2" dependencies: - mimic-fn "^2.1.0" + "mimic-fn" "^2.1.0" -open@^8.0.9, open@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== +"open@^8.0.9", "open@^8.4.0": + "integrity" "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==" + "resolved" "https://registry.npmjs.org/open/-/open-8.4.0.tgz" + "version" "8.4.0" dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" + "define-lazy-prop" "^2.0.0" + "is-docker" "^2.1.1" + "is-wsl" "^2.2.0" -opener@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +"opener@^1.5.2": + "integrity" "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" + "resolved" "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz" + "version" "1.5.2" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +"p-cancelable@^1.0.0": + "integrity" "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + "resolved" "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz" + "version" "1.1.0" -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== +"p-limit@^2.0.0", "p-limit@^2.2.0": + "integrity" "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + "version" "2.3.0" dependencies: - p-try "^2.0.0" + "p-try" "^2.0.0" -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== +"p-limit@^3.0.2": + "integrity" "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + "version" "3.1.0" dependencies: - yocto-queue "^0.1.0" + "yocto-queue" "^0.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +"p-locate@^3.0.0": + "integrity" "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" + "version" "3.0.0" dependencies: - p-limit "^2.0.0" + "p-limit" "^2.0.0" -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== +"p-locate@^4.1.0": + "integrity" "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + "version" "4.1.0" dependencies: - p-limit "^2.2.0" + "p-limit" "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== +"p-locate@^5.0.0": + "integrity" "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + "version" "5.0.0" dependencies: - p-limit "^3.0.2" + "p-limit" "^3.0.2" -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== +"p-map@^4.0.0": + "integrity" "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==" + "resolved" "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" + "version" "4.0.0" dependencies: - aggregate-error "^3.0.0" + "aggregate-error" "^3.0.0" -p-retry@^4.5.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" - integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== +"p-retry@^4.5.0": + "integrity" "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==" + "resolved" "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz" + "version" "4.6.1" dependencies: "@types/retry" "^0.12.0" - retry "^0.13.1" + "retry" "^0.13.1" -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +"p-try@^2.0.0": + "integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + "version" "2.2.0" -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== +"package-json@^6.3.0": + "integrity" "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==" + "resolved" "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz" + "version" "6.5.0" dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" + "got" "^9.6.0" + "registry-auth-token" "^4.0.0" + "registry-url" "^5.0.0" + "semver" "^6.2.0" -param-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== +"param-case@^3.0.4": + "integrity" "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==" + "resolved" "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz" + "version" "3.0.4" dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" + "dot-case" "^3.0.4" + "tslib" "^2.0.3" -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== +"parent-module@^1.0.0": + "integrity" "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==" + "resolved" "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + "version" "1.0.1" dependencies: - callsites "^3.0.0" + "callsites" "^3.0.0" -parse-entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" - integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== +"parse-entities@^2.0.0": + "integrity" "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==" + "resolved" "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz" + "version" "2.0.0" dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" + "character-entities" "^1.0.0" + "character-entities-legacy" "^1.0.0" + "character-reference-invalid" "^1.0.0" + "is-alphanumerical" "^1.0.0" + "is-decimal" "^1.0.0" + "is-hexadecimal" "^1.0.0" -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== +"parse-json@^5.0.0": + "integrity" "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==" + "resolved" "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + "version" "5.2.0" dependencies: "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" + "error-ex" "^1.3.1" + "json-parse-even-better-errors" "^2.3.0" + "lines-and-columns" "^1.1.6" -parse-numeric-range@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" - integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== +"parse-numeric-range@^1.3.0": + "integrity" "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + "resolved" "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz" + "version" "1.3.0" -parse5-htmlparser2-tree-adapter@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== +"parse5-htmlparser2-tree-adapter@^6.0.1": + "integrity" "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==" + "resolved" "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" + "version" "6.0.1" dependencies: - parse5 "^6.0.1" + "parse5" "^6.0.1" -parse5@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== +"parse5@^5.0.0": + "integrity" "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + "resolved" "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz" + "version" "5.1.1" -parse5@^6.0.0, parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +"parse5@^6.0.0", "parse5@^6.0.1": + "integrity" "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "resolved" "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" + "version" "6.0.1" -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +"parseurl@~1.3.2", "parseurl@~1.3.3": + "integrity" "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "resolved" "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + "version" "1.3.3" -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== +"pascal-case@^3.1.2": + "integrity" "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==" + "resolved" "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz" + "version" "3.1.2" dependencies: - no-case "^3.0.4" - tslib "^2.0.3" + "no-case" "^3.0.4" + "tslib" "^2.0.3" -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +"path-exists@^3.0.0": + "integrity" "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + "version" "3.0.0" -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== +"path-exists@^4.0.0": + "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + "version" "4.0.0" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +"path-is-absolute@^1.0.0": + "integrity" "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + "version" "1.0.1" -path-is-inside@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= +"path-is-inside@1.0.2": + "integrity" "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + "resolved" "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" + "version" "1.0.2" -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +"path-key@^3.0.0", "path-key@^3.1.0": + "integrity" "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "resolved" "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + "version" "3.1.1" -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +"path-parse@^1.0.7": + "integrity" "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + "version" "1.0.7" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== +"path-to-regexp@^1.7.0": + "integrity" "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==" + "resolved" "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" + "version" "1.8.0" dependencies: - isarray "0.0.1" + "isarray" "0.0.1" -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +"path-to-regexp@0.1.7": + "integrity" "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "resolved" "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + "version" "0.1.7" -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +"path-to-regexp@2.2.1": + "integrity" "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "resolved" "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz" + "version" "2.2.1" -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +"path-type@^4.0.0": + "integrity" "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "resolved" "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + "version" "4.0.0" -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== +"picocolors@^1.0.0": + "integrity" "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "resolved" "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + "version" "1.0.0" + +"picomatch@^2.0.4", "picomatch@^2.2.1", "picomatch@^2.2.3": + "integrity" "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + "version" "2.3.1" + +"pkg-dir@^4.1.0": + "integrity" "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==" + "resolved" "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + "version" "4.2.0" dependencies: - find-up "^4.0.0" + "find-up" "^4.0.0" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== +"pkg-up@^3.1.0": + "integrity" "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==" + "resolved" "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz" + "version" "3.1.0" dependencies: - find-up "^3.0.0" + "find-up" "^3.0.0" -portfinder@^1.0.28: - version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" - integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== +"portfinder@^1.0.28": + "integrity" "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==" + "resolved" "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz" + "version" "1.0.28" dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.5" + "async" "^2.6.2" + "debug" "^3.1.1" + "mkdirp" "^0.5.5" -postcss-calc@^8.2.0: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== +"postcss-calc@^8.2.0": + "integrity" "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==" + "resolved" "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz" + "version" "8.2.4" dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" + "postcss-selector-parser" "^6.0.9" + "postcss-value-parser" "^4.2.0" -postcss-colormin@^5.2.5: - version "5.2.5" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.5.tgz#d1fc269ac2ad03fe641d462b5d1dada35c69968a" - integrity sha512-+X30aDaGYq81mFqwyPpnYInsZQnNpdxMX0ajlY7AExCexEFkPVV+KrO7kXwayqEWL2xwEbNQ4nUO0ZsRWGnevg== +"postcss-colormin@^5.2.5": + "integrity" "sha512-+X30aDaGYq81mFqwyPpnYInsZQnNpdxMX0ajlY7AExCexEFkPVV+KrO7kXwayqEWL2xwEbNQ4nUO0ZsRWGnevg==" + "resolved" "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.5.tgz" + "version" "5.2.5" dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" + "browserslist" "^4.16.6" + "caniuse-api" "^3.0.0" + "colord" "^2.9.1" + "postcss-value-parser" "^4.2.0" -postcss-convert-values@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.4.tgz#3e74dd97c581f475ae7b4500bc0a7c4fb3a6b1b6" - integrity sha512-bugzSAyjIexdObovsPZu/sBCTHccImJxLyFgeV0MmNBm/Lw5h5XnjfML6gzEmJ3A6nyfCW7hb1JXzcsA4Zfbdw== +"postcss-convert-values@^5.0.4": + "integrity" "sha512-bugzSAyjIexdObovsPZu/sBCTHccImJxLyFgeV0MmNBm/Lw5h5XnjfML6gzEmJ3A6nyfCW7hb1JXzcsA4Zfbdw==" + "resolved" "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-discard-comments@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.3.tgz#011acb63418d600fdbe18804e1bbecb543ad2f87" - integrity sha512-6W5BemziRoqIdAKT+1QjM4bNcJAQ7z7zk073730NHg4cUXh3/rQHHj7pmYxUB9aGhuRhBiUf0pXvIHkRwhQP0Q== +"postcss-discard-comments@^5.0.3": + "integrity" "sha512-6W5BemziRoqIdAKT+1QjM4bNcJAQ7z7zk073730NHg4cUXh3/rQHHj7pmYxUB9aGhuRhBiUf0pXvIHkRwhQP0Q==" + "resolved" "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.3.tgz" + "version" "5.0.3" -postcss-discard-duplicates@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.3.tgz#10f202a4cfe9d407b73dfea7a477054d21ea0c1f" - integrity sha512-vPtm1Mf+kp7iAENTG7jI1MN1lk+fBqL5y+qxyi4v3H+lzsXEdfS3dwUZD45KVhgzDEgduur8ycB4hMegyMTeRw== +"postcss-discard-duplicates@^5.0.3": + "integrity" "sha512-vPtm1Mf+kp7iAENTG7jI1MN1lk+fBqL5y+qxyi4v3H+lzsXEdfS3dwUZD45KVhgzDEgduur8ycB4hMegyMTeRw==" + "resolved" "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.3.tgz" + "version" "5.0.3" -postcss-discard-empty@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.3.tgz#ec185af4a3710b88933b0ff751aa157b6041dd6a" - integrity sha512-xGJugpaXKakwKI7sSdZjUuN4V3zSzb2Y0LOlmTajFbNinEjTfVs9PFW2lmKBaC/E64WwYppfqLD03P8l9BuueA== +"postcss-discard-empty@^5.0.3": + "integrity" "sha512-xGJugpaXKakwKI7sSdZjUuN4V3zSzb2Y0LOlmTajFbNinEjTfVs9PFW2lmKBaC/E64WwYppfqLD03P8l9BuueA==" + "resolved" "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.3.tgz" + "version" "5.0.3" -postcss-discard-overridden@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.4.tgz#cc999d6caf18ea16eff8b2b58f48ec3ddee35c9c" - integrity sha512-3j9QH0Qh1KkdxwiZOW82cId7zdwXVQv/gRXYDnwx5pBtR1sTkU4cXRK9lp5dSdiM0r0OICO/L8J6sV1/7m0kHg== +"postcss-discard-overridden@^5.0.4": + "integrity" "sha512-3j9QH0Qh1KkdxwiZOW82cId7zdwXVQv/gRXYDnwx5pBtR1sTkU4cXRK9lp5dSdiM0r0OICO/L8J6sV1/7m0kHg==" + "resolved" "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.4.tgz" + "version" "5.0.4" -postcss-discard-unused@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.0.3.tgz#89fd3ebdbed8320df77a4ad503bd83cff52409f5" - integrity sha512-WO6FJxL5fGnuE77ZbTcZ/nRZJ4+TOqNaqLBLWgkR4e+WdmHn77OHPyQmsRv7eOB2rLKL6tsq2bs1GwoKXD/++Q== +"postcss-discard-unused@^5.0.3": + "integrity" "sha512-WO6FJxL5fGnuE77ZbTcZ/nRZJ4+TOqNaqLBLWgkR4e+WdmHn77OHPyQmsRv7eOB2rLKL6tsq2bs1GwoKXD/++Q==" + "resolved" "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.0.3.tgz" + "version" "5.0.3" dependencies: - postcss-selector-parser "^6.0.5" + "postcss-selector-parser" "^6.0.5" -postcss-loader@^6.1.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" - integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== +"postcss-loader@^6.1.1": + "integrity" "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==" + "resolved" "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz" + "version" "6.2.1" dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.5" + "cosmiconfig" "^7.0.0" + "klona" "^2.0.5" + "semver" "^7.3.5" -postcss-merge-idents@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.0.3.tgz#04f333f32767bd7b7b002f0032da347ec3c8c484" - integrity sha512-Z4LCzh2WzMn69KaS2FaJcrIeDQ170V13QHq+0hnBEFKJJkD+y5qndZ/bl3AhpddrSrXWIVR+xAwjmHQIJI2Eog== +"postcss-merge-idents@^5.0.3": + "integrity" "sha512-Z4LCzh2WzMn69KaS2FaJcrIeDQ170V13QHq+0hnBEFKJJkD+y5qndZ/bl3AhpddrSrXWIVR+xAwjmHQIJI2Eog==" + "resolved" "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.0.3.tgz" + "version" "5.0.3" dependencies: - cssnano-utils "^3.0.2" - postcss-value-parser "^4.2.0" + "cssnano-utils" "^3.0.2" + "postcss-value-parser" "^4.2.0" -postcss-merge-longhand@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.6.tgz#090e60d5d3b3caad899f8774f8dccb33217d2166" - integrity sha512-rkmoPwQO6ymJSmWsX6l2hHeEBQa7C4kJb9jyi5fZB1sE8nSCv7sqchoYPixRwX/yvLoZP2y6FA5kcjiByeJqDg== +"postcss-merge-longhand@^5.0.6": + "integrity" "sha512-rkmoPwQO6ymJSmWsX6l2hHeEBQa7C4kJb9jyi5fZB1sE8nSCv7sqchoYPixRwX/yvLoZP2y6FA5kcjiByeJqDg==" + "resolved" "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.6.tgz" + "version" "5.0.6" dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.0.3" + "postcss-value-parser" "^4.2.0" + "stylehacks" "^5.0.3" -postcss-merge-rules@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.6.tgz#26b37411fe1e80202fcef61cab027265b8925f2b" - integrity sha512-nzJWJ9yXWp8AOEpn/HFAW72WKVGD2bsLiAmgw4hDchSij27bt6TF+sIK0cJUBAYT3SGcjtGGsOR89bwkkMuMgQ== +"postcss-merge-rules@^5.0.6": + "integrity" "sha512-nzJWJ9yXWp8AOEpn/HFAW72WKVGD2bsLiAmgw4hDchSij27bt6TF+sIK0cJUBAYT3SGcjtGGsOR89bwkkMuMgQ==" + "resolved" "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.6.tgz" + "version" "5.0.6" dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^3.0.2" - postcss-selector-parser "^6.0.5" + "browserslist" "^4.16.6" + "caniuse-api" "^3.0.0" + "cssnano-utils" "^3.0.2" + "postcss-selector-parser" "^6.0.5" -postcss-minify-font-values@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.4.tgz#627d824406b0712243221891f40a44fffe1467fd" - integrity sha512-RN6q3tyuEesvyCYYFCRGJ41J1XFvgV+dvYGHr0CeHv8F00yILlN8Slf4t8XW4IghlfZYCeyRrANO6HpJ948ieA== +"postcss-minify-font-values@^5.0.4": + "integrity" "sha512-RN6q3tyuEesvyCYYFCRGJ41J1XFvgV+dvYGHr0CeHv8F00yILlN8Slf4t8XW4IghlfZYCeyRrANO6HpJ948ieA==" + "resolved" "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-minify-gradients@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.6.tgz#b07cef51a93f075e94053fd972ff1cba2eaf6503" - integrity sha512-E/dT6oVxB9nLGUTiY/rG5dX9taugv9cbLNTFad3dKxOO+BQg25Q/xo2z2ddG+ZB1CbkZYaVwx5blY8VC7R/43A== +"postcss-minify-gradients@^5.0.6": + "integrity" "sha512-E/dT6oVxB9nLGUTiY/rG5dX9taugv9cbLNTFad3dKxOO+BQg25Q/xo2z2ddG+ZB1CbkZYaVwx5blY8VC7R/43A==" + "resolved" "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.6.tgz" + "version" "5.0.6" dependencies: - colord "^2.9.1" - cssnano-utils "^3.0.2" - postcss-value-parser "^4.2.0" + "colord" "^2.9.1" + "cssnano-utils" "^3.0.2" + "postcss-value-parser" "^4.2.0" -postcss-minify-params@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.5.tgz#86cb624358cd45c21946f8c317893f0449396646" - integrity sha512-YBNuq3Rz5LfLFNHb9wrvm6t859b8qIqfXsWeK7wROm3jSKNpO1Y5e8cOyBv6Acji15TgSrAwb3JkVNCqNyLvBg== +"postcss-minify-params@^5.0.5": + "integrity" "sha512-YBNuq3Rz5LfLFNHb9wrvm6t859b8qIqfXsWeK7wROm3jSKNpO1Y5e8cOyBv6Acji15TgSrAwb3JkVNCqNyLvBg==" + "resolved" "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.5.tgz" + "version" "5.0.5" dependencies: - browserslist "^4.16.6" - cssnano-utils "^3.0.2" - postcss-value-parser "^4.2.0" + "browserslist" "^4.16.6" + "cssnano-utils" "^3.0.2" + "postcss-value-parser" "^4.2.0" -postcss-minify-selectors@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.3.tgz#6ac12d52aa661fd509469d87ab2cebb0a1e3a1b5" - integrity sha512-9RJfTiQEKA/kZhMaEXND893nBqmYQ8qYa/G+uPdVnXF6D/FzpfI6kwBtWEcHx5FqDbA79O9n6fQJfrIj6M8jvQ== +"postcss-minify-selectors@^5.1.3": + "integrity" "sha512-9RJfTiQEKA/kZhMaEXND893nBqmYQ8qYa/G+uPdVnXF6D/FzpfI6kwBtWEcHx5FqDbA79O9n6fQJfrIj6M8jvQ==" + "resolved" "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.3.tgz" + "version" "5.1.3" dependencies: - postcss-selector-parser "^6.0.5" + "postcss-selector-parser" "^6.0.5" -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== +"postcss-modules-extract-imports@^3.0.0": + "integrity" "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" + "resolved" "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" + "version" "3.0.0" -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== +"postcss-modules-local-by-default@^4.0.0": + "integrity" "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==" + "resolved" "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz" + "version" "4.0.0" dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" + "icss-utils" "^5.0.0" + "postcss-selector-parser" "^6.0.2" + "postcss-value-parser" "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== +"postcss-modules-scope@^3.0.0": + "integrity" "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==" + "resolved" "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz" + "version" "3.0.0" dependencies: - postcss-selector-parser "^6.0.4" + "postcss-selector-parser" "^6.0.4" -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== +"postcss-modules-values@^4.0.0": + "integrity" "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==" + "resolved" "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" + "version" "4.0.0" dependencies: - icss-utils "^5.0.0" + "icss-utils" "^5.0.0" -postcss-normalize-charset@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.3.tgz#719fb9f9ca9835fcbd4fed8d6e0d72a79e7b5472" - integrity sha512-iKEplDBco9EfH7sx4ut7R2r/dwTnUqyfACf62Unc9UiyFuI7uUqZZtY+u+qp7g8Qszl/U28HIfcsI3pEABWFfA== +"postcss-normalize-charset@^5.0.3": + "integrity" "sha512-iKEplDBco9EfH7sx4ut7R2r/dwTnUqyfACf62Unc9UiyFuI7uUqZZtY+u+qp7g8Qszl/U28HIfcsI3pEABWFfA==" + "resolved" "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.3.tgz" + "version" "5.0.3" -postcss-normalize-display-values@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.3.tgz#94cc82e20c51cc4ffba6b36e9618adc1e50db8c1" - integrity sha512-FIV5FY/qs4Ja32jiDb5mVj5iWBlS3N8tFcw2yg98+8MkRgyhtnBgSC0lxU+16AMHbjX5fbSJgw5AXLMolonuRQ== +"postcss-normalize-display-values@^5.0.3": + "integrity" "sha512-FIV5FY/qs4Ja32jiDb5mVj5iWBlS3N8tFcw2yg98+8MkRgyhtnBgSC0lxU+16AMHbjX5fbSJgw5AXLMolonuRQ==" + "resolved" "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.3.tgz" + "version" "5.0.3" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-normalize-positions@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.4.tgz#4001f38c99675437b83277836fb4291887fcc6cc" - integrity sha512-qynirjBX0Lc73ROomZE3lzzmXXTu48/QiEzKgMeqh28+MfuHLsuqC9po4kj84igZqqFGovz8F8hf44hA3dPYmQ== +"postcss-normalize-positions@^5.0.4": + "integrity" "sha512-qynirjBX0Lc73ROomZE3lzzmXXTu48/QiEzKgMeqh28+MfuHLsuqC9po4kj84igZqqFGovz8F8hf44hA3dPYmQ==" + "resolved" "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-normalize-repeat-style@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.4.tgz#d005adf9ee45fae78b673031a376c0c871315145" - integrity sha512-Innt+wctD7YpfeDR7r5Ik6krdyppyAg2HBRpX88fo5AYzC1Ut/l3xaxACG0KsbX49cO2n5EB13clPwuYVt8cMA== +"postcss-normalize-repeat-style@^5.0.4": + "integrity" "sha512-Innt+wctD7YpfeDR7r5Ik6krdyppyAg2HBRpX88fo5AYzC1Ut/l3xaxACG0KsbX49cO2n5EB13clPwuYVt8cMA==" + "resolved" "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-normalize-string@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.4.tgz#b5e00a07597e7aa8a871817bfeac2bfaa59c3333" - integrity sha512-Dfk42l0+A1CDnVpgE606ENvdmksttLynEqTQf5FL3XGQOyqxjbo25+pglCUvziicTxjtI2NLUR6KkxyUWEVubQ== +"postcss-normalize-string@^5.0.4": + "integrity" "sha512-Dfk42l0+A1CDnVpgE606ENvdmksttLynEqTQf5FL3XGQOyqxjbo25+pglCUvziicTxjtI2NLUR6KkxyUWEVubQ==" + "resolved" "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-normalize-timing-functions@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.3.tgz#47210227bfcba5e52650d7a18654337090de7072" - integrity sha512-QRfjvFh11moN4PYnJ7hia4uJXeFotyK3t2jjg8lM9mswleGsNw2Lm3I5wO+l4k1FzK96EFwEVn8X8Ojrp2gP4g== +"postcss-normalize-timing-functions@^5.0.3": + "integrity" "sha512-QRfjvFh11moN4PYnJ7hia4uJXeFotyK3t2jjg8lM9mswleGsNw2Lm3I5wO+l4k1FzK96EFwEVn8X8Ojrp2gP4g==" + "resolved" "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.3.tgz" + "version" "5.0.3" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-normalize-unicode@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.4.tgz#02866096937005cdb2c17116c690f29505a1623d" - integrity sha512-W79Regn+a+eXTzB+oV/8XJ33s3pDyFTND2yDuUCo0Xa3QSy1HtNIfRVPXNubHxjhlqmMFADr3FSCHT84ITW3ig== +"postcss-normalize-unicode@^5.0.4": + "integrity" "sha512-W79Regn+a+eXTzB+oV/8XJ33s3pDyFTND2yDuUCo0Xa3QSy1HtNIfRVPXNubHxjhlqmMFADr3FSCHT84ITW3ig==" + "resolved" "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.4.tgz" + "version" "5.0.4" dependencies: - browserslist "^4.16.6" - postcss-value-parser "^4.2.0" + "browserslist" "^4.16.6" + "postcss-value-parser" "^4.2.0" -postcss-normalize-url@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.5.tgz#c39efc12ff119f6f45f0b4f516902b12c8080e3a" - integrity sha512-Ws3tX+PcekYlXh+ycAt0wyzqGthkvVtZ9SZLutMVvHARxcpu4o7vvXcNoiNKyjKuWecnjS6HDI3fjBuDr5MQxQ== +"postcss-normalize-url@^5.0.5": + "integrity" "sha512-Ws3tX+PcekYlXh+ycAt0wyzqGthkvVtZ9SZLutMVvHARxcpu4o7vvXcNoiNKyjKuWecnjS6HDI3fjBuDr5MQxQ==" + "resolved" "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.5.tgz" + "version" "5.0.5" dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" + "normalize-url" "^6.0.1" + "postcss-value-parser" "^4.2.0" -postcss-normalize-whitespace@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.4.tgz#1d477e7da23fecef91fc4e37d462272c7b55c5ca" - integrity sha512-wsnuHolYZjMwWZJoTC9jeI2AcjA67v4UuidDrPN9RnX8KIZfE+r2Nd6XZRwHVwUiHmRvKQtxiqo64K+h8/imaw== +"postcss-normalize-whitespace@^5.0.4": + "integrity" "sha512-wsnuHolYZjMwWZJoTC9jeI2AcjA67v4UuidDrPN9RnX8KIZfE+r2Nd6XZRwHVwUiHmRvKQtxiqo64K+h8/imaw==" + "resolved" "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-ordered-values@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.5.tgz#e878af822a130c3f3709737e24cb815ca7c6d040" - integrity sha512-mfY7lXpq+8bDEHfP+muqibDPhZ5eP9zgBEF9XRvoQgXcQe2Db3G1wcvjbnfjXG6wYsl+0UIjikqq4ym1V2jGMQ== +"postcss-ordered-values@^5.0.5": + "integrity" "sha512-mfY7lXpq+8bDEHfP+muqibDPhZ5eP9zgBEF9XRvoQgXcQe2Db3G1wcvjbnfjXG6wYsl+0UIjikqq4ym1V2jGMQ==" + "resolved" "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.5.tgz" + "version" "5.0.5" dependencies: - cssnano-utils "^3.0.2" - postcss-value-parser "^4.2.0" + "cssnano-utils" "^3.0.2" + "postcss-value-parser" "^4.2.0" -postcss-reduce-idents@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.0.3.tgz#b632796275b4fa1a4040799969dd17167eaf4d8b" - integrity sha512-9bj9/Xhwiti0Z35kkguJX4G6yUYVw8S1kRLU4jFSCTEuHu4yJggf4rNUoVnT45lm/vU97Wd593CxspMDbHxy4w== +"postcss-reduce-idents@^5.0.3": + "integrity" "sha512-9bj9/Xhwiti0Z35kkguJX4G6yUYVw8S1kRLU4jFSCTEuHu4yJggf4rNUoVnT45lm/vU97Wd593CxspMDbHxy4w==" + "resolved" "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.0.3.tgz" + "version" "5.0.3" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-reduce-initial@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.3.tgz#68891594defd648253703bbd8f1093162f19568d" - integrity sha512-c88TkSnQ/Dnwgb4OZbKPOBbCaauwEjbECP5uAuFPOzQ+XdjNjRH7SG0dteXrpp1LlIFEKK76iUGgmw2V0xeieA== +"postcss-reduce-initial@^5.0.3": + "integrity" "sha512-c88TkSnQ/Dnwgb4OZbKPOBbCaauwEjbECP5uAuFPOzQ+XdjNjRH7SG0dteXrpp1LlIFEKK76iUGgmw2V0xeieA==" + "resolved" "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.3.tgz" + "version" "5.0.3" dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" + "browserslist" "^4.16.6" + "caniuse-api" "^3.0.0" -postcss-reduce-transforms@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.4.tgz#717e72d30befe857f7d2784dba10eb1157863712" - integrity sha512-VIJB9SFSaL8B/B7AXb7KHL6/GNNbbCHslgdzS9UDfBZYIA2nx8NLY7iD/BXFSO/1sRUILzBTfHCoW5inP37C5g== +"postcss-reduce-transforms@^5.0.4": + "integrity" "sha512-VIJB9SFSaL8B/B7AXb7KHL6/GNNbbCHslgdzS9UDfBZYIA2nx8NLY7iD/BXFSO/1sRUILzBTfHCoW5inP37C5g==" + "resolved" "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" + "postcss-value-parser" "^4.2.0" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.9" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" - integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== +"postcss-selector-parser@^6.0.2", "postcss-selector-parser@^6.0.4", "postcss-selector-parser@^6.0.5", "postcss-selector-parser@^6.0.9": + "integrity" "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==" + "resolved" "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz" + "version" "6.0.9" dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" + "cssesc" "^3.0.0" + "util-deprecate" "^1.0.2" -postcss-sort-media-queries@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz#a99bae69ef1098ee3b64a5fa94d258ec240d0355" - integrity sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ== +"postcss-sort-media-queries@^4.1.0": + "integrity" "sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ==" + "resolved" "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz" + "version" "4.2.1" dependencies: - sort-css-media-queries "2.0.4" + "sort-css-media-queries" "2.0.4" -postcss-svgo@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.4.tgz#cfa8682f47b88f7cd75108ec499e133b43102abf" - integrity sha512-yDKHvULbnZtIrRqhZoA+rxreWpee28JSRH/gy9727u0UCgtpv1M/9WEWY3xySlFa0zQJcqf6oCBJPR5NwkmYpg== +"postcss-svgo@^5.0.4": + "integrity" "sha512-yDKHvULbnZtIrRqhZoA+rxreWpee28JSRH/gy9727u0UCgtpv1M/9WEWY3xySlFa0zQJcqf6oCBJPR5NwkmYpg==" + "resolved" "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" + "postcss-value-parser" "^4.2.0" + "svgo" "^2.7.0" -postcss-unique-selectors@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.4.tgz#08e188126b634ddfa615fb1d6c262bafdd64826e" - integrity sha512-5ampwoSDJCxDPoANBIlMgoBcYUHnhaiuLYJR5pj1DLnYQvMRVyFuTA5C3Bvt+aHtiqWpJkD/lXT50Vo1D0ZsAQ== +"postcss-unique-selectors@^5.0.4": + "integrity" "sha512-5ampwoSDJCxDPoANBIlMgoBcYUHnhaiuLYJR5pj1DLnYQvMRVyFuTA5C3Bvt+aHtiqWpJkD/lXT50Vo1D0ZsAQ==" + "resolved" "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.4.tgz" + "version" "5.0.4" dependencies: - postcss-selector-parser "^6.0.5" + "postcss-selector-parser" "^6.0.5" -postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +"postcss-value-parser@^4.1.0", "postcss-value-parser@^4.2.0": + "integrity" "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "resolved" "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + "version" "4.2.0" -postcss-zindex@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.0.2.tgz#7e48aee54062c93418593035229ea06b92381251" - integrity sha512-KPQFjQu73H35HLHmE8Wv31ygfQoucxD52oRm4FPFv1emYhFMzUQdF8adaXCevFLIHPRp2rRYfbaDiEqZ4YjVtw== +"postcss-zindex@^5.0.2": + "integrity" "sha512-KPQFjQu73H35HLHmE8Wv31ygfQoucxD52oRm4FPFv1emYhFMzUQdF8adaXCevFLIHPRp2rRYfbaDiEqZ4YjVtw==" + "resolved" "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.0.2.tgz" + "version" "5.0.2" -postcss@^8.3.11, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.5: - version "8.4.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" - integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== +"postcss@^7.0.0 || ^8.0.1", "postcss@^8.0.9", "postcss@^8.1.0", "postcss@^8.2.15", "postcss@^8.2.2", "postcss@^8.3.11", "postcss@^8.3.5", "postcss@^8.3.7", "postcss@^8.4.4", "postcss@^8.4.5": + "integrity" "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==" + "resolved" "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz" + "version" "8.4.6" dependencies: - nanoid "^3.2.0" - picocolors "^1.0.0" - source-map-js "^1.0.2" + "nanoid" "^3.2.0" + "picocolors" "^1.0.0" + "source-map-js" "^1.0.2" -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +"prepend-http@^2.0.0": + "integrity" "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + "resolved" "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" + "version" "2.0.0" -pretty-error@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" - integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== +"pretty-error@^4.0.0": + "integrity" "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==" + "resolved" "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz" + "version" "4.0.0" dependencies: - lodash "^4.17.20" - renderkid "^3.0.0" + "lodash" "^4.17.20" + "renderkid" "^3.0.0" -pretty-time@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" - integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== +"pretty-time@^1.1.0": + "integrity" "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" + "resolved" "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz" + "version" "1.1.0" -prism-react-renderer@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" - integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== +"prism-react-renderer@^1.2.1": + "integrity" "sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ==" + "resolved" "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz" + "version" "1.3.1" -prismjs@^1.23.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" - integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== +"prismjs@^1.23.0": + "integrity" "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==" + "resolved" "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz" + "version" "1.27.0" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +"process-nextick-args@~2.0.0": + "integrity" "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "resolved" "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + "version" "2.0.1" -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== +"promise@^7.1.1": + "integrity" "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==" + "resolved" "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz" + "version" "7.3.1" dependencies: - asap "~2.0.3" + "asap" "~2.0.3" -prompts@^2.4.1, prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== +"prompts@^2.4.1", "prompts@^2.4.2": + "integrity" "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==" + "resolved" "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + "version" "2.4.2" dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" + "kleur" "^3.0.3" + "sisteransi" "^1.0.5" -prop-types@^15.6.2, prop-types@^15.7.2: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== +"prop-types@^15.0.0", "prop-types@^15.6.2", "prop-types@^15.7.2": + "integrity" "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==" + "resolved" "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + "version" "15.8.1" dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" + "loose-envify" "^1.4.0" + "object-assign" "^4.1.1" + "react-is" "^16.13.1" -property-information@^5.0.0, property-information@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" - integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== +"property-information@^5.0.0", "property-information@^5.3.0": + "integrity" "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==" + "resolved" "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz" + "version" "5.6.0" dependencies: - xtend "^4.0.0" + "xtend" "^4.0.0" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== +"proxy-addr@~2.0.7": + "integrity" "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==" + "resolved" "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + "version" "2.0.7" dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" + "forwarded" "0.2.0" + "ipaddr.js" "1.9.1" -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== +"pump@^3.0.0": + "integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==" + "resolved" "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" + "version" "3.0.0" dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" + "end-of-stream" "^1.1.0" + "once" "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= +"punycode@^1.3.2": + "integrity" "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + "version" "1.4.1" -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +"punycode@^2.1.0": + "integrity" "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + "version" "2.1.1" -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +"punycode@1.3.2": + "integrity" "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + "resolved" "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + "version" "1.3.2" -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== +"pupa@^2.1.1": + "integrity" "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==" + "resolved" "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" + "version" "2.1.1" dependencies: - escape-goat "^2.0.0" + "escape-goat" "^2.0.0" -pure-color@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" - integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= +"pure-color@^1.2.0": + "integrity" "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" + "resolved" "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz" + "version" "1.3.0" -qs@6.9.7: - version "6.9.7" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" - integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== +"qs@6.9.7": + "integrity" "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + "resolved" "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz" + "version" "6.9.7" -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +"querystring@0.2.0": + "integrity" "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + "resolved" "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + "version" "0.2.0" -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +"queue-microtask@^1.2.2": + "integrity" "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + "resolved" "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + "version" "1.2.3" -queue@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" - integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== +"queue@6.0.2": + "integrity" "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==" + "resolved" "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz" + "version" "6.0.2" dependencies: - inherits "~2.0.3" + "inherits" "~2.0.3" -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== +"randombytes@^2.1.0": + "integrity" "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==" + "resolved" "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + "version" "2.1.0" dependencies: - safe-buffer "^5.1.0" + "safe-buffer" "^5.1.0" -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= +"range-parser@^1.2.1", "range-parser@~1.2.1": + "integrity" "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + "resolved" "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + "version" "1.2.1" -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +"range-parser@1.2.0": + "integrity" "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "resolved" "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz" + "version" "1.2.0" -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" - integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== +"raw-body@2.4.3": + "integrity" "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==" + "resolved" "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz" + "version" "2.4.3" dependencies: - bytes "3.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - unpipe "1.0.0" + "bytes" "3.1.2" + "http-errors" "1.8.1" + "iconv-lite" "0.4.24" + "unpipe" "1.0.0" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== +"rc@^1.2.8": + "integrity" "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==" + "resolved" "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" + "version" "1.2.8" dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" + "deep-extend" "^0.6.0" + "ini" "~1.3.0" + "minimist" "^1.2.0" + "strip-json-comments" "~2.0.1" -react-base16-styling@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" - integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw= +"react-base16-styling@^0.6.0": + "integrity" "sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=" + "resolved" "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz" + "version" "0.6.0" dependencies: - base16 "^1.0.0" - lodash.curry "^4.0.1" - lodash.flow "^3.3.0" - pure-color "^1.2.0" + "base16" "^1.0.0" + "lodash.curry" "^4.0.1" + "lodash.flow" "^3.3.0" + "pure-color" "^1.2.0" -react-dev-utils@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" - integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== +"react-dev-utils@^12.0.0": + "integrity" "sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ==" + "resolved" "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz" + "version" "12.0.0" dependencies: "@babel/code-frame" "^7.16.0" - address "^1.1.2" - browserslist "^4.18.1" - chalk "^4.1.2" - cross-spawn "^7.0.3" - detect-port-alt "^1.1.6" - escape-string-regexp "^4.0.0" - filesize "^8.0.6" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.5.0" - global-modules "^2.0.0" - globby "^11.0.4" - gzip-size "^6.0.0" - immer "^9.0.7" - is-root "^2.1.0" - loader-utils "^3.2.0" - open "^8.4.0" - pkg-up "^3.1.0" - prompts "^2.4.2" - react-error-overlay "^6.0.10" - recursive-readdir "^2.2.2" - shell-quote "^1.7.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" + "address" "^1.1.2" + "browserslist" "^4.18.1" + "chalk" "^4.1.2" + "cross-spawn" "^7.0.3" + "detect-port-alt" "^1.1.6" + "escape-string-regexp" "^4.0.0" + "filesize" "^8.0.6" + "find-up" "^5.0.0" + "fork-ts-checker-webpack-plugin" "^6.5.0" + "global-modules" "^2.0.0" + "globby" "^11.0.4" + "gzip-size" "^6.0.0" + "immer" "^9.0.7" + "is-root" "^2.1.0" + "loader-utils" "^3.2.0" + "open" "^8.4.0" + "pkg-up" "^3.1.0" + "prompts" "^2.4.2" + "react-error-overlay" "^6.0.10" + "recursive-readdir" "^2.2.2" + "shell-quote" "^1.7.3" + "strip-ansi" "^6.0.1" + "text-table" "^0.2.0" -react-dom@^16.10.2: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +"react-dom@*", "react-dom@^16.10.2", "react-dom@^16.8.4 || ^17.0.0", "react-dom@^17.0.0 || ^16.3.0 || ^15.5.4", "react-dom@>= 16.8.0 < 18.0.0": + "integrity" "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==" + "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz" + "version" "16.14.0" dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + "loose-envify" "^1.1.0" + "object-assign" "^4.1.1" + "prop-types" "^15.6.2" + "scheduler" "^0.19.1" -react-error-overlay@^6.0.10: - version "6.0.10" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" - integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +"react-error-overlay@^6.0.10": + "integrity" "sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==" + "resolved" "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz" + "version" "6.0.10" -react-fast-compare@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +"react-fast-compare@^3.1.1": + "integrity" "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + "resolved" "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz" + "version" "3.2.0" -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== +"react-helmet@^6.1.0": + "integrity" "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==" + "resolved" "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz" + "version" "6.1.0" dependencies: - object-assign "^4.1.1" - prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" + "object-assign" "^4.1.1" + "prop-types" "^15.7.2" + "react-fast-compare" "^3.1.1" + "react-side-effect" "^2.1.0" -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +"react-is@^16.13.1", "react-is@^16.6.0", "react-is@^16.7.0": + "integrity" "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "resolved" "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + "version" "16.13.1" -react-json-view@^1.21.3: - version "1.21.3" - resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" - integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw== +"react-json-view@^1.21.3": + "integrity" "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==" + "resolved" "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz" + "version" "1.21.3" dependencies: - flux "^4.0.1" - react-base16-styling "^0.6.0" - react-lifecycles-compat "^3.0.4" - react-textarea-autosize "^8.3.2" + "flux" "^4.0.1" + "react-base16-styling" "^0.6.0" + "react-lifecycles-compat" "^3.0.4" + "react-textarea-autosize" "^8.3.2" -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +"react-lifecycles-compat@^3.0.4": + "integrity" "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "resolved" "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" + "version" "3.0.4" -react-loadable-ssr-addon-v5-slorber@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" - integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== +"react-loadable-ssr-addon-v5-slorber@^1.0.1": + "integrity" "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==" + "resolved" "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz" + "version" "1.0.1" dependencies: "@babel/runtime" "^7.10.3" -react-popupbox@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/react-popupbox/-/react-popupbox-2.0.8.tgz#9e0c96dcf4ddbbea8d03c28ee6c0634f0d51b791" - integrity sha512-5DT0SxLMIchKgnUkdPwTzvFhtTL5SOQd6n5dzUnnELiimjFE8eaQwL1n58NZUxs9oJsHXF3qQNvcgwEfn8VHrw== +"react-loadable@*", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": + "integrity" "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==" + "resolved" "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz" + "version" "5.5.2" dependencies: - deepmerge "^1.3.2" - react "^16.3.1" + "@types/react" "*" + "prop-types" "^15.6.2" -react-router-config@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" - integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== +"react-popupbox@^2.0.8": + "integrity" "sha512-5DT0SxLMIchKgnUkdPwTzvFhtTL5SOQd6n5dzUnnELiimjFE8eaQwL1n58NZUxs9oJsHXF3qQNvcgwEfn8VHrw==" + "resolved" "https://registry.npmjs.org/react-popupbox/-/react-popupbox-2.0.8.tgz" + "version" "2.0.8" + dependencies: + "deepmerge" "^1.3.2" + "react" "^16.3.1" + +"react-router-config@^5.1.1": + "integrity" "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==" + "resolved" "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz" + "version" "5.1.1" dependencies: "@babel/runtime" "^7.1.2" -react-router-dom@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363" - integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ== +"react-router-dom@^5.2.0": + "integrity" "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==" + "resolved" "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz" + "version" "5.3.0" dependencies: "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.2.1" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "history" "^4.9.0" + "loose-envify" "^1.3.1" + "prop-types" "^15.6.2" + "react-router" "5.2.1" + "tiny-invariant" "^1.0.2" + "tiny-warning" "^1.0.0" -react-router@5.2.1, react-router@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" - integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ== +"react-router@^5.2.0", "react-router@>=5", "react-router@5.2.1": + "integrity" "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==" + "resolved" "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz" + "version" "5.2.1" dependencies: "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.4.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "history" "^4.9.0" + "hoist-non-react-statics" "^3.1.0" + "loose-envify" "^1.3.1" + "mini-create-react-context" "^0.4.0" + "path-to-regexp" "^1.7.0" + "prop-types" "^15.6.2" + "react-is" "^16.6.0" + "tiny-invariant" "^1.0.2" + "tiny-warning" "^1.0.0" -react-side-effect@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" - integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== +"react-side-effect@^2.1.0": + "integrity" "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==" + "resolved" "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz" + "version" "2.1.1" -react-textarea-autosize@^8.3.2: - version "8.3.3" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" - integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ== +"react-textarea-autosize@^8.3.2": + "integrity" "sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ==" + "resolved" "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz" + "version" "8.3.3" dependencies: "@babel/runtime" "^7.10.2" - use-composed-ref "^1.0.0" - use-latest "^1.0.0" + "use-composed-ref" "^1.0.0" + "use-latest" "^1.0.0" -react@^16.10.2, react@^16.3.1: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +"react@*", "react@^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", "react@^15.0.2 || ^16.0.0 || ^17.0.0", "react@^16.10.2", "react@^16.13.1", "react@^16.13.1 || ^17.0.0", "react@^16.14.0", "react@^16.3.0 || ^17.0.0", "react@^16.3.1", "react@^16.8.0 || ^17.0.0", "react@^16.8.4 || ^17.0.0", "react@^17.0.0 || ^16.3.0 || ^15.5.4", "react@>= 16.8.0 < 18.0.0", "react@>=0.14.9", "react@>=15", "react@>=16.3.0": + "integrity" "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==" + "resolved" "https://registry.npmjs.org/react/-/react-16.14.0.tgz" + "version" "16.14.0" dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" + "loose-envify" "^1.1.0" + "object-assign" "^4.1.1" + "prop-types" "^15.6.2" -readable-stream@^2.0.1: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +"readable-stream@^2.0.1": + "integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + "version" "2.3.7" dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" + "core-util-is" "~1.0.0" + "inherits" "~2.0.3" + "isarray" "~1.0.0" + "process-nextick-args" "~2.0.0" + "safe-buffer" "~5.1.1" + "string_decoder" "~1.1.1" + "util-deprecate" "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +"readable-stream@^3.0.6", "readable-stream@^3.1.1": + "integrity" "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" + "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + "version" "3.6.0" dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + "inherits" "^2.0.3" + "string_decoder" "^1.1.1" + "util-deprecate" "^1.0.1" -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== +"readdirp@~3.6.0": + "integrity" "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==" + "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + "version" "3.6.0" dependencies: - picomatch "^2.2.1" + "picomatch" "^2.2.1" -reading-time@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" - integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== +"reading-time@^1.5.0": + "integrity" "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + "resolved" "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz" + "version" "1.5.0" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= +"rechoir@^0.6.2": + "integrity" "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=" + "resolved" "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + "version" "0.6.2" dependencies: - resolve "^1.1.6" + "resolve" "^1.1.6" -recursive-readdir@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" - integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== +"recursive-readdir@^2.2.2": + "integrity" "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==" + "resolved" "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz" + "version" "2.2.2" dependencies: - minimatch "3.0.4" + "minimatch" "3.0.4" -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +"regenerate-unicode-properties@^10.0.1": + "integrity" "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==" + "resolved" "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" + "version" "10.0.1" dependencies: - regenerate "^1.4.2" + "regenerate" "^1.4.2" -regenerate-unicode-properties@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" - integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA== +"regenerate-unicode-properties@^9.0.0": + "integrity" "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==" + "resolved" "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz" + "version" "9.0.0" dependencies: - regenerate "^1.4.2" + "regenerate" "^1.4.2" -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +"regenerate@^1.4.2": + "integrity" "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "resolved" "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + "version" "1.4.2" -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +"regenerator-runtime@^0.13.4": + "integrity" "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "resolved" "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + "version" "0.13.9" -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== +"regenerator-transform@^0.14.2": + "integrity" "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==" + "resolved" "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz" + "version" "0.14.5" dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" - integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== +"regexp.prototype.flags@^1.2.0": + "integrity" "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==" + "resolved" "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz" + "version" "1.4.1" dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" + "call-bind" "^1.0.2" + "define-properties" "^1.1.3" -regexpu-core@^4.5.4: - version "4.8.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0" - integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg== +"regexpu-core@^4.5.4": + "integrity" "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==" + "resolved" "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz" + "version" "4.8.0" dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^9.0.0" - regjsgen "^0.5.2" - regjsparser "^0.7.0" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" + "regenerate" "^1.4.2" + "regenerate-unicode-properties" "^9.0.0" + "regjsgen" "^0.5.2" + "regjsparser" "^0.7.0" + "unicode-match-property-ecmascript" "^2.0.0" + "unicode-match-property-value-ecmascript" "^2.0.0" -regexpu-core@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" - integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== +"regexpu-core@^5.0.1": + "integrity" "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==" + "resolved" "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz" + "version" "5.0.1" dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" + "regenerate" "^1.4.2" + "regenerate-unicode-properties" "^10.0.1" + "regjsgen" "^0.6.0" + "regjsparser" "^0.8.2" + "unicode-match-property-ecmascript" "^2.0.0" + "unicode-match-property-value-ecmascript" "^2.0.0" -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== +"registry-auth-token@^4.0.0": + "integrity" "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==" + "resolved" "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz" + "version" "4.2.1" dependencies: - rc "^1.2.8" + "rc" "^1.2.8" -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== +"registry-url@^5.0.0": + "integrity" "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==" + "resolved" "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz" + "version" "5.1.0" dependencies: - rc "^1.2.8" + "rc" "^1.2.8" -regjsgen@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +"regjsgen@^0.5.2": + "integrity" "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" + "resolved" "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz" + "version" "0.5.2" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== +"regjsgen@^0.6.0": + "integrity" "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" + "resolved" "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" + "version" "0.6.0" -regjsparser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" - integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ== +"regjsparser@^0.7.0": + "integrity" "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==" + "resolved" "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz" + "version" "0.7.0" dependencies: - jsesc "~0.5.0" + "jsesc" "~0.5.0" -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +"regjsparser@^0.8.2": + "integrity" "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==" + "resolved" "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" + "version" "0.8.4" dependencies: - jsesc "~0.5.0" + "jsesc" "~0.5.0" -rehype-parse@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-6.0.2.tgz#aeb3fdd68085f9f796f1d3137ae2b85a98406964" - integrity sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug== +"rehype-parse@^6.0.2": + "integrity" "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==" + "resolved" "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz" + "version" "6.0.2" dependencies: - hast-util-from-parse5 "^5.0.0" - parse5 "^5.0.0" - xtend "^4.0.0" + "hast-util-from-parse5" "^5.0.0" + "parse5" "^5.0.0" + "xtend" "^4.0.0" -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +"relateurl@^0.2.7": + "integrity" "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + "resolved" "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" + "version" "0.2.7" -remark-admonitions@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/remark-admonitions/-/remark-admonitions-1.2.1.tgz#87caa1a442aa7b4c0cafa04798ed58a342307870" - integrity sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow== +"remark-admonitions@^1.2.1": + "integrity" "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==" + "resolved" "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz" + "version" "1.2.1" dependencies: - rehype-parse "^6.0.2" - unified "^8.4.2" - unist-util-visit "^2.0.1" + "rehype-parse" "^6.0.2" + "unified" "^8.4.2" + "unist-util-visit" "^2.0.1" -remark-emoji@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" - integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== +"remark-emoji@^2.1.0": + "integrity" "sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==" + "resolved" "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.2.0.tgz" + "version" "2.2.0" dependencies: - emoticon "^3.2.0" - node-emoji "^1.10.0" - unist-util-visit "^2.0.3" + "emoticon" "^3.2.0" + "node-emoji" "^1.10.0" + "unist-util-visit" "^2.0.3" -remark-footnotes@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" - integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== +"remark-footnotes@2.0.0": + "integrity" "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==" + "resolved" "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz" + "version" "2.0.0" -remark-mdx-remove-exports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-exports/-/remark-mdx-remove-exports-1.6.22.tgz#9e34f3d02c9c54b02ca0a1fde946449338d06ecb" - integrity sha512-7g2uiTmTGfz5QyVb+toeX25frbk1Y6yd03RXGPtqx0+DVh86Gb7MkNYbk7H2X27zdZ3CQv1W/JqlFO0Oo8IxVA== +"remark-mdx-remove-exports@^1.6.22": + "integrity" "sha512-7g2uiTmTGfz5QyVb+toeX25frbk1Y6yd03RXGPtqx0+DVh86Gb7MkNYbk7H2X27zdZ3CQv1W/JqlFO0Oo8IxVA==" + "resolved" "https://registry.npmjs.org/remark-mdx-remove-exports/-/remark-mdx-remove-exports-1.6.22.tgz" + "version" "1.6.22" dependencies: - unist-util-remove "2.0.0" + "unist-util-remove" "2.0.0" -remark-mdx-remove-imports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-imports/-/remark-mdx-remove-imports-1.6.22.tgz#79f711c95359cff437a120d1fbdc1326ec455826" - integrity sha512-lmjAXD8Ltw0TsvBzb45S+Dxx7LTJAtDaMneMAv8LAUIPEyYoKkmGbmVsiF0/pY6mhM1Q16swCmu1TN+ie/vn/A== +"remark-mdx-remove-imports@^1.6.22": + "integrity" "sha512-lmjAXD8Ltw0TsvBzb45S+Dxx7LTJAtDaMneMAv8LAUIPEyYoKkmGbmVsiF0/pY6mhM1Q16swCmu1TN+ie/vn/A==" + "resolved" "https://registry.npmjs.org/remark-mdx-remove-imports/-/remark-mdx-remove-imports-1.6.22.tgz" + "version" "1.6.22" dependencies: - unist-util-remove "2.0.0" + "unist-util-remove" "2.0.0" -remark-mdx@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" - integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== +"remark-mdx@1.6.22": + "integrity" "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==" + "resolved" "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz" + "version" "1.6.22" dependencies: "@babel/core" "7.12.9" "@babel/helper-plugin-utils" "7.10.4" "@babel/plugin-proposal-object-rest-spread" "7.12.1" "@babel/plugin-syntax-jsx" "7.12.1" "@mdx-js/util" "1.6.22" - is-alphabetical "1.0.4" - remark-parse "8.0.3" - unified "9.2.0" + "is-alphabetical" "1.0.4" + "remark-parse" "8.0.3" + "unified" "9.2.0" -remark-parse@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" - integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== +"remark-parse@8.0.3": + "integrity" "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==" + "resolved" "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz" + "version" "8.0.3" dependencies: - ccount "^1.0.0" - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^2.0.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^2.0.0" - vfile-location "^3.0.0" - xtend "^4.0.1" + "ccount" "^1.0.0" + "collapse-white-space" "^1.0.2" + "is-alphabetical" "^1.0.0" + "is-decimal" "^1.0.0" + "is-whitespace-character" "^1.0.0" + "is-word-character" "^1.0.0" + "markdown-escapes" "^1.0.0" + "parse-entities" "^2.0.0" + "repeat-string" "^1.5.4" + "state-toggle" "^1.0.0" + "trim" "0.0.1" + "trim-trailing-lines" "^1.0.0" + "unherit" "^1.0.4" + "unist-util-remove-position" "^2.0.0" + "vfile-location" "^3.0.0" + "xtend" "^4.0.1" -remark-squeeze-paragraphs@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" - integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== +"remark-squeeze-paragraphs@4.0.0": + "integrity" "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==" + "resolved" "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz" + "version" "4.0.0" dependencies: - mdast-squeeze-paragraphs "^4.0.0" + "mdast-squeeze-paragraphs" "^4.0.0" -remarkable-admonitions@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/remarkable-admonitions/-/remarkable-admonitions-0.2.2.tgz#8765f9ec66be4f4c651a4e1cfb559dd7f920819c" - integrity sha512-CcMTEcLYmJLXX3IVMk4LyW4oFD2NQxh5FeLzn4k89TAPpyWIeVix/B/g/gDbZAUpCNY9l6heovR5NNIktf8X5A== +"remarkable-admonitions@^0.2.1": + "integrity" "sha512-CcMTEcLYmJLXX3IVMk4LyW4oFD2NQxh5FeLzn4k89TAPpyWIeVix/B/g/gDbZAUpCNY9l6heovR5NNIktf8X5A==" + "resolved" "https://registry.npmjs.org/remarkable-admonitions/-/remarkable-admonitions-0.2.2.tgz" + "version" "0.2.2" -renderkid@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" - integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== +"renderkid@^3.0.0": + "integrity" "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==" + "resolved" "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz" + "version" "3.0.0" dependencies: - css-select "^4.1.3" - dom-converter "^0.2.0" - htmlparser2 "^6.1.0" - lodash "^4.17.21" - strip-ansi "^6.0.1" + "css-select" "^4.1.3" + "dom-converter" "^0.2.0" + "htmlparser2" "^6.1.0" + "lodash" "^4.17.21" + "strip-ansi" "^6.0.1" -repeat-string@^1.5.4: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +"repeat-string@^1.5.4": + "integrity" "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "resolved" "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + "version" "1.6.1" -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +"require-from-string@^2.0.2": + "integrity" "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + "resolved" "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + "version" "2.0.2" "require-like@>= 0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" - integrity sha1-rW8wwTvs15cBDEaK+ndcDAprR/o= + "integrity" "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" + "resolved" "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz" + "version" "0.1.2" -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +"requires-port@^1.0.0": + "integrity" "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + "resolved" "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + "version" "1.0.0" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +"resolve-from@^4.0.0": + "integrity" "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "resolved" "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + "version" "4.0.0" -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== +"resolve-pathname@^3.0.0": + "integrity" "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + "resolved" "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz" + "version" "3.0.0" -resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== +"resolve@^1.1.6", "resolve@^1.14.2", "resolve@^1.3.2": + "integrity" "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==" + "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz" + "version" "1.22.0" dependencies: - is-core-module "^2.8.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" + "is-core-module" "^2.8.1" + "path-parse" "^1.0.7" + "supports-preserve-symlinks-flag" "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= +"responselike@^1.0.2": + "integrity" "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=" + "resolved" "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz" + "version" "1.0.2" dependencies: - lowercase-keys "^1.0.0" + "lowercase-keys" "^1.0.0" -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== +"retry@^0.13.1": + "integrity" "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + "resolved" "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" + "version" "0.13.1" -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +"reusify@^1.0.4": + "integrity" "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "resolved" "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + "version" "1.0.4" -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== +"rimraf@^3.0.2": + "integrity" "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==" + "resolved" "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + "version" "3.0.2" dependencies: - glob "^7.1.3" + "glob" "^7.1.3" -rtl-detect@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" - integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== +"rtl-detect@^1.0.4": + "integrity" "sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==" + "resolved" "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.0.4.tgz" + "version" "1.0.4" -rtlcss@^3.3.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" - integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== +"rtlcss@^3.3.0": + "integrity" "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==" + "resolved" "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz" + "version" "3.5.0" dependencies: - find-up "^5.0.0" - picocolors "^1.0.0" - postcss "^8.3.11" - strip-json-comments "^3.1.1" + "find-up" "^5.0.0" + "picocolors" "^1.0.0" + "postcss" "^8.3.11" + "strip-json-comments" "^3.1.1" -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== +"run-parallel@^1.1.9": + "integrity" "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==" + "resolved" "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + "version" "1.2.0" dependencies: - queue-microtask "^1.2.2" + "queue-microtask" "^1.2.2" -rxjs@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.4.tgz#3d6bd407e6b7ce9a123e76b1e770dc5761aa368d" - integrity sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ== +"rxjs@^7.5.4": + "integrity" "sha512-h5M3Hk78r6wAheJF0a5YahB1yRQKCsZ4MsGdZ5O9ETbVtjPcScGfrMmoOq7EBsCRzd4BDkvDJ7ogP8Sz5tTFiQ==" + "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-7.5.4.tgz" + "version" "7.5.4" dependencies: - tslib "^2.1.0" + "tslib" "^2.1.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +"safe-buffer@^5.0.1", "safe-buffer@^5.1.0", "safe-buffer@>=5.1.0", "safe-buffer@~5.2.0", "safe-buffer@5.2.1": + "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + "version" "5.2.1" -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +"safe-buffer@~5.1.0", "safe-buffer@~5.1.1": + "integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + "version" "5.1.2" + +"safe-buffer@5.1.2": + "integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + "version" "5.1.2" "safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + "integrity" "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "resolved" "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + "version" "2.1.2" -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +"sax@^1.2.4": + "integrity" "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "resolved" "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + "version" "1.2.4" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +"scheduler@^0.19.1": + "integrity" "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==" + "resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz" + "version" "0.19.1" dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" + "loose-envify" "^1.1.0" + "object-assign" "^4.1.1" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - -schema-utils@^2.6.5: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== +"schema-utils@^2.6.5": + "integrity" "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==" + "resolved" "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz" + "version" "2.7.1" dependencies: "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" + "ajv" "^6.12.4" + "ajv-keywords" "^3.5.2" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +"schema-utils@^3.0.0", "schema-utils@^3.1.0", "schema-utils@^3.1.1": + "integrity" "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==" + "resolved" "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" + "version" "3.1.1" dependencies: "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" + "ajv" "^6.12.5" + "ajv-keywords" "^3.5.2" -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== +"schema-utils@^4.0.0": + "integrity" "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==" + "resolved" "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz" + "version" "4.0.0" dependencies: "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" + "ajv" "^8.8.0" + "ajv-formats" "^2.1.1" + "ajv-keywords" "^5.0.0" -section-matter@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" - integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== +"schema-utils@2.7.0": + "integrity" "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==" + "resolved" "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz" + "version" "2.7.0" dependencies: - extend-shallow "^2.0.1" - kind-of "^6.0.0" + "@types/json-schema" "^7.0.4" + "ajv" "^6.12.2" + "ajv-keywords" "^3.4.1" -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -selfsigned@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" - integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== +"section-matter@^1.0.0": + "integrity" "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==" + "resolved" "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz" + "version" "1.0.0" dependencies: - node-forge "^1.2.0" + "extend-shallow" "^2.0.1" + "kind-of" "^6.0.0" -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== +"select-hose@^2.0.0": + "integrity" "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" + "resolved" "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" + "version" "2.0.0" + +"selfsigned@^2.0.0": + "integrity" "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==" + "resolved" "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz" + "version" "2.0.0" dependencies: - semver "^6.3.0" + "node-forge" "^1.2.0" -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@^5.4.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +"semver-diff@^3.1.1": + "integrity" "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==" + "resolved" "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz" + "version" "3.1.1" dependencies: - lru-cache "^6.0.0" + "semver" "^6.3.0" -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== +"semver@^5.4.1": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"semver@^6.0.0", "semver@^6.1.1", "semver@^6.1.2", "semver@^6.2.0", "semver@^6.3.0": + "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + "version" "6.3.0" + +"semver@^7.3.2": + "integrity" "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + "version" "7.3.5" dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "1.8.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" + "lru-cache" "^6.0.0" -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +"semver@^7.3.4": + "integrity" "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + "version" "7.3.5" dependencies: - randombytes "^2.1.0" + "lru-cache" "^6.0.0" -serve-handler@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8" - integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w== +"semver@^7.3.5": + "integrity" "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + "version" "7.3.5" dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.0.4" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" + "lru-cache" "^6.0.0" -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= +"semver@7.0.0": + "integrity" "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" + "version" "7.0.0" + +"send@0.17.2": + "integrity" "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==" + "resolved" "https://registry.npmjs.org/send/-/send-0.17.2.tgz" + "version" "0.17.2" dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" + "debug" "2.6.9" + "depd" "~1.1.2" + "destroy" "~1.0.4" + "encodeurl" "~1.0.2" + "escape-html" "~1.0.3" + "etag" "~1.8.1" + "fresh" "0.5.2" + "http-errors" "1.8.1" + "mime" "1.6.0" + "ms" "2.1.3" + "on-finished" "~2.3.0" + "range-parser" "~1.2.1" + "statuses" "~1.5.0" -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== +"serialize-javascript@^6.0.0": + "integrity" "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==" + "resolved" "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" + "version" "6.0.0" dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.2" + "randombytes" "^2.1.0" -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== +"serve-handler@^6.1.3": + "integrity" "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==" + "resolved" "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz" + "version" "6.1.3" dependencies: - kind-of "^6.0.2" + "bytes" "3.0.0" + "content-disposition" "0.5.2" + "fast-url-parser" "1.1.3" + "mime-types" "2.1.18" + "minimatch" "3.0.4" + "path-is-inside" "1.0.2" + "path-to-regexp" "2.2.1" + "range-parser" "1.2.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== +"serve-index@^1.9.1": + "integrity" "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=" + "resolved" "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz" + "version" "1.9.1" dependencies: - shebang-regex "^3.0.0" + "accepts" "~1.3.4" + "batch" "0.6.1" + "debug" "2.6.9" + "escape-html" "~1.0.3" + "http-errors" "~1.6.2" + "mime-types" "~2.1.17" + "parseurl" "~1.3.2" -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - -shelljs@^0.8.4: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== +"serve-static@1.14.2": + "integrity" "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==" + "resolved" "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz" + "version" "1.14.2" dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" + "encodeurl" "~1.0.2" + "escape-html" "~1.0.3" + "parseurl" "~1.3.3" + "send" "0.17.2" -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +"setimmediate@^1.0.5": + "integrity" "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "resolved" "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" + "version" "1.0.5" -sirv@^1.0.7: - version "1.0.19" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" - integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== +"setprototypeof@1.1.0": + "integrity" "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "resolved" "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + "version" "1.1.0" + +"setprototypeof@1.2.0": + "integrity" "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "resolved" "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + "version" "1.2.0" + +"shallow-clone@^3.0.0": + "integrity" "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==" + "resolved" "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" + "version" "3.0.1" + dependencies: + "kind-of" "^6.0.2" + +"shebang-command@^2.0.0": + "integrity" "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==" + "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + "version" "2.0.0" + dependencies: + "shebang-regex" "^3.0.0" + +"shebang-regex@^3.0.0": + "integrity" "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + "version" "3.0.0" + +"shell-quote@^1.7.3": + "integrity" "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==" + "resolved" "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz" + "version" "1.7.3" + +"shelljs@^0.8.4": + "integrity" "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==" + "resolved" "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz" + "version" "0.8.5" + dependencies: + "glob" "^7.0.0" + "interpret" "^1.0.0" + "rechoir" "^0.6.2" + +"signal-exit@^3.0.2", "signal-exit@^3.0.3": + "integrity" "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + "version" "3.0.7" + +"sirv@^1.0.7": + "integrity" "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==" + "resolved" "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz" + "version" "1.0.19" dependencies: "@polka/url" "^1.0.0-next.20" - mrmime "^1.0.0" - totalist "^1.0.0" + "mrmime" "^1.0.0" + "totalist" "^1.0.0" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +"sisteransi@^1.0.5": + "integrity" "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + "resolved" "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + "version" "1.0.5" -sitemap@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" - integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== +"sitemap@^7.0.0": + "integrity" "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==" + "resolved" "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz" + "version" "7.1.1" dependencies: "@types/node" "^17.0.5" "@types/sax" "^1.2.1" - arg "^5.0.0" - sax "^1.2.4" + "arg" "^5.0.0" + "sax" "^1.2.4" -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +"slash@^3.0.0": + "integrity" "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "resolved" "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + "version" "3.0.0" -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +"slash@^4.0.0": + "integrity" "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" + "resolved" "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" + "version" "4.0.0" -sockjs@^0.3.21: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== +"sockjs@^0.3.21": + "integrity" "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==" + "resolved" "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz" + "version" "0.3.24" dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" + "faye-websocket" "^0.11.3" + "uuid" "^8.3.2" + "websocket-driver" "^0.7.4" -sort-css-media-queries@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.0.4.tgz#b2badfa519cb4a938acbc6d3aaa913d4949dc908" - integrity sha512-PAIsEK/XupCQwitjv7XxoMvYhT7EAfyzI3hsy/MyDgTvc+Ft55ctdkctJLOy6cQejaIC+zjpUL4djFVm2ivOOw== +"sort-css-media-queries@2.0.4": + "integrity" "sha512-PAIsEK/XupCQwitjv7XxoMvYhT7EAfyzI3hsy/MyDgTvc+Ft55ctdkctJLOy6cQejaIC+zjpUL4djFVm2ivOOw==" + "resolved" "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.0.4.tgz" + "version" "2.0.4" -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +"source-list-map@^2.0.0": + "integrity" "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + "resolved" "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz" + "version" "2.0.1" -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +"source-map-js@^1.0.2": + "integrity" "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "resolved" "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + "version" "1.0.2" -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== +"source-map-support@~0.5.20": + "integrity" "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==" + "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + "version" "0.5.21" dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" + "buffer-from" "^1.0.0" + "source-map" "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +"source-map@^0.5.0": + "integrity" "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + "version" "0.5.7" -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +"source-map@^0.6.0", "source-map@^0.6.1", "source-map@~0.6.0", "source-map@~0.6.1": + "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "version" "0.6.1" -sourcemap-codec@^1.4.4: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +"sourcemap-codec@^1.4.4": + "integrity" "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + "resolved" "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" + "version" "1.4.8" -space-separated-tokens@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" - integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== +"space-separated-tokens@^1.0.0": + "integrity" "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + "resolved" "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz" + "version" "1.1.5" -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== +"spdy-transport@^3.0.0": + "integrity" "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==" + "resolved" "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz" + "version" "3.0.0" dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" + "debug" "^4.1.0" + "detect-node" "^2.0.4" + "hpack.js" "^2.1.6" + "obuf" "^1.1.2" + "readable-stream" "^3.0.6" + "wbuf" "^1.7.3" -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== +"spdy@^4.0.2": + "integrity" "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==" + "resolved" "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz" + "version" "4.0.2" dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" + "debug" "^4.1.0" + "handle-thing" "^2.0.0" + "http-deceiver" "^1.2.7" + "select-hose" "^2.0.0" + "spdy-transport" "^3.0.0" -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +"sprintf-js@~1.0.2": + "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + "version" "1.0.3" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +"stable@^0.1.8": + "integrity" "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + "resolved" "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" + "version" "0.1.8" -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== +"state-toggle@^1.0.0": + "integrity" "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" + "resolved" "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz" + "version" "1.0.3" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", "statuses@~1.5.0": + "integrity" "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "resolved" "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + "version" "1.5.0" -std-env@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.1.tgz#bc4cbc0e438610197e34c2d79c3df30b491f5182" - integrity sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw== +"std-env@^3.0.1": + "integrity" "sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw==" + "resolved" "https://registry.npmjs.org/std-env/-/std-env-3.0.1.tgz" + "version" "3.0.1" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== +"string_decoder@^1.1.1": + "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" + "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + "version" "1.3.0" dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" + "safe-buffer" "~5.2.0" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== +"string_decoder@~1.1.1": + "integrity" "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==" + "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + "version" "1.1.1" dependencies: - safe-buffer "~5.2.0" + "safe-buffer" "~5.1.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +"string-width@^4.0.0", "string-width@^4.1.0", "string-width@^4.2.2": + "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + "version" "4.2.3" dependencies: - safe-buffer "~5.1.0" + "emoji-regex" "^8.0.0" + "is-fullwidth-code-point" "^3.0.0" + "strip-ansi" "^6.0.1" -stringify-object@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== +"stringify-object@^3.3.0": + "integrity" "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==" + "resolved" "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz" + "version" "3.3.0" dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" + "get-own-enumerable-property-symbols" "^3.0.0" + "is-obj" "^1.0.1" + "is-regexp" "^1.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +"strip-ansi@^6.0.0", "strip-ansi@^6.0.1": + "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + "version" "6.0.1" dependencies: - ansi-regex "^5.0.1" + "ansi-regex" "^5.0.1" -strip-ansi@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" - integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== +"strip-ansi@^7.0.0": + "integrity" "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz" + "version" "7.0.1" dependencies: - ansi-regex "^6.0.1" + "ansi-regex" "^6.0.1" -strip-bom-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= +"strip-bom-string@^1.0.0": + "integrity" "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + "resolved" "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz" + "version" "1.0.0" -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +"strip-final-newline@^2.0.0": + "integrity" "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + "resolved" "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + "version" "2.0.0" -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +"strip-json-comments@^3.1.1": + "integrity" "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + "version" "3.1.1" -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +"strip-json-comments@~2.0.1": + "integrity" "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + "version" "2.0.1" -style-to-object@0.3.0, style-to-object@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" - integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== +"style-to-object@^0.3.0", "style-to-object@0.3.0": + "integrity" "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==" + "resolved" "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz" + "version" "0.3.0" dependencies: - inline-style-parser "0.1.1" + "inline-style-parser" "0.1.1" -stylehacks@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.3.tgz#2ef3de567bfa2be716d29a93bf3d208c133e8d04" - integrity sha512-ENcUdpf4yO0E1rubu8rkxI+JGQk4CgjchynZ4bDBJDfqdy+uhTRSWb8/F3Jtu+Bw5MW45Po3/aQGeIyyxgQtxg== +"stylehacks@^5.0.3": + "integrity" "sha512-ENcUdpf4yO0E1rubu8rkxI+JGQk4CgjchynZ4bDBJDfqdy+uhTRSWb8/F3Jtu+Bw5MW45Po3/aQGeIyyxgQtxg==" + "resolved" "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.3.tgz" + "version" "5.0.3" dependencies: - browserslist "^4.16.6" - postcss-selector-parser "^6.0.4" + "browserslist" "^4.16.6" + "postcss-selector-parser" "^6.0.4" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== +"supports-color@^5.3.0": + "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "version" "5.5.0" dependencies: - has-flag "^3.0.0" + "has-flag" "^3.0.0" -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== +"supports-color@^7.1.0": + "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + "version" "7.2.0" dependencies: - has-flag "^4.0.0" + "has-flag" "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== +"supports-color@^8.0.0": + "integrity" "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + "version" "8.1.1" dependencies: - has-flag "^4.0.0" + "has-flag" "^4.0.0" -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +"supports-preserve-symlinks-flag@^1.0.0": + "integrity" "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "resolved" "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + "version" "1.0.0" -svg-parser@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" - integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +"svg-parser@^2.0.2": + "integrity" "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + "resolved" "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz" + "version" "2.0.4" -svgo@^2.5.0, svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== +"svgo@^2.5.0", "svgo@^2.7.0": + "integrity" "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==" + "resolved" "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz" + "version" "2.8.0" dependencies: "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" + "commander" "^7.2.0" + "css-select" "^4.1.3" + "css-tree" "^1.1.3" + "csso" "^4.2.0" + "picocolors" "^1.0.0" + "stable" "^0.1.8" -tapable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +"tapable@^1.0.0": + "integrity" "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + "resolved" "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz" + "version" "1.1.3" -tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +"tapable@^2.0.0", "tapable@^2.1.1", "tapable@^2.2.0": + "integrity" "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + "resolved" "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + "version" "2.2.1" -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: - version "5.3.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" - integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== +"terser-webpack-plugin@^5.1.3", "terser-webpack-plugin@^5.2.4": + "integrity" "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==" + "resolved" "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz" + "version" "5.3.1" dependencies: - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.2" + "jest-worker" "^27.4.5" + "schema-utils" "^3.1.1" + "serialize-javascript" "^6.0.0" + "source-map" "^0.6.1" + "terser" "^5.7.2" -terser@^5.10.0, terser@^5.7.2: - version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" - integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== +"terser@^5.10.0", "terser@^5.7.2": + "integrity" "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==" + "resolved" "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz" + "version" "5.14.2" dependencies: "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" + "acorn" "^8.5.0" + "commander" "^2.20.0" + "source-map-support" "~0.5.20" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +"text-table@^0.2.0": + "integrity" "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + "resolved" "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + "version" "0.2.0" -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +"thunky@^1.0.2": + "integrity" "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "resolved" "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" + "version" "1.1.0" -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +"timsort@^0.3.0": + "integrity" "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + "resolved" "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" + "version" "0.3.0" -tiny-invariant@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" - integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== +"tiny-invariant@^1.0.2": + "integrity" "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + "resolved" "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz" + "version" "1.2.0" -tiny-warning@^1.0.0, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +"tiny-warning@^1.0.0", "tiny-warning@^1.0.3": + "integrity" "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + "resolved" "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" + "version" "1.0.3" -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +"to-fast-properties@^2.0.0": + "integrity" "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "resolved" "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + "version" "2.0.0" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== +"to-readable-stream@^1.0.0": + "integrity" "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + "resolved" "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz" + "version" "1.0.0" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== +"to-regex-range@^5.0.1": + "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + "version" "5.0.1" dependencies: - is-number "^7.0.0" + "is-number" "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +"toidentifier@1.0.1": + "integrity" "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + "resolved" "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + "version" "1.0.1" -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +"totalist@^1.0.0": + "integrity" "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==" + "resolved" "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz" + "version" "1.1.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +"tr46@~0.0.3": + "integrity" "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + "resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + "version" "0.0.3" -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== +"trim-trailing-lines@^1.0.0": + "integrity" "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==" + "resolved" "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz" + "version" "1.1.4" -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= +"trim@0.0.1": + "integrity" "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + "resolved" "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz" + "version" "0.0.1" -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +"trough@^1.0.0": + "integrity" "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" + "resolved" "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz" + "version" "1.0.5" -tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +"tslib@^2.0.3", "tslib@^2.1.0", "tslib@^2.2.0", "tslib@^2.3.1": + "integrity" "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" + "version" "2.3.1" -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +"type-fest@^0.20.2": + "integrity" "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + "version" "0.20.2" -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== +"type-is@~1.6.18": + "integrity" "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==" + "resolved" "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + "version" "1.6.18" dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" + "media-typer" "0.3.0" + "mime-types" "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== +"typedarray-to-buffer@^3.1.5": + "integrity" "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==" + "resolved" "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + "version" "3.1.5" dependencies: - is-typedarray "^1.0.0" + "is-typedarray" "^1.0.0" -ua-parser-js@^0.7.30: - version "0.7.33" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" - integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== +"typescript@>= 2.7": + "integrity" "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" + "version" "4.9.5" -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== +"ua-parser-js@^0.7.30": + "integrity" "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==" + "resolved" "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz" + "version" "0.7.33" + +"unherit@^1.0.4": + "integrity" "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==" + "resolved" "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz" + "version" "1.1.3" dependencies: - inherits "^2.0.0" - xtend "^4.0.0" + "inherits" "^2.0.0" + "xtend" "^4.0.0" -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== +"unicode-canonical-property-names-ecmascript@^2.0.0": + "integrity" "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" + "resolved" "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + "version" "2.0.0" -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== +"unicode-match-property-ecmascript@^2.0.0": + "integrity" "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==" + "resolved" "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + "version" "2.0.0" dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" + "unicode-canonical-property-names-ecmascript" "^2.0.0" + "unicode-property-aliases-ecmascript" "^2.0.0" -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +"unicode-match-property-value-ecmascript@^2.0.0": + "integrity" "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" + "resolved" "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" + "version" "2.0.0" -unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== +"unicode-property-aliases-ecmascript@^2.0.0": + "integrity" "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "resolved" "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" + "version" "2.0.0" -unified@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" - integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== +"unified@^8.4.2": + "integrity" "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==" + "resolved" "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz" + "version" "8.4.2" dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" + "bail" "^1.0.0" + "extend" "^3.0.0" + "is-plain-obj" "^2.0.0" + "trough" "^1.0.0" + "vfile" "^4.0.0" -unified@^8.4.2: - version "8.4.2" - resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1" - integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA== +"unified@9.2.0": + "integrity" "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==" + "resolved" "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz" + "version" "9.2.0" dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" + "bail" "^1.0.0" + "extend" "^3.0.0" + "is-buffer" "^2.0.0" + "is-plain-obj" "^2.0.0" + "trough" "^1.0.0" + "vfile" "^4.0.0" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== +"unique-string@^2.0.0": + "integrity" "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==" + "resolved" "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" + "version" "2.0.0" dependencies: - crypto-random-string "^2.0.0" + "crypto-random-string" "^2.0.0" -unist-builder@2.0.3, unist-builder@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" - integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== +"unist-builder@^2.0.0", "unist-builder@2.0.3": + "integrity" "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" + "resolved" "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz" + "version" "2.0.3" -unist-util-generated@^1.0.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" - integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== +"unist-util-generated@^1.0.0": + "integrity" "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==" + "resolved" "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz" + "version" "1.1.6" -unist-util-is@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" - integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== +"unist-util-is@^4.0.0": + "integrity" "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==" + "resolved" "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz" + "version" "4.1.0" -unist-util-position@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" - integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== +"unist-util-position@^3.0.0": + "integrity" "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" + "resolved" "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz" + "version" "3.1.0" -unist-util-remove-position@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" - integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== +"unist-util-remove-position@^2.0.0": + "integrity" "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==" + "resolved" "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz" + "version" "2.0.1" dependencies: - unist-util-visit "^2.0.0" + "unist-util-visit" "^2.0.0" -unist-util-remove@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.0.0.tgz#32c2ad5578802f2ca62ab808173d505b2c898488" - integrity sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g== +"unist-util-remove@^2.0.0": + "integrity" "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==" + "resolved" "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz" + "version" "2.1.0" dependencies: - unist-util-is "^4.0.0" + "unist-util-is" "^4.0.0" -unist-util-remove@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" - integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== +"unist-util-remove@2.0.0": + "integrity" "sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g==" + "resolved" "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.0.0.tgz" + "version" "2.0.0" dependencies: - unist-util-is "^4.0.0" + "unist-util-is" "^4.0.0" -unist-util-stringify-position@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" - integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== +"unist-util-stringify-position@^2.0.0": + "integrity" "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==" + "resolved" "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz" + "version" "2.0.3" dependencies: "@types/unist" "^2.0.2" -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== +"unist-util-visit-parents@^3.0.0": + "integrity" "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==" + "resolved" "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz" + "version" "3.1.1" dependencies: "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" + "unist-util-is" "^4.0.0" -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.1, unist-util-visit@^2.0.2, unist-util-visit@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== +"unist-util-visit@^2.0.0", "unist-util-visit@^2.0.1", "unist-util-visit@^2.0.2", "unist-util-visit@^2.0.3", "unist-util-visit@2.0.3": + "integrity" "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==" + "resolved" "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz" + "version" "2.0.3" dependencies: "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" + "unist-util-is" "^4.0.0" + "unist-util-visit-parents" "^3.0.0" -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +"universalify@^2.0.0": + "integrity" "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "resolved" "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" + "version" "2.0.0" -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +"unpipe@~1.0.0", "unpipe@1.0.0": + "integrity" "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "resolved" "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + "version" "1.0.0" -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== +"update-browserslist-db@^1.0.10": + "integrity" "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==" + "resolved" "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz" + "version" "1.0.10" dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" + "escalade" "^3.1.1" + "picocolors" "^1.0.0" -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== +"update-notifier@^5.1.0": + "integrity" "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==" + "resolved" "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz" + "version" "5.1.0" dependencies: - punycode "^2.1.0" + "boxen" "^5.0.0" + "chalk" "^4.1.0" + "configstore" "^5.0.1" + "has-yarn" "^2.1.0" + "import-lazy" "^2.1.0" + "is-ci" "^2.0.0" + "is-installed-globally" "^0.4.0" + "is-npm" "^5.0.0" + "is-yarn-global" "^0.3.0" + "latest-version" "^5.1.0" + "pupa" "^2.1.1" + "semver" "^7.3.4" + "semver-diff" "^3.1.1" + "xdg-basedir" "^4.0.0" -url-loader@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" - integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== +"uri-js@^4.2.2": + "integrity" "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==" + "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + "version" "4.4.1" dependencies: - loader-utils "^2.0.0" - mime-types "^2.1.27" - schema-utils "^3.0.0" + "punycode" "^2.1.0" -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= +"url-loader@^4.1.1": + "integrity" "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==" + "resolved" "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz" + "version" "4.1.1" dependencies: - prepend-http "^2.0.0" + "loader-utils" "^2.0.0" + "mime-types" "^2.1.27" + "schema-utils" "^3.0.0" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= +"url-parse-lax@^3.0.0": + "integrity" "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=" + "resolved" "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz" + "version" "3.0.0" dependencies: - punycode "1.3.2" - querystring "0.2.0" + "prepend-http" "^2.0.0" -use-composed-ref@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" - integrity sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw== - -use-isomorphic-layout-effect@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225" - integrity sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ== - -use-latest@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" - integrity sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw== +"url@^0.11.0": + "integrity" "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=" + "resolved" "https://registry.npmjs.org/url/-/url-0.11.0.tgz" + "version" "0.11.0" dependencies: - use-isomorphic-layout-effect "^1.0.0" + "punycode" "1.3.2" + "querystring" "0.2.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +"use-composed-ref@^1.0.0": + "integrity" "sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==" + "resolved" "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.2.1.tgz" + "version" "1.2.1" -utila@~0.4: - version "0.4.0" - resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= +"use-isomorphic-layout-effect@^1.0.0": + "integrity" "sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ==" + "resolved" "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz" + "version" "1.1.1" -utility-types@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" - integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== +"use-latest@^1.0.0": + "integrity" "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==" + "resolved" "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz" + "version" "1.2.0" + dependencies: + "use-isomorphic-layout-effect" "^1.0.0" -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +"util-deprecate@^1.0.1", "util-deprecate@^1.0.2", "util-deprecate@~1.0.1": + "integrity" "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + "version" "1.0.2" -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +"utila@~0.4": + "integrity" "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + "resolved" "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz" + "version" "0.4.0" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== +"utility-types@^3.10.0": + "integrity" "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + "resolved" "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz" + "version" "3.10.0" -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +"utils-merge@1.0.1": + "integrity" "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "resolved" "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + "version" "1.0.1" -vfile-location@^3.0.0, vfile-location@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" - integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== +"uuid@^8.3.2": + "integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + "version" "8.3.2" -vfile-message@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" - integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== +"value-equal@^1.0.1": + "integrity" "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + "resolved" "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz" + "version" "1.0.1" + +"vary@~1.1.2": + "integrity" "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "resolved" "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + "version" "1.1.2" + +"vfile-location@^3.0.0", "vfile-location@^3.2.0": + "integrity" "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==" + "resolved" "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz" + "version" "3.2.0" + +"vfile-message@^2.0.0": + "integrity" "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==" + "resolved" "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz" + "version" "2.0.4" dependencies: "@types/unist" "^2.0.0" - unist-util-stringify-position "^2.0.0" + "unist-util-stringify-position" "^2.0.0" -vfile@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" - integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== +"vfile@^4.0.0": + "integrity" "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==" + "resolved" "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz" + "version" "4.2.1" dependencies: "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^2.0.0" - vfile-message "^2.0.0" + "is-buffer" "^2.0.0" + "unist-util-stringify-position" "^2.0.0" + "vfile-message" "^2.0.0" -wait-on@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" - integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== +"wait-on@^6.0.0": + "integrity" "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==" + "resolved" "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz" + "version" "6.0.1" dependencies: - axios "^0.25.0" - joi "^17.6.0" - lodash "^4.17.21" - minimist "^1.2.5" - rxjs "^7.5.4" + "axios" "^0.25.0" + "joi" "^17.6.0" + "lodash" "^4.17.21" + "minimist" "^1.2.5" + "rxjs" "^7.5.4" -watchpack@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== +"watchpack@^2.3.1": + "integrity" "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==" + "resolved" "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz" + "version" "2.3.1" dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" + "glob-to-regexp" "^0.4.1" + "graceful-fs" "^4.1.2" -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== +"wbuf@^1.1.0", "wbuf@^1.7.3": + "integrity" "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==" + "resolved" "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz" + "version" "1.7.3" dependencies: - minimalistic-assert "^1.0.0" + "minimalistic-assert" "^1.0.0" -web-namespaces@^1.0.0, web-namespaces@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" - integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== +"web-namespaces@^1.0.0", "web-namespaces@^1.1.2": + "integrity" "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" + "resolved" "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz" + "version" "1.1.4" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= +"webidl-conversions@^3.0.0": + "integrity" "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + "version" "3.0.1" -webpack-bundle-analyzer@^4.4.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" - integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== +"webpack-bundle-analyzer@^4.4.2": + "integrity" "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==" + "resolved" "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz" + "version" "4.5.0" dependencies: - acorn "^8.0.4" - acorn-walk "^8.0.0" - chalk "^4.1.0" - commander "^7.2.0" - gzip-size "^6.0.0" - lodash "^4.17.20" - opener "^1.5.2" - sirv "^1.0.7" - ws "^7.3.1" + "acorn" "^8.0.4" + "acorn-walk" "^8.0.0" + "chalk" "^4.1.0" + "commander" "^7.2.0" + "gzip-size" "^6.0.0" + "lodash" "^4.17.20" + "opener" "^1.5.2" + "sirv" "^1.0.7" + "ws" "^7.3.1" -webpack-dev-middleware@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" - integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== +"webpack-dev-middleware@^5.3.1": + "integrity" "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==" + "resolved" "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz" + "version" "5.3.1" dependencies: - colorette "^2.0.10" - memfs "^3.4.1" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" + "colorette" "^2.0.10" + "memfs" "^3.4.1" + "mime-types" "^2.1.31" + "range-parser" "^1.2.1" + "schema-utils" "^4.0.0" -webpack-dev-server@^4.7.1: - version "4.7.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" - integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== +"webpack-dev-server@^4.7.1": + "integrity" "sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A==" + "resolved" "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz" + "version" "4.7.4" dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" @@ -7519,212 +7582,217 @@ webpack-dev-server@^4.7.1: "@types/serve-index" "^1.9.1" "@types/sockjs" "^0.3.33" "@types/ws" "^8.2.2" - ansi-html-community "^0.0.8" - bonjour "^3.5.0" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^1.6.0" - default-gateway "^6.0.3" - del "^6.0.0" - express "^4.17.1" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.0" - ipaddr.js "^2.0.1" - open "^8.0.9" - p-retry "^4.5.0" - portfinder "^1.0.28" - schema-utils "^4.0.0" - selfsigned "^2.0.0" - serve-index "^1.9.1" - sockjs "^0.3.21" - spdy "^4.0.2" - strip-ansi "^7.0.0" - webpack-dev-middleware "^5.3.1" - ws "^8.4.2" + "ansi-html-community" "^0.0.8" + "bonjour" "^3.5.0" + "chokidar" "^3.5.3" + "colorette" "^2.0.10" + "compression" "^1.7.4" + "connect-history-api-fallback" "^1.6.0" + "default-gateway" "^6.0.3" + "del" "^6.0.0" + "express" "^4.17.1" + "graceful-fs" "^4.2.6" + "html-entities" "^2.3.2" + "http-proxy-middleware" "^2.0.0" + "ipaddr.js" "^2.0.1" + "open" "^8.0.9" + "p-retry" "^4.5.0" + "portfinder" "^1.0.28" + "schema-utils" "^4.0.0" + "selfsigned" "^2.0.0" + "serve-index" "^1.9.1" + "sockjs" "^0.3.21" + "spdy" "^4.0.2" + "strip-ansi" "^7.0.0" + "webpack-dev-middleware" "^5.3.1" + "ws" "^8.4.2" -webpack-merge@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== +"webpack-merge@^5.8.0": + "integrity" "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==" + "resolved" "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz" + "version" "5.8.0" dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" + "clone-deep" "^4.0.1" + "wildcard" "^2.0.0" -webpack-sources@^1.1.0, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== +"webpack-sources@^1.1.0", "webpack-sources@^1.4.3": + "integrity" "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==" + "resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz" + "version" "1.4.3" dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" + "source-list-map" "^2.0.0" + "source-map" "~0.6.1" -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== +"webpack-sources@^3.2.3": + "integrity" "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" + "resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" + "version" "3.2.3" -webpack@^5.61.0: - version "5.69.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.1.tgz#8cfd92c192c6a52c99ab00529b5a0d33aa848dc5" - integrity sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A== +"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", "webpack@^4.4.0 || ^5.0.0", "webpack@^5.0.0", "webpack@^5.1.0", "webpack@^5.20.0", "webpack@^5.61.0", "webpack@>= 4", "webpack@>=2", "webpack@>=4.41.1 || 5.x", "webpack@3 || 4 || 5", "webpack@5.x": + "integrity" "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==" + "resolved" "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz" + "version" "5.69.1" dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" - webpack-sources "^3.2.3" + "acorn" "^8.4.1" + "acorn-import-assertions" "^1.7.6" + "browserslist" "^4.14.5" + "chrome-trace-event" "^1.0.2" + "enhanced-resolve" "^5.8.3" + "es-module-lexer" "^0.9.0" + "eslint-scope" "5.1.1" + "events" "^3.2.0" + "glob-to-regexp" "^0.4.1" + "graceful-fs" "^4.2.9" + "json-parse-better-errors" "^1.0.2" + "loader-runner" "^4.2.0" + "mime-types" "^2.1.27" + "neo-async" "^2.6.2" + "schema-utils" "^3.1.0" + "tapable" "^2.1.1" + "terser-webpack-plugin" "^5.1.3" + "watchpack" "^2.3.1" + "webpack-sources" "^3.2.3" -webpackbar@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" - integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== +"webpackbar@^5.0.2": + "integrity" "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==" + "resolved" "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz" + "version" "5.0.2" dependencies: - chalk "^4.1.0" - consola "^2.15.3" - pretty-time "^1.1.0" - std-env "^3.0.1" + "chalk" "^4.1.0" + "consola" "^2.15.3" + "pretty-time" "^1.1.0" + "std-env" "^3.0.1" -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== +"websocket-driver@^0.7.4", "websocket-driver@>=0.5.1": + "integrity" "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==" + "resolved" "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" + "version" "0.7.4" dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" + "http-parser-js" ">=0.5.1" + "safe-buffer" ">=5.1.0" + "websocket-extensions" ">=0.1.1" -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +"websocket-extensions@>=0.1.1": + "integrity" "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + "resolved" "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + "version" "0.1.4" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= +"whatwg-url@^5.0.0": + "integrity" "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=" + "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + "version" "5.0.0" dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + "tr46" "~0.0.3" + "webidl-conversions" "^3.0.0" -which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== +"which@^1.3.1": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" dependencies: - isexe "^2.0.0" + "isexe" "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== +"which@^2.0.1": + "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==" + "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + "version" "2.0.2" dependencies: - isexe "^2.0.0" + "isexe" "^2.0.0" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== +"widest-line@^3.1.0": + "integrity" "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==" + "resolved" "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz" + "version" "3.1.0" dependencies: - string-width "^4.0.0" + "string-width" "^4.0.0" -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +"wildcard@^2.0.0": + "integrity" "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" + "resolved" "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz" + "version" "2.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +"wrap-ansi@^7.0.0": + "integrity" "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==" + "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + "version" "7.0.0" dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + "ansi-styles" "^4.0.0" + "string-width" "^4.1.0" + "strip-ansi" "^6.0.0" -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +"wrappy@1": + "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + "version" "1.0.2" -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== +"write-file-atomic@^3.0.0": + "integrity" "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==" + "resolved" "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" + "version" "3.0.3" dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" + "imurmurhash" "^0.1.4" + "is-typedarray" "^1.0.0" + "signal-exit" "^3.0.2" + "typedarray-to-buffer" "^3.1.5" -ws@^7.3.1: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== +"ws@^7.3.1": + "integrity" "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==" + "resolved" "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz" + "version" "7.5.7" -ws@^8.4.2: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +"ws@^8.4.2": + "integrity" "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==" + "resolved" "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz" + "version" "8.5.0" -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +"xdg-basedir@^4.0.0": + "integrity" "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + "resolved" "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz" + "version" "4.0.0" -xml-js@^1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" - integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== +"xml-js@^1.6.11": + "integrity" "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==" + "resolved" "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz" + "version" "1.6.11" dependencies: - sax "^1.2.4" + "sax" "^1.2.4" -xtend@^4.0.0, xtend@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +"xtend@^4.0.0", "xtend@^4.0.1": + "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "resolved" "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" + "version" "4.0.2" -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +"yallist@^3.0.2": + "integrity" "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "resolved" "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + "version" "3.1.1" -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +"yallist@^4.0.0": + "integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + "version" "4.0.0" -yarn@^1.17.3: - version "1.22.17" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.17.tgz#bf910747d22497b573131f7341c0e1d15c74036c" - integrity sha512-H0p241BXaH0UN9IeH//RT82tl5PfNraVpSpEoW+ET7lmopNC61eZ+A+IDvU8FM6Go5vx162SncDL8J1ZjRBriQ== +"yaml@^1.10.0", "yaml@^1.10.2", "yaml@^1.7.2": + "integrity" "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + "resolved" "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" + "version" "1.10.2" -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +"yarn@^1.17.3": + "integrity" "sha512-H0p241BXaH0UN9IeH//RT82tl5PfNraVpSpEoW+ET7lmopNC61eZ+A+IDvU8FM6Go5vx162SncDL8J1ZjRBriQ==" + "resolved" "https://registry.npmjs.org/yarn/-/yarn-1.22.17.tgz" + "version" "1.22.17" -zwitch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" - integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== +"yocto-queue@^0.1.0": + "integrity" "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + "resolved" "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + "version" "0.1.0" + +"zwitch@^1.0.0": + "integrity" "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" + "resolved" "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz" + "version" "1.0.5" From cca97a036bcf9acc1c7a8d1bf55d0df203fd2388 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 14:57:33 +0200 Subject: [PATCH 160/428] Use `get_output_parameter` --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 8357e188a8..280d69b287 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -2,7 +2,7 @@ import os import pyblish.api import openpype.api -from openpype.hosts.houdini.api.lib import render_rop +from openpype.hosts.houdini.api.lib import render_rop, get_output_parameter import hou @@ -21,7 +21,7 @@ class ExtractOpenGL(openpype.api.Extractor): # Get the filename from the filename parameter # `.evalParm(parameter)` will make sure all tokens are resolved - output = ropnode.evalParm("picture") + output = get_output_parameter(ropnode).eval() staging_dir = os.path.dirname(output) instance.data["stagingDir"] = staging_dir file_name = os.path.basename(output) From 72dcd437fcf9da715a9fd12888bed07d421670ac Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 16:22:21 +0200 Subject: [PATCH 161/428] Fix import --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 280d69b287..4875d9b98d 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -1,13 +1,13 @@ import os import pyblish.api -import openpype.api +from openpype.pipeline import publish from openpype.hosts.houdini.api.lib import render_rop, get_output_parameter import hou -class ExtractOpenGL(openpype.api.Extractor): +class ExtractOpenGL(publish.Extractor): order = pyblish.api.ExtractorOrder label = "Extract OpenGL" From 429d4fbd5cfbd62d5352f39d039b5268ade8983a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 17:45:13 +0200 Subject: [PATCH 162/428] Fix burnin frame range + add support for focal length in burnin (incl. animated focal length) --- .../plugins/publish/collect_review_data.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/collect_review_data.py diff --git a/openpype/hosts/houdini/plugins/publish/collect_review_data.py b/openpype/hosts/houdini/plugins/publish/collect_review_data.py new file mode 100644 index 0000000000..7c20f9bea8 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_review_data.py @@ -0,0 +1,52 @@ +import hou +import pyblish.api + + +class CollectHoudiniReviewData(pyblish.api.InstancePlugin): + """Collect Review Data.""" + + label = "Collect Review Data" + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["houdini"] + families = ["review"] + + def process(self, instance): + + # This fixes the burnin having the incorrect start/end timestamps + # because without this it would take it from the context instead + # which isn't the actual frame range that this instance renders. + instance.data["handleStart"] = 0 + instance.data["handleEnd"] = 0 + + # Get the camera from the rop node to collect the focal length + ropnode_path = instance.data["instance_node"] + ropnode = hou.node(ropnode_path) + + try: + camera = ropnode.parm("camera").evalAsNode() + except TypeError: + # Not a valid node path set + self.log.error("No valid camera node found on review node: " + "{}".format(ropnode.path())) + return + + # Collect focal length. + focal_length_parm = camera.parm("focal") + if not focal_length_parm: + self.log.warning("No 'focal' (focal length) parameter found on " + "camera: {}".format(camera.path())) + return + + if focal_length_parm.isTimeDependent(): + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + 1 + focal_length = [ + focal_length_parm.evalAsFloatAtFrame(t) + for t in range(int(start), int(end)) + ] + else: + focal_length = focal_length_parm.evalAsFloat() + + # Store focal length in `burninDataMembers` + burnin_members = instance.data.setdefault("burninDataMembers", {}) + burnin_members["focalLength"] = focal_length From 11c7256a2aa2e41d67667349f16b4c4a139d70a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 17:51:04 +0200 Subject: [PATCH 163/428] Fix `families` filtering --- openpype/hosts/houdini/plugins/publish/collect_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index 9cca07fdc7..bd4ab31d59 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -11,7 +11,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder - 0.01 label = "Houdini Current File" hosts = ["houdini"] - family = ["workfile"] + families = ["workfile"] def process(self, instance): """Inject the current working file""" From 8b74d0958da7d2545cd74d8df91b43ef9bf922d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 17:51:13 +0200 Subject: [PATCH 164/428] Remove unused import --- openpype/hosts/houdini/plugins/publish/collect_current_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index bd4ab31d59..3a391f22bd 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -1,7 +1,6 @@ import os import hou -from openpype.pipeline import legacy_io import pyblish.api From 71adb2964b5807742622949045899c845b5ada94 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Mar 2023 17:52:54 +0200 Subject: [PATCH 165/428] Fix set correct variable --- openpype/hosts/houdini/plugins/publish/collect_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index 3a391f22bd..caf679f98b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -20,7 +20,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): # By default, Houdini will even point a new scene to a path. # However if the file is not saved at all and does not exist, # we assume the user never set it. - filepath = "" + current_file = "" elif os.path.basename(current_file) == "untitled.hip": # Due to even a new file being called 'untitled.hip' we are unable From 97d5b187e81601864d70a84ec54f13baf885a509 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 30 Mar 2023 13:42:30 +0200 Subject: [PATCH 166/428] OP-3951 - updated logic Doesn't make sense to validate sequence for single frame. Updated tests accordingly. No sense in testing single frames so widely. --- .../publish/validate_sequence_frames.py | 2 +- .../publish/test_validate_sequence_frames.py | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py index a804a6d9dd..6c14d0cbbf 100644 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ b/openpype/plugins/publish/validate_sequence_frames.py @@ -31,7 +31,7 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): for repr in representations: repr_files = repr["files"] if isinstance(repr_files, str): - repr_files = [repr_files] + continue ext = repr.get("ext") if not ext: diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py index e1facdc37b..f9d1e59096 100644 --- a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -37,7 +37,7 @@ class TestValidateSequenceFrames(BaseTest): class Instance(PyblishInstance): data = { "frameStart": 1001, - "frameEnd": 1001, + "frameEnd": 1002, "representations": [] } yield Instance @@ -57,17 +57,18 @@ class TestValidateSequenceFrames(BaseTest): } ] instance.data["representations"] = representations + instance.data["frameEnd"] = 1001 plugin.process(instance) @pytest.mark.parametrize("files", - ["Main_beauty.v001.1001.exr", - "Main_beauty_v001.1001.exr", - "Main_beauty.1001.1001.exr", - "Main_beauty_v001_1001.exr"]) - def test_validate_sequence_frames_single_frame_name(self, instance, - plugin, - files): + [ + ["Main_beauty.v001.1001.exr", "Main_beauty.v001.1002.exr"], + ["Main_beauty_v001.1001.exr", "Main_beauty_v001.1002.exr"], + ["Main_beauty.1001.1001.exr", "Main_beauty.1001.1002.exr"], + ["Main_beauty_v001_1001.exr", "Main_beauty_v001_1002.exr"]]) + def test_validate_sequence_frames_name(self, instance, + plugin, files): # tests for names with number inside, caused clique failure before representations = [ { @@ -80,10 +81,9 @@ class TestValidateSequenceFrames(BaseTest): plugin.process(instance) @pytest.mark.parametrize("files", - ["Main_beauty.1001.v001.exr"]) - def test_validate_sequence_frames_single_frame_wrong_name(self, instance, - plugin, - files): + [["Main_beauty.1001.v001.exr", "Main_beauty.1002.v001.exr"]]) + def test_validate_sequence_frames_wrong_name(self, instance, + plugin, files): # tests for names with number inside, caused clique failure before representations = [ { @@ -93,14 +93,14 @@ class TestValidateSequenceFrames(BaseTest): ] instance.data["representations"] = representations - with pytest.raises(ValueError) as excinfo: + with pytest.raises(AssertionError) as excinfo: plugin.process(instance) - assert ("Invalid frame range: (1, 1) - expected: (1001, 1001)" in + assert ("Must detect single collection" in str(excinfo.value)) @pytest.mark.parametrize("files", - ["Main_beauty.v001.1001.ass.gz"]) - def test_validate_sequence_frames_single_frame_possible_wrong_name( + [["Main_beauty.v001.1001.ass.gz", "Main_beauty.v001.1002.ass.gz"]]) + def test_validate_sequence_frames_possible_wrong_name( self, instance, plugin, files): # currently pattern fails on extensions with dots representations = [ @@ -116,8 +116,8 @@ class TestValidateSequenceFrames(BaseTest): str(excinfo.value)) @pytest.mark.parametrize("files", - ["Main_beauty.v001.1001.ass.gz"]) - def test_validate_sequence_frames_single_frame_correct_ext( + [["Main_beauty.v001.1001.ass.gz", "Main_beauty.v001.1002.ass.gz"]]) + def test_validate_sequence_frames__correct_ext( self, instance, plugin, files): # currently pattern fails on extensions with dots representations = [ From 80c50af2b903737c3d4635063423876cc3b9d210 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 30 Mar 2023 13:45:37 +0200 Subject: [PATCH 167/428] OP-3951 - Hound --- .../publish/test_validate_sequence_frames.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py index f9d1e59096..58d9de011d 100644 --- a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py +++ b/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py @@ -63,10 +63,14 @@ class TestValidateSequenceFrames(BaseTest): @pytest.mark.parametrize("files", [ - ["Main_beauty.v001.1001.exr", "Main_beauty.v001.1002.exr"], - ["Main_beauty_v001.1001.exr", "Main_beauty_v001.1002.exr"], - ["Main_beauty.1001.1001.exr", "Main_beauty.1001.1002.exr"], - ["Main_beauty_v001_1001.exr", "Main_beauty_v001_1002.exr"]]) + ["Main_beauty.v001.1001.exr", + "Main_beauty.v001.1002.exr"], + ["Main_beauty_v001.1001.exr", + "Main_beauty_v001.1002.exr"], + ["Main_beauty.1001.1001.exr", + "Main_beauty.1001.1002.exr"], + ["Main_beauty_v001_1001.exr", + "Main_beauty_v001_1002.exr"]]) def test_validate_sequence_frames_name(self, instance, plugin, files): # tests for names with number inside, caused clique failure before @@ -81,7 +85,8 @@ class TestValidateSequenceFrames(BaseTest): plugin.process(instance) @pytest.mark.parametrize("files", - [["Main_beauty.1001.v001.exr", "Main_beauty.1002.v001.exr"]]) + [["Main_beauty.1001.v001.exr", + "Main_beauty.1002.v001.exr"]]) def test_validate_sequence_frames_wrong_name(self, instance, plugin, files): # tests for names with number inside, caused clique failure before @@ -99,7 +104,8 @@ class TestValidateSequenceFrames(BaseTest): str(excinfo.value)) @pytest.mark.parametrize("files", - [["Main_beauty.v001.1001.ass.gz", "Main_beauty.v001.1002.ass.gz"]]) + [["Main_beauty.v001.1001.ass.gz", + "Main_beauty.v001.1002.ass.gz"]]) def test_validate_sequence_frames_possible_wrong_name( self, instance, plugin, files): # currently pattern fails on extensions with dots @@ -116,7 +122,8 @@ class TestValidateSequenceFrames(BaseTest): str(excinfo.value)) @pytest.mark.parametrize("files", - [["Main_beauty.v001.1001.ass.gz", "Main_beauty.v001.1002.ass.gz"]]) + [["Main_beauty.v001.1001.ass.gz", + "Main_beauty.v001.1002.ass.gz"]]) def test_validate_sequence_frames__correct_ext( self, instance, plugin, files): # currently pattern fails on extensions with dots From 32743b7a855e1a9df980f923f35da0620adbb237 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 30 Mar 2023 12:48:00 +0100 Subject: [PATCH 168/428] Setup settings. --- .../defaults/project_settings/maya.json | 245 ++-- .../schemas/schema_maya_capture.json | 1258 +++++++++-------- 2 files changed, 770 insertions(+), 733 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index e914eb29f9..4044bdf5df 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -788,126 +788,133 @@ "validate_shapes": true }, "ExtractPlayblast": { - "capture_preset": { - "Codec": { - "compression": "png", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 125, - 125, - 125, - 255 - ], - "backgroundBottom": [ - 125, - 125, - 125, - 255 - ], - "backgroundTop": [ - 125, - 125, - 125, - 255 - ], - "override_display": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true, - "pan_zoom": false - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "width": 1920, - "height": 1080 - }, - "Viewport Options": { - "override_viewport_options": true, - "displayLights": "default", - "displayTextures": true, - "textureMaxResolution": 1024, - "renderDepthOfField": true, - "shadows": true, - "twoSidedLighting": true, - "lineAAEnable": true, - "multiSample": 8, - "useDefaultMaterial": false, - "wireframeOnShaded": false, - "xray": false, - "jointXray": false, - "backfaceCulling": false, - "ssaoEnable": false, - "ssaoAmount": 1, - "ssaoRadius": 16, - "ssaoFilterRadius": 16, - "ssaoSamples": 16, - "fogging": false, - "hwFogFalloff": "0", - "hwFogDensity": 0.0, - "hwFogStart": 0, - "hwFogEnd": 100, - "hwFogAlpha": 0, - "hwFogColorR": 1.0, - "hwFogColorG": 1.0, - "hwFogColorB": 1.0, - "motionBlurEnable": false, - "motionBlurSampleCount": 8, - "motionBlurShutterOpenFraction": 0.2, - "cameras": false, - "clipGhosts": false, - "deformers": false, - "dimensions": false, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "gpuCacheDisplayFilter": false, - "greasePencils": false, - "grid": false, - "hairSystems": true, - "handles": false, - "headsUpDisplay": false, - "ikHandles": false, - "imagePlane": true, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "controlVertices": false, - "nurbsCurves": false, - "hulls": false, - "nurbsSurfaces": false, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "strokes": false, - "subdivSurfaces": false, - "textures": false - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 + "profiles": [ + { + "task_types": [], + "task_names": [], + "subsets": [], + "capture_preset": { + "Codec": { + "compression": "png", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 125, + 125, + 125, + 255 + ], + "backgroundBottom": [ + 125, + 125, + 125, + 255 + ], + "backgroundTop": [ + 125, + 125, + 125, + 255 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true, + "pan_zoom": false + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1920, + "height": 1080 + }, + "Viewport Options": { + "override_viewport_options": true, + "displayLights": "default", + "displayTextures": true, + "textureMaxResolution": 1024, + "renderDepthOfField": true, + "shadows": true, + "twoSidedLighting": true, + "lineAAEnable": true, + "multiSample": 8, + "useDefaultMaterial": false, + "wireframeOnShaded": false, + "xray": false, + "jointXray": false, + "backfaceCulling": false, + "ssaoEnable": false, + "ssaoAmount": 1, + "ssaoRadius": 16, + "ssaoFilterRadius": 16, + "ssaoSamples": 16, + "fogging": false, + "hwFogFalloff": "0", + "hwFogDensity": 0.0, + "hwFogStart": 0, + "hwFogEnd": 100, + "hwFogAlpha": 0, + "hwFogColorR": 1.0, + "hwFogColorG": 1.0, + "hwFogColorB": 1.0, + "motionBlurEnable": false, + "motionBlurSampleCount": 0, + "motionBlurShutterOpenFraction": 0.2, + "cameras": false, + "clipGhosts": false, + "deformers": false, + "dimensions": false, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": true, + "handles": false, + "headsUpDisplay": false, + "ikHandles": false, + "imagePlane": true, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "controlVertices": false, + "nurbsCurves": false, + "hulls": false, + "nurbsSurfaces": false, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": false, + "strokes": false, + "subdivSurfaces": false, + "textures": false + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } + } } - } + ] }, "ExtractMayaSceneRaw": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 416e530db2..1d0f94e5b8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -5,622 +5,652 @@ "label": "Extract Playblast settings", "children": [ { - "type": "dict", - "key": "capture_preset", - "children": [ - { - "type": "dict", - "key": "Codec", - "children": [ - { - "type": "label", - "label": "Codec" - }, - { - "type": "text", - "key": "compression", - "label": "Encoding" - }, - { - "type": "text", - "key": "format", - "label": "Format" - }, - { - "type": "number", - "key": "quality", - "label": "Quality", - "decimal": 0, - "minimum": 0, - "maximum": 100 - }, + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "capture_preset", + "children": [ + { + "type": "dict", + "key": "Codec", + "children": [ + { + "type": "label", + "label": "Codec" + }, + { + "type": "text", + "key": "compression", + "label": "Encoding" + }, + { + "type": "text", + "key": "format", + "label": "Format" + }, + { + "type": "number", + "key": "quality", + "label": "Quality", + "decimal": 0, + "minimum": 0, + "maximum": 100 + }, - { - "type": "splitter" - } - ] - }, - { - "type": "dict", - "key": "Display Options", - "children": [ - { - "type": "label", - "label": "Display Options" - }, + { + "type": "splitter" + } + ] + }, + { + "type": "dict", + "key": "Display Options", + "children": [ + { + "type": "label", + "label": "Display Options" + }, - { - "type": "color", - "key": "background", - "label": "Background Color: " - }, - { - "type": "color", - "key": "backgroundBottom", - "label": "Background Bottom: " - }, - { - "type": "color", - "key": "backgroundTop", - "label": "Background Top: " - }, - { - "type": "boolean", - "key": "override_display", - "label": "Override display options" - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "key": "Generic", - "children": [ - { - "type": "label", - "label": "Generic" - }, - { - "type": "boolean", - "key": "isolate_view", - "label": " Isolate view" - }, - { - "type": "boolean", - "key": "off_screen", - "label": " Off Screen" - }, - { - "type": "boolean", - "key": "pan_zoom", - "label": " 2D Pan/Zoom" - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "key": "Renderer", - "children": [ - { - "type": "label", - "label": "Renderer" - }, - { - "type": "enum", - "key": "rendererName", - "label": "Renderer name", - "enum_items": [ - { "vp2Renderer": "Viewport 2.0" } - ] - } - ] - }, - { - "type": "dict", - "key": "Resolution", - "children": [ - { - "type": "splitter" - }, - { - "type": "label", - "label": "Resolution" - }, - { - "type": "number", - "key": "width", - "label": " Width", - "decimal": 0, - "minimum": 0, - "maximum": 99999 - }, - { - "type": "number", - "key": "height", - "label": "Height", - "decimal": 0, - "minimum": 0, - "maximum": 99999 - } - ] - }, - { - "type": "splitter" - }, - { - "type": "dict", - "collapsible": true, - "key": "Viewport Options", - "label": "Viewport Options", - "children": [ - { - "type": "boolean", - "key": "override_viewport_options", - "label": "Override Viewport Options" - }, - { - "type": "enum", - "key": "displayLights", - "label": "Display Lights", - "enum_items": [ - { "default": "Default Lighting"}, - { "all": "All Lights"}, - { "selected": "Selected Lights"}, - { "flat": "Flat Lighting"}, - { "nolights": "No Lights"} - ] - }, - { - "type": "boolean", - "key": "displayTextures", - "label": "Display Textures" - }, - { - "type": "number", - "key": "textureMaxResolution", - "label": "Texture Clamp Resolution", - "decimal": 0 - }, - { - "type": "splitter" - }, - { - "type": "label", - "label": "Display" - }, - { - "type":"boolean", - "key": "renderDepthOfField", - "label": "Depth of Field" - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "shadows", - "label": "Display Shadows" - }, - { - "type": "boolean", - "key": "twoSidedLighting", - "label": "Two Sided Lighting" - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "lineAAEnable", - "label": "Enable Anti-Aliasing" - }, - { - "type": "number", - "key": "multiSample", - "label": "Anti Aliasing Samples", - "decimal": 0, - "minimum": 0, - "maximum": 32 - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "useDefaultMaterial", - "label": "Use Default Material" - }, - { - "type": "boolean", - "key": "wireframeOnShaded", - "label": "Wireframe On Shaded" - }, - { - "type": "boolean", - "key": "xray", - "label": "X-Ray" - }, - { - "type": "boolean", - "key": "jointXray", - "label": "X-Ray Joints" - }, - { - "type": "boolean", - "key": "backfaceCulling", - "label": "Backface Culling" - }, - { - "type": "boolean", - "key": "ssaoEnable", - "label": "Screen Space Ambient Occlusion" - }, - { - "type": "number", - "key": "ssaoAmount", - "label": "SSAO Amount" - }, - { - "type": "number", - "key": "ssaoRadius", - "label": "SSAO Radius" - }, - { - "type": "number", - "key": "ssaoFilterRadius", - "label": "SSAO Filter Radius", - "decimal": 0, - "minimum": 1, - "maximum": 32 - }, - { - "type": "number", - "key": "ssaoSamples", - "label": "SSAO Samples", - "decimal": 0, - "minimum": 8, - "maximum": 32 - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "fogging", - "label": "Enable Hardware Fog" - }, - { - "type": "enum", - "key": "hwFogFalloff", - "label": "Hardware Falloff", - "enum_items": [ - { "0": "Linear"}, - { "1": "Exponential"}, - { "2": "Exponential Squared"} - ] - }, - { - "type": "number", - "key": "hwFogDensity", - "label": "Fog Density", - "decimal": 2, - "minimum": 0, - "maximum": 1 - }, - { - "type": "number", - "key": "hwFogStart", - "label": "Fog Start" - }, - { - "type": "number", - "key": "hwFogEnd", - "label": "Fog End" - }, - { - "type": "number", - "key": "hwFogAlpha", - "label": "Fog Alpha" - }, - { - "type": "number", - "key": "hwFogColorR", - "label": "Fog Color R", - "decimal": 2, - "minimum": 0, - "maximum": 1 - }, - { - "type": "number", - "key": "hwFogColorG", - "label": "Fog Color G", - "decimal": 2, - "minimum": 0, - "maximum": 1 - }, - { - "type": "number", - "key": "hwFogColorB", - "label": "Fog Color B", - "decimal": 2, - "minimum": 0, - "maximum": 1 - }, - { - "type": "splitter" - }, - { - "type": "boolean", - "key": "motionBlurEnable", - "label": "Enable Motion Blur" - }, - { - "type": "number", - "key": "motionBlurSampleCount", - "label": "Motion Blur Sample Count", - "decimal": 0, - "minimum": 8, - "maximum": 32 - }, - { - "type": "number", - "key": "motionBlurShutterOpenFraction", - "label": "Shutter Open Fraction", - "decimal": 3, - "minimum": 0.01, - "maximum": 32 - }, - { - "type": "splitter" - }, - { - "type": "label", - "label": "Show" - }, - { - "type": "boolean", - "key": "cameras", - "label": "Cameras" - }, - { - "type": "boolean", - "key": "clipGhosts", - "label": "Clip Ghosts" - }, - { - "type": "boolean", - "key": "deformers", - "label": "Deformers" - }, - { - "type": "boolean", - "key": "dimensions", - "label": "Dimensions" - }, - { - "type": "boolean", - "key": "dynamicConstraints", - "label": "Dynamic Constraints" - }, - { - "type": "boolean", - "key": "dynamics", - "label": "Dynamics" - }, - { - "type": "boolean", - "key": "fluids", - "label": "Fluids" - }, - { - "type": "boolean", - "key": "follicles", - "label": "Follicles" - }, - { - "type": "boolean", - "key": "gpuCacheDisplayFilter", - "label": "GPU Cache" - }, - { - "type": "boolean", - "key": "greasePencils", - "label": "Grease Pencil" - }, - { - "type": "boolean", - "key": "grid", - "label": "Grid" - }, - { - "type": "boolean", - "key": "hairSystems", - "label": "Hair Systems" - }, - { - "type": "boolean", - "key": "handles", - "label": "Handles" - }, - { - "type": "boolean", - "key": "headsUpDisplay", - "label": "HUD" - }, - { - "type": "boolean", - "key": "ikHandles", - "label": "IK Handles" - }, - { - "type": "boolean", - "key": "imagePlane", - "label": "Image Planes" - }, - { - "type": "boolean", - "key": "joints", - "label": "Joints" - }, - { - "type": "boolean", - "key": "lights", - "label": "Lights" - }, - { - "type": "boolean", - "key": "locators", - "label": "Locators" - }, - { - "type": "boolean", - "key": "manipulators", - "label": "Manipulators" - }, - { - "type": "boolean", - "key": "motionTrails", - "label": "Motion Trails" - }, - { - "type": "boolean", - "key": "nCloths", - "label": "nCloths" - }, - { - "type": "boolean", - "key": "nParticles", - "label": "nParticles" - }, - { - "type": "boolean", - "key": "nRigids", - "label": "nRigids" - }, - { - "type": "boolean", - "key": "controlVertices", - "label": "NURBS CVs" - }, - { - "type": "boolean", - "key": "nurbsCurves", - "label": "NURBS Curves" - }, - { - "type": "boolean", - "key": "hulls", - "label": "NURBS Hulls" - }, - { - "type": "boolean", - "key": "nurbsSurfaces", - "label": "NURBS Surfaces" - }, - { - "type": "boolean", - "key": "particleInstancers", - "label": "Particle Instancers" - }, - { - "type": "boolean", - "key": "pivots", - "label": "Pivots" - }, - { - "type": "boolean", - "key": "planes", - "label": "Planes" - }, - { - "type": "boolean", - "key": "pluginShapes", - "label": "Plugin Shapes" - }, - { - "type": "boolean", - "key": "polymeshes", - "label": "Polygons" - }, - { - "type": "boolean", - "key": "strokes", - "label": "Strokes" - }, - { - "type": "boolean", - "key": "subdivSurfaces", - "label": "Subdiv Surfaces" - }, - { - "type": "boolean", - "key": "textures", - "label": "Texture Placements" - } - ] - }, - { - "type": "dict", - "collapsible": true, - "key": "Camera Options", - "label": "Camera Options", - "children": [ - { - "type": "boolean", - "key": "displayGateMask", - "label": "Display Gate Mask" - }, - { - "type": "boolean", - "key": "displayResolution", - "label": "Display Resolution" - }, - { - "type": "boolean", - "key": "displayFilmGate", - "label": "Display Film Gate" - }, - { - "type": "boolean", - "key": "displayFieldChart", - "label": "Display Field Chart" - }, - { - "type": "boolean", - "key": "displaySafeAction", - "label": "Display Safe Action" - }, - { - "type": "boolean", - "key": "displaySafeTitle", - "label": "Display Safe Title" - }, - { - "type": "boolean", - "key": "displayFilmPivot", - "label": "Display Film Pivot" - }, - { - "type": "boolean", - "key": "displayFilmOrigin", - "label": "Display Film Origin" - }, - { - "type": "number", - "key": "overscan", - "label": "Overscan", - "decimal": 1, - "minimum": 0, - "maximum": 10 - } - ] - } - ] + { + "type": "color", + "key": "background", + "label": "Background Color: " + }, + { + "type": "color", + "key": "backgroundBottom", + "label": "Background Bottom: " + }, + { + "type": "color", + "key": "backgroundTop", + "label": "Background Top: " + }, + { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + }, + { + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + }, + { + "type": "boolean", + "key": "pan_zoom", + "label": " 2D Pan/Zoom" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Renderer", + "children": [ + { + "type": "label", + "label": "Renderer" + }, + { + "type": "enum", + "key": "rendererName", + "label": "Renderer name", + "enum_items": [ + { "vp2Renderer": "Viewport 2.0" } + ] + } + ] + }, + { + "type": "dict", + "key": "Resolution", + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Resolution" + }, + { + "type": "number", + "key": "width", + "label": " Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "collapsible": true, + "key": "Viewport Options", + "label": "Viewport Options", + "children": [ + { + "type": "boolean", + "key": "override_viewport_options", + "label": "Override Viewport Options" + }, + { + "type": "enum", + "key": "displayLights", + "label": "Display Lights", + "enum_items": [ + { "default": "Default Lighting"}, + { "all": "All Lights"}, + { "selected": "Selected Lights"}, + { "flat": "Flat Lighting"}, + { "nolights": "No Lights"} + ] + }, + { + "type": "boolean", + "key": "displayTextures", + "label": "Display Textures" + }, + { + "type": "number", + "key": "textureMaxResolution", + "label": "Texture Clamp Resolution", + "decimal": 0 + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Display" + }, + { + "type":"boolean", + "key": "renderDepthOfField", + "label": "Depth of Field" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "shadows", + "label": "Display Shadows" + }, + { + "type": "boolean", + "key": "twoSidedLighting", + "label": "Two Sided Lighting" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "lineAAEnable", + "label": "Enable Anti-Aliasing" + }, + { + "type": "number", + "key": "multiSample", + "label": "Anti Aliasing Samples", + "decimal": 0, + "minimum": 0, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "useDefaultMaterial", + "label": "Use Default Material" + }, + { + "type": "boolean", + "key": "wireframeOnShaded", + "label": "Wireframe On Shaded" + }, + { + "type": "boolean", + "key": "xray", + "label": "X-Ray" + }, + { + "type": "boolean", + "key": "jointXray", + "label": "X-Ray Joints" + }, + { + "type": "boolean", + "key": "backfaceCulling", + "label": "Backface Culling" + }, + { + "type": "boolean", + "key": "ssaoEnable", + "label": "Screen Space Ambient Occlusion" + }, + { + "type": "number", + "key": "ssaoAmount", + "label": "SSAO Amount" + }, + { + "type": "number", + "key": "ssaoRadius", + "label": "SSAO Radius" + }, + { + "type": "number", + "key": "ssaoFilterRadius", + "label": "SSAO Filter Radius", + "decimal": 0, + "minimum": 1, + "maximum": 32 + }, + { + "type": "number", + "key": "ssaoSamples", + "label": "SSAO Samples", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "fogging", + "label": "Enable Hardware Fog" + }, + { + "type": "enum", + "key": "hwFogFalloff", + "label": "Hardware Falloff", + "enum_items": [ + { "0": "Linear"}, + { "1": "Exponential"}, + { "2": "Exponential Squared"} + ] + }, + { + "type": "number", + "key": "hwFogDensity", + "label": "Fog Density", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogStart", + "label": "Fog Start" + }, + { + "type": "number", + "key": "hwFogEnd", + "label": "Fog End" + }, + { + "type": "number", + "key": "hwFogAlpha", + "label": "Fog Alpha" + }, + { + "type": "number", + "key": "hwFogColorR", + "label": "Fog Color R", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorG", + "label": "Fog Color G", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorB", + "label": "Fog Color B", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "motionBlurEnable", + "label": "Enable Motion Blur" + }, + { + "type": "number", + "key": "motionBlurSampleCount", + "label": "Motion Blur Sample Count", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, + { + "type": "number", + "key": "motionBlurShutterOpenFraction", + "label": "Shutter Open Fraction", + "decimal": 3, + "minimum": 0.01, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Show" + }, + { + "type": "boolean", + "key": "cameras", + "label": "Cameras" + }, + { + "type": "boolean", + "key": "clipGhosts", + "label": "Clip Ghosts" + }, + { + "type": "boolean", + "key": "deformers", + "label": "Deformers" + }, + { + "type": "boolean", + "key": "dimensions", + "label": "Dimensions" + }, + { + "type": "boolean", + "key": "dynamicConstraints", + "label": "Dynamic Constraints" + }, + { + "type": "boolean", + "key": "dynamics", + "label": "Dynamics" + }, + { + "type": "boolean", + "key": "fluids", + "label": "Fluids" + }, + { + "type": "boolean", + "key": "follicles", + "label": "Follicles" + }, + { + "type": "boolean", + "key": "gpuCacheDisplayFilter", + "label": "GPU Cache" + }, + { + "type": "boolean", + "key": "greasePencils", + "label": "Grease Pencil" + }, + { + "type": "boolean", + "key": "grid", + "label": "Grid" + }, + { + "type": "boolean", + "key": "hairSystems", + "label": "Hair Systems" + }, + { + "type": "boolean", + "key": "handles", + "label": "Handles" + }, + { + "type": "boolean", + "key": "headsUpDisplay", + "label": "HUD" + }, + { + "type": "boolean", + "key": "ikHandles", + "label": "IK Handles" + }, + { + "type": "boolean", + "key": "imagePlane", + "label": "Image Planes" + }, + { + "type": "boolean", + "key": "joints", + "label": "Joints" + }, + { + "type": "boolean", + "key": "lights", + "label": "Lights" + }, + { + "type": "boolean", + "key": "locators", + "label": "Locators" + }, + { + "type": "boolean", + "key": "manipulators", + "label": "Manipulators" + }, + { + "type": "boolean", + "key": "motionTrails", + "label": "Motion Trails" + }, + { + "type": "boolean", + "key": "nCloths", + "label": "nCloths" + }, + { + "type": "boolean", + "key": "nParticles", + "label": "nParticles" + }, + { + "type": "boolean", + "key": "nRigids", + "label": "nRigids" + }, + { + "type": "boolean", + "key": "controlVertices", + "label": "NURBS CVs" + }, + { + "type": "boolean", + "key": "nurbsCurves", + "label": "NURBS Curves" + }, + { + "type": "boolean", + "key": "hulls", + "label": "NURBS Hulls" + }, + { + "type": "boolean", + "key": "nurbsSurfaces", + "label": "NURBS Surfaces" + }, + { + "type": "boolean", + "key": "particleInstancers", + "label": "Particle Instancers" + }, + { + "type": "boolean", + "key": "pivots", + "label": "Pivots" + }, + { + "type": "boolean", + "key": "planes", + "label": "Planes" + }, + { + "type": "boolean", + "key": "pluginShapes", + "label": "Plugin Shapes" + }, + { + "type": "boolean", + "key": "polymeshes", + "label": "Polygons" + }, + { + "type": "boolean", + "key": "strokes", + "label": "Strokes" + }, + { + "type": "boolean", + "key": "subdivSurfaces", + "label": "Subdiv Surfaces" + }, + { + "type": "boolean", + "key": "textures", + "label": "Texture Placements" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "Camera Options", + "label": "Camera Options", + "children": [ + { + "type": "boolean", + "key": "displayGateMask", + "label": "Display Gate Mask" + }, + { + "type": "boolean", + "key": "displayResolution", + "label": "Display Resolution" + }, + { + "type": "boolean", + "key": "displayFilmGate", + "label": "Display Film Gate" + }, + { + "type": "boolean", + "key": "displayFieldChart", + "label": "Display Field Chart" + }, + { + "type": "boolean", + "key": "displaySafeAction", + "label": "Display Safe Action" + }, + { + "type": "boolean", + "key": "displaySafeTitle", + "label": "Display Safe Title" + }, + { + "type": "boolean", + "key": "displayFilmPivot", + "label": "Display Film Pivot" + }, + { + "type": "boolean", + "key": "displayFilmOrigin", + "label": "Display Film Origin" + }, + { + "type": "number", + "key": "overscan", + "label": "Overscan", + "decimal": 1, + "minimum": 0, + "maximum": 10 + } + ] + } + ] + } + ] + } } ] } From 7d21437dae8c552c0f8f278d71498bcdb02f5fd0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 30 Mar 2023 21:19:24 +0800 Subject: [PATCH 169/428] add settings and adjustments based on the new publishers --- .../houdini/plugins/create/create_review.py | 145 ++++++++++++++---- .../plugins/publish/collect_review_data.py | 18 +-- .../houdini/plugins/publish/extract_opengl.py | 55 +++---- .../plugins/publish/validate_scene_review.py | 50 ++++++ 4 files changed, 202 insertions(+), 66 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/publish/validate_scene_review.py diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index a1bcfc23ed..32b187e511 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -1,11 +1,11 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating pointcache alembics.""" from openpype.hosts.houdini.api import plugin -from openpype.pipeline import ( - CreatedInstance, - OpenPypePyblishPluginMixin -) +from openpype.pipeline import CreatedInstance +from openpype.lib import EnumDef, BoolDef, NumberDef -class CreateReview(plugin.HoudiniCreator, OpenPypePyblishPluginMixin): +class CreateReview(plugin.HoudiniCreator): """Review with OpenGL ROP""" identifier = "io.openpype.creators.houdini.review" @@ -13,34 +13,30 @@ class CreateReview(plugin.HoudiniCreator, OpenPypePyblishPluginMixin): family = "review" icon = "video-camera" - # Default settings for the ROP - # todo: expose in OpenPype settings? - override_resolution = True - width = 1280 - height = 720 - aspect = 1.0 - - def create(self, subset_name, instance_data, pre_create_data): - + def create(self, subset_name, instance_data, pre_create_data): # type: CreatedInstance import hou - # Remove the active, we are checking the bypass flag of the nodes instance_data.pop("active", None) - - instance_data["node_type"] = "opengl" + instance_data.update({"node_type": "opengl"}) + instance_data["imageFormat"] = pre_create_data.get("imageFormat") + instance_data["keepImages"] = pre_create_data.get("keepImages") instance = super(CreateReview, self).create( subset_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) frame_range = hou.playbar.frameRange() parms = { - "picture": '$HIP/pyblish/`chs("subset")`/`chs("subset")`.$F4.png', - # Render frame range + "picture": "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}/{}.$F4.{}".format( + subset_name, + subset_name, + pre_create_data.get("image_format") or "png")), "trange": 1, # Unlike many other ROP nodes the opengl node does not default @@ -50,13 +46,13 @@ class CreateReview(plugin.HoudiniCreator, OpenPypePyblishPluginMixin): "f2": frame_range[1], } - if self.override_resolution: - # Override resolution + override_resolution = pre_create_data.get("override_resolution") + if override_resolution: parms.update({ - "tres": True, # Override Camera Resolution - "res1": self.width, - "res2": self.height, - "aspect": self.aspect + "tres": override_resolution, + "res1": pre_create_data.get("resx"), + "res2": pre_create_data.get("resy"), + "aspect": pre_create_data.get("aspect"), }) if self.selected_nodes: @@ -66,9 +62,96 @@ class CreateReview(plugin.HoudiniCreator, OpenPypePyblishPluginMixin): instance_node.setParms(parms) - # Lock any parameters in this list - to_lock = [ - "family", - "id" - ] + to_lock = ["id", "family"] + self.lock_parameters(instance_node, to_lock) + + def get_pre_create_attr_defs(self): + attrs = super().get_pre_create_attr_defs() + image_format_enum = [ + { + "value": "png", + "label": ".png" + }, + { + "value": "tif", + "label": ".tif" + }, + { + "value": "sgi", + "label": ".sgi" + }, + { + "value": "pic.gz", + "label": ".pic.gz" + }, + { + "value": "rat", + "label": ".rat" + }, + { + "value": "jpg", + "label": ".jpg" + }, + { + "value": "cin", + "label": ".cin" + }, + { + "value": "rta", + "label": ".rta" + }, + { + "value": "rat", + "label": ".rat" + }, + { + "value": "bmp", + "label": ".bmp" + }, + { + "value": "tga", + "label": ".tga" + }, + { + "value": "rad", + "label": ".rad" + }, + { + "value": "exr", + "label": ".exr" + }, + { + "value": "pic", + "label": ".pic" + } + ] + + return attrs + [ + BoolDef("keepImages", + label="Keep Image Sequences", + default=False), + EnumDef("imageFormat", + image_format_enum, + label="Image Format Options"), + BoolDef("override_resolution", + label="Override resolution", + tooltip="When disabled the resolution set on the camera " + "is used instead.", + default=True), + NumberDef("resx", + label="Resolution Width", + default=1280, + minimum=2, + decimals=0), + NumberDef("resy", + label="Resolution Height", + default=720, + minimum=2, + decimals=0), + NumberDef("aspect", + label="Aspect Ratio", + default=1.0, + minimum=0.0001, + decimals=3) + ] diff --git a/openpype/hosts/houdini/plugins/publish/collect_review_data.py b/openpype/hosts/houdini/plugins/publish/collect_review_data.py index 7c20f9bea8..e321dcb2fa 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_review_data.py +++ b/openpype/hosts/houdini/plugins/publish/collect_review_data.py @@ -22,19 +22,17 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): ropnode_path = instance.data["instance_node"] ropnode = hou.node(ropnode_path) - try: - camera = ropnode.parm("camera").evalAsNode() - except TypeError: - # Not a valid node path set - self.log.error("No valid camera node found on review node: " - "{}".format(ropnode.path())) - return + camera_path = ropnode.parm("camera").eval() + camera_node = hou.node(camera_path) + if not camera_node: + raise RuntimeError("No valid camera node found on review node: " + "{}".format(camera_path)) # Collect focal length. - focal_length_parm = camera.parm("focal") + focal_length_parm = camera_node.parm("focal") if not focal_length_parm: self.log.warning("No 'focal' (focal length) parameter found on " - "camera: {}".format(camera.path())) + "camera: {}".format(camera_path)) return if focal_length_parm.isTimeDependent(): @@ -50,3 +48,5 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): # Store focal length in `burninDataMembers` burnin_members = instance.data.setdefault("burninDataMembers", {}) burnin_members["focalLength"] = focal_length + + instance.data.setdefault("families", []).append('ftrack') diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 4875d9b98d..83a85ffb9a 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -1,53 +1,56 @@ import os import pyblish.api -from openpype.pipeline import publish -from openpype.hosts.houdini.api.lib import render_rop, get_output_parameter + +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin +) +from openpype.hosts.houdini.api.lib import render_rop import hou -class ExtractOpenGL(publish.Extractor): +class ExtractOpenGL(publish.Extractor, + OptionalPyblishPluginMixin): - order = pyblish.api.ExtractorOrder + order = pyblish.api.ExtractorOrder - 0.01 label = "Extract OpenGL" families = ["review"] hosts = ["houdini"] optional = True def process(self, instance): + if not self.is_active(instance.data): + return + ropnode = hou.node(instance.data.get("instance_node")) - ropnode = hou.node(instance.data["instance_node"]) - - # Get the filename from the filename parameter - # `.evalParm(parameter)` will make sure all tokens are resolved - output = get_output_parameter(ropnode).eval() - staging_dir = os.path.dirname(output) + output = ropnode.evalParm("picture") + staging_dir = os.path.normpath(os.path.dirname(output)) instance.data["stagingDir"] = staging_dir file_name = os.path.basename(output) - # We run the render - self.log.info("Extracting '%s' to '%s'" % (file_name, staging_dir)) + self.log.info("Extracting '%s' to '%s'" % (file_name, + staging_dir)) + render_rop(ropnode) - # Unfortunately user interrupting the extraction does not raise an - # error and thus still continues to the integrator. To capture that - # we make sure all files exist - files = instance.data["frames"] - missing = [fname for fname in files - if not os.path.exists(os.path.join(staging_dir, fname))] - if missing: - raise RuntimeError("Failed to complete review extraction. " - "Missing output files: {}".format(missing)) + output = instance.data["frames"] - representation = { - "name": "png", - "ext": "png", - "files": files, + tags = ["review"] + if not instance.data.get("keepImages"): + tags.append("delete") + + representation = { + "name": instance.data["imageFormat"], + "ext": instance.data["imageFormat"], + "files": output, "stagingDir": staging_dir, "frameStart": instance.data["frameStart"], "frameEnd": instance.data["frameEnd"], - "tags": ["review"] + "tags": tags, + "preview": True, + "camera_name": instance.data.get("review_camera") } if "representations" not in instance.data: diff --git a/openpype/hosts/houdini/plugins/publish/validate_scene_review.py b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py new file mode 100644 index 0000000000..3a3b7e6659 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from openpype.pipeline import PublishValidationError +import hou + + +class ValidateSceneReview(pyblish.api.InstancePlugin): + """Validator Some Scene Settings before publishing the review + 1. Scene Path + 2. Resolution + """ + + order = pyblish.api.ValidatorOrder + families = ["review"] + hosts = ["houdini"] + label = "Scene Setting for review" + + def process(self, instance): + invalid = self.get_invalid_scene_path(instance) + if invalid: + raise PublishValidationError( + "Scene path does not exist: %s" % invalid, + title=self.label) + invalid = self.get_invalid_resolution(instance) + if invalid: + raise PublishValidationError( + "Invalid Resolution Setting", + title=self.label) + + def get_invalid_scene_path(self, instance): + invalid = list() + node = hou.node(instance.data.get("instance_node")) + scene_path = node.parm("scenepath").eval() + scene_path_node = hou.node(scene_path) + if not scene_path_node: + invalid.append(scene_path_node) + + return invalid + + def get_invalid_resolution(self, instance): + invalid = list() + node = hou.node(instance.data.get("instance_node")) + res_width = node.parm("res1").eval() + res_height = node.parm("res2").eval() + if res_width == 0: + invalid.append(res_width) + if res_height == 0: + invalid.append(res_height) + + return invalid From f5c68f57bfc2fa91bef48c6f983b08d7b17fa89b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:24:58 +0200 Subject: [PATCH 170/428] Simplify image formats --- .../houdini/plugins/create/create_review.py | 62 ++----------------- 1 file changed, 5 insertions(+), 57 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 32b187e511..ca9c88de11 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -67,64 +67,11 @@ class CreateReview(plugin.HoudiniCreator): self.lock_parameters(instance_node, to_lock) def get_pre_create_attr_defs(self): - attrs = super().get_pre_create_attr_defs() + attrs = super(CreateReview, self).get_pre_create_attr_defs() + image_format_enum = [ - { - "value": "png", - "label": ".png" - }, - { - "value": "tif", - "label": ".tif" - }, - { - "value": "sgi", - "label": ".sgi" - }, - { - "value": "pic.gz", - "label": ".pic.gz" - }, - { - "value": "rat", - "label": ".rat" - }, - { - "value": "jpg", - "label": ".jpg" - }, - { - "value": "cin", - "label": ".cin" - }, - { - "value": "rta", - "label": ".rta" - }, - { - "value": "rat", - "label": ".rat" - }, - { - "value": "bmp", - "label": ".bmp" - }, - { - "value": "tga", - "label": ".tga" - }, - { - "value": "rad", - "label": ".rad" - }, - { - "value": "exr", - "label": ".exr" - }, - { - "value": "pic", - "label": ".pic" - } + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", ] return attrs + [ @@ -133,6 +80,7 @@ class CreateReview(plugin.HoudiniCreator): default=False), EnumDef("imageFormat", image_format_enum, + default="png", label="Image Format Options"), BoolDef("override_resolution", label="Override resolution", From 85a49632429c0aa92c89b702b4028255a3ec7c23 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:27:44 +0200 Subject: [PATCH 171/428] Simplify --- .../hosts/houdini/plugins/publish/validate_scene_review.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_scene_review.py b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py index 3a3b7e6659..4b7ced7f0d 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_scene_review.py +++ b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py @@ -30,8 +30,7 @@ class ValidateSceneReview(pyblish.api.InstancePlugin): def get_invalid_scene_path(self, instance): invalid = list() node = hou.node(instance.data.get("instance_node")) - scene_path = node.parm("scenepath").eval() - scene_path_node = hou.node(scene_path) + scene_path_node = node.parm("scenepath").evalAsNode() if not scene_path_node: invalid.append(scene_path_node) From b20a0c03b08f94806411e4901c8e3a303178ea54 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:34:47 +0200 Subject: [PATCH 172/428] Clarify report of scene path and resolution --- .../plugins/publish/validate_scene_review.py | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_scene_review.py b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py index 4b7ced7f0d..ade01d4b90 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_scene_review.py +++ b/openpype/hosts/houdini/plugins/publish/validate_scene_review.py @@ -17,33 +17,45 @@ class ValidateSceneReview(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid_scene_path(instance) + + report = [] if invalid: - raise PublishValidationError( - "Scene path does not exist: %s" % invalid, - title=self.label) + report.append( + "Scene path does not exist: '%s'" % invalid[0], + ) + invalid = self.get_invalid_resolution(instance) if invalid: + report.extend(invalid) + + if report: raise PublishValidationError( - "Invalid Resolution Setting", + "\n\n".join(report), title=self.label) def get_invalid_scene_path(self, instance): - invalid = list() - node = hou.node(instance.data.get("instance_node")) - scene_path_node = node.parm("scenepath").evalAsNode() - if not scene_path_node: - invalid.append(scene_path_node) - return invalid + node = hou.node(instance.data.get("instance_node")) + scene_path_parm = node.parm("scenepath") + scene_path_node = scene_path_parm.evalAsNode() + if not scene_path_node: + return [scene_path_parm.evalAsString()] def get_invalid_resolution(self, instance): - invalid = list() node = hou.node(instance.data.get("instance_node")) + + # The resolution setting is only used when Override Camera Resolution + # is enabled. So we skip validation if it is disabled. + override = node.parm("tres").eval() + if not override: + return + + invalid = [] res_width = node.parm("res1").eval() res_height = node.parm("res2").eval() if res_width == 0: - invalid.append(res_width) + invalid.append("Override Resolution width is set to zero.") if res_height == 0: - invalid.append(res_height) + invalid.append("Override Resolution height is set to zero") return invalid From d092e509342ce87982987ecb1184583d1eb02f77 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Mar 2023 17:38:51 +0200 Subject: [PATCH 173/428] reveiwable attribute is showing on demand --- openpype/hosts/nuke/api/plugin.py | 7 +++++-- openpype/hosts/nuke/plugins/create/create_write_image.py | 7 ------- .../hosts/nuke/plugins/create/create_write_prerender.py | 7 ------- openpype/hosts/nuke/plugins/create/create_write_render.py | 7 ------- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index aec87be5ab..67b60d45ca 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -301,8 +301,11 @@ class NukeWriteCreator(NukeCreator): def get_instance_attr_defs(self): attr_defs = [ self._get_render_target_enum(), - self._get_reviewable_bool() ] + # add reviewable attribute + if "reviewable" in self.instance_attributes: + attr_defs.append(self._get_reviewable_bool()) + return attr_defs def _get_render_target_enum(self): @@ -322,7 +325,7 @@ class NukeWriteCreator(NukeCreator): def _get_reviewable_bool(self): return BoolDef( "review", - default=("reviewable" in self.instance_attributes), + default=True, label="Review" ) diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index d38253ab2f..b74cea5dae 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -63,13 +63,6 @@ class CreateWriteImage(napi.NukeWriteCreator): default=nuke.frame() ) - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): linked_knobs_ = [] if "use_range_limit" in self.instance_attributes: diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 8103cb7c4d..387768b1dd 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -41,13 +41,6 @@ class CreateWritePrerender(napi.NukeWriteCreator): ] return attr_defs - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): linked_knobs_ = [] if "use_range_limit" in self.instance_attributes: diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 23efa62e36..09257f662e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -38,13 +38,6 @@ class CreateWriteRender(napi.NukeWriteCreator): ] return attr_defs - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): # add fpath_template write_data = { From 2475f9eb47e9a5b3ea5fb70f88c2d32c82bcb50e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:43:44 +0200 Subject: [PATCH 174/428] Shush hound + fix subset name for output --- .../houdini/plugins/create/create_review.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index ca9c88de11..8dd3f1429f 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -"""Creator plugin for creating pointcache alembics.""" +"""Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin -from openpype.pipeline import CreatedInstance from openpype.lib import EnumDef, BoolDef, NumberDef @@ -13,7 +12,7 @@ class CreateReview(plugin.HoudiniCreator): family = "review" icon = "video-camera" - def create(self, subset_name, instance_data, pre_create_data): # type: CreatedInstance + def create(self, subset_name, instance_data, pre_create_data): import hou instance_data.pop("active", None) @@ -30,13 +29,15 @@ class CreateReview(plugin.HoudiniCreator): frame_range = hou.playbar.frameRange() + filepath = "{root}/{subset}/{subset}.$F4.{ext}".format( + root=hou.text.expandString("$HIP/pyblish"), + subset="`chs(\"subset\")`", # keep dynamic link to subset + ext=pre_create_data.get("image_format") or "png" + ) + parms = { - "picture": "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}/{}.$F4.{}".format( - subset_name, - subset_name, - pre_create_data.get("image_format") or "png")), + "picture": filepath, + "trange": 1, # Unlike many other ROP nodes the opengl node does not default From 3124f3af97687c18dd1f4b34b980a012ef44ac03 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:44:15 +0200 Subject: [PATCH 175/428] Hound fixes --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 83a85ffb9a..c26d0813a6 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -41,7 +41,7 @@ class ExtractOpenGL(publish.Extractor, if not instance.data.get("keepImages"): tags.append("delete") - representation = { + representation = { "name": instance.data["imageFormat"], "ext": instance.data["imageFormat"], "files": output, From f29b7bfc4d82e37deee2fd0b316179fd85883075 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:45:36 +0200 Subject: [PATCH 176/428] Add houdini to default focal length burnin preset --- openpype/settings/defaults/project_settings/global.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 30e56300d1..4c4a7487cf 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -253,13 +253,14 @@ { "families": [], "hosts": [ - "maya" + "maya", + "houdini" ], "task_types": [], "task_names": [], "subsets": [], "burnins": { - "maya_burnin": { + "focal_length_burnin": { "TOP_LEFT": "{yy}-{mm}-{dd}", "TOP_CENTERED": "{focalLength:.2f} mm", "TOP_RIGHT": "{anatomy[version]}", From fd467450ea0601d7584a19aae274dad2344537f2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Mar 2023 17:46:26 +0200 Subject: [PATCH 177/428] Update docs --- website/docs/pype2/admin_presets_plugins.md | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index 44c2a28dec..0491ce70b3 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -294,17 +294,17 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"** - Additional keys in burnins: - | Burnin key | Description | - | --- | --- | - | frame_start | First frame number. | - | frame_end | Last frame number. | - | current_frame | Frame number for each frame. | - | duration | Count number of frames. | - | resolution_width | Resolution width. | - | resolution_height | Resolution height. | - | fps | Fps of an output. | - | timecode | Timecode by frame start and fps. | - | focalLength | **Only available in Maya**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | + | Burnin key | Description | + |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | + | frame_start | First frame number. | + | frame_end | Last frame number. | + | current_frame | Frame number for each frame. | + | duration | Count number of frames. | + | resolution_width | Resolution width. | + | resolution_height | Resolution height. | + | fps | Fps of an output. | + | timecode | Timecode by frame start and fps. | + | focalLength | **Only available in Maya and Houdini**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | :::warning `timecode` is specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`) From 777864461376cf2efa3080984ae90d2461e1621e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 31 Mar 2023 12:33:58 +0200 Subject: [PATCH 178/428] Update openpype/hosts/houdini/plugins/create/create_review.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/houdini/plugins/create/create_review.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 8dd3f1429f..8817b174bb 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -57,9 +57,13 @@ class CreateReview(plugin.HoudiniCreator): }) if self.selected_nodes: - # todo: allow only object paths? - node_paths = " ".join(node.path() for node in self.selected_nodes) - parms.update({"scenepath": node_paths}) + for node in self.selected_nodes: + path = node.path() + if node.type().name() == "cam": + parms.update({"camera": path}) + else: + node_paths = " ".join(path) + parms.update({"forceobjects": node_paths}) instance_node.setParms(parms) From 194387d329dd1221b063d2e9fc87ab93101de9ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 31 Mar 2023 13:59:04 +0200 Subject: [PATCH 179/428] Improve selection handling --- .../houdini/plugins/create/create_review.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 8817b174bb..ab06b30c35 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -57,13 +57,28 @@ class CreateReview(plugin.HoudiniCreator): }) if self.selected_nodes: + # The first camera found in selection we will use as camera + # Other node types we set in force objects + camera = None + force_objects = [] for node in self.selected_nodes: path = node.path() if node.type().name() == "cam": - parms.update({"camera": path}) + if camera: + continue + camera = path else: - node_paths = " ".join(path) - parms.update({"forceobjects": node_paths}) + force_objects.append(path) + + if not camera: + self.log.warning("No camera found in selection.") + + parms.update({ + "camera": camera or "", + "scenepath": "/obj", + "forceobjects": " ".join(force_objects), + "vobjects": "" # clear candidate objects from '*' value + }) instance_node.setParms(parms) From 889fafbd25dd1af8df9cbd43c704ed99e267d55c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 31 Mar 2023 16:12:41 +0200 Subject: [PATCH 180/428] Add docs (+ tweak some of the existing ones) --- website/docs/artist_hosts_houdini.md | 31 ++++++++++++++---- .../assets/houdini_review_create_attrs.png | Bin 0 -> 37048 bytes 2 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 website/docs/assets/houdini_review_create_attrs.png diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md index f2b128ffc6..8874a0b5cf 100644 --- a/website/docs/artist_hosts_houdini.md +++ b/website/docs/artist_hosts_houdini.md @@ -14,20 +14,29 @@ sidebar_label: Houdini - [Library Loader](artist_tools_library-loader) ## Publishing Alembic Cameras -You can publish baked camera in Alembic format. Select your camera and go **OpenPype -> Create** and select **Camera (abc)**. +You can publish baked camera in Alembic format. + +Select your camera and go **OpenPype -> Create** and select **Camera (abc)**. This will create Alembic ROP in **out** with path and frame range already set. This node will have a name you've assigned in the **Creator** menu. For example if you name the subset `Default`, output Alembic Driver will be named `cameraDefault`. After that, you can **OpenPype -> Publish** and after some validations your camera will be published to `abc` file. ## Publishing Composites - Image Sequences -You can publish image sequence directly from Houdini. You can use any `cop` network you have and publish image -sequence generated from it. For example I've created simple **cop** graph to generate some noise: +You can publish image sequences directly from Houdini's image COP networks. + +You can use any COP node and publish the image sequence generated from it. For example this simple graph to generate some noise: + ![Noise COP](assets/houdini_imagesequence_cop.png) -If I want to publish it, I'll select node I like - in this case `radialblur1` and go **OpenPype -> Create** and -select **Composite (Image Sequence)**. This will create `/out/imagesequenceNoise` Composite ROP (I've named my subset -*Noise*) with frame range set. When you hit **Publish** it will render image sequence from selected node. +To publish the output of the `radialblur1` go to **OpenPype -> Create** and +select **Composite (Image Sequence)**. If you name the variant *Noise* this will create the `/out/imagesequenceNoise` Composite ROP with the frame range set. + +When you hit **Publish** it will render image sequence from selected node. + +:::info Use selection +With *Use selection* is enabled on create the node you have selected when creating will be the node used for published. (It set the Composite ROP node's COP path to it). If you don't do this you'll have to manually set the path as needed on e.g. `/out/imagesequenceNoise` to ensure it outputs what you want. +::: ## Publishing Point Caches (alembic) Publishing point caches in alembic format is pretty straightforward, but it is by default enforcing better compatibility @@ -46,6 +55,16 @@ you handle `path` attribute is up to you, this is just an example.* Now select the `output0` node and go **OpenPype -> Create** and select **Point Cache**. It will create Alembic ROP `/out/pointcacheStrange` +## Publishing Reviews (OpenGL) +To generate a review output from Houdini you need to create a **review** instance. +Go to **OpenPype -> Create** and select **Review**. + +![Houdini Create Review](assets/houdini_review_create_attrs.png) + +On create, with the **Use Selection** checkbox enabled it will set up the first +camera found in your selection as the camera for the OpenGL ROP node and other +non-cameras are set in **Force Objects**. It will then render those even if +their display flag is disabled in your scene. ## Redshift :::note Work in progress diff --git a/website/docs/assets/houdini_review_create_attrs.png b/website/docs/assets/houdini_review_create_attrs.png new file mode 100644 index 0000000000000000000000000000000000000000..8735e79914033db1b9afb2ca3da4d33b1aad668f GIT binary patch literal 37048 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 literal 0 HcmV?d00001 From 2278478598dbda1b465f2d9108ce27106d807e21 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sun, 2 Apr 2023 08:25:06 +0100 Subject: [PATCH 181/428] Update openpype/hosts/maya/plugins/publish/collect_review.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_review.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index d15eb7a12b..a184865602 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -143,4 +143,8 @@ class CollectReview(pyblish.api.InstancePlugin): # Convert enum attribute index to string. index = instance.data.get("displayLights", 0) - instance.data["displayLights"] = lib.DISPLAY_LIGHTS[index] + display_lights = lib.DISPLAY_LIGHTS[index] + if display_lights == "project_settings": + # project_settings/maya/publish/ExtractPlayblast/capture_preset/Viewport Options/displayLights + display_lights = instance.context.data["project_settings"]["maya"]["publish"]["ExtractPlayblast"]["capture_preset"]["Viewport Options"]["displayLights"] # noqa + instance.data["displayLights"] = display_lights From b9e9750377d44836c94013f2222422bca736c644 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sun, 2 Apr 2023 08:25:25 +0100 Subject: [PATCH 182/428] Update openpype/hosts/maya/plugins/publish/extract_playblast.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 801f05a770..2167f2c5b3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -95,10 +95,8 @@ class ExtractPlayblast(publish.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) - # Show lighting mode. - display_lights = instance.data["displayLights"] - if display_lights != "project_settings": - preset["viewport_options"]["displayLights"] = display_lights + # Use displayLights setting from instance + preset["viewport_options"]["displayLights"] = instance.data["displayLights"] # Override transparency if requested. transparency = instance.data.get("transparency", 0) From 3ebac0b326e4f06aaa5a396554e4fff16df5efeb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sun, 2 Apr 2023 08:25:34 +0100 Subject: [PATCH 183/428] Update openpype/hosts/maya/plugins/publish/extract_thumbnail.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 79c768228f..92d0141f01 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -105,11 +105,8 @@ class ExtractThumbnail(publish.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) - # Show lighting mode. - display_lights = instance.data["displayLights"] - if display_lights != "project_settings": - preset["viewport_options"]["displayLights"] = display_lights - + # Use displayLights setting from instance + preset["viewport_options"]["displayLights"] = instance.data["displayLights"] # Override transparency if requested. transparency = instance.data.get("transparency", 0) if transparency != 0: From 2e234a84dc791bf3452fb9e0610a23bda3cec233 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 2 Apr 2023 08:34:26 +0100 Subject: [PATCH 184/428] Hound --- openpype/hosts/maya/plugins/publish/extract_playblast.py | 3 ++- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 0381a8adc1..f790d08ae3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -117,7 +117,8 @@ class ExtractPlayblast(publish.Extractor): pm.currentTime(refreshFrameInt, edit=True) # Use displayLights setting from instance - preset["viewport_options"]["displayLights"] = instance.data["displayLights"] + key = "displayLights" + preset["viewport_options"][key] = instance.data[key] # Override transparency if requested. transparency = instance.data.get("transparency", 0) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 430322c911..d66f65ce88 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -106,7 +106,9 @@ class ExtractThumbnail(publish.Extractor): pm.currentTime(refreshFrameInt, edit=True) # Use displayLights setting from instance - preset["viewport_options"]["displayLights"] = instance.data["displayLights"] + key = "displayLights" + preset["viewport_options"][key] = instance.data[key] + # Override transparency if requested. transparency = instance.data.get("transparency", 0) if transparency != 0: From 76c0a0266f9ea976d992718dc0c3a4a3ca0c62c3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 11:59:23 +0200 Subject: [PATCH 185/428] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- .../plugins/publish/collect_textureset_images.py | 12 ++++++------ .../publish/collect_workfile_representation.py | 10 +++++----- .../plugins/publish/extract_textures.py | 2 +- .../plugins/publish/save_workfile.py | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 4e800bd623..a93b830de0 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -62,7 +62,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa print("Reload succeeded") else: - raise RuntimeError("Reload of mesh failed") + raise LoadError("Reload of mesh failed") path = self.fname substance_painter.project.reload_mesh(path, @@ -105,7 +105,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if status == substance_painter.project.ReloadMeshStatus.SUCCESS: print("Reload succeeded") else: - raise RuntimeError("Reload of mesh failed") + raise LoaderError("Reload of mesh failed") substance_painter.project.reload_mesh(path, settings, on_mesh_reload) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 14168138b6..56694614eb 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -19,7 +19,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # TODO: Detect what source data channels end up in each file label = "Collect Texture Set images" - hosts = ['substancepainter'] + hosts = ["substancepainter"] families = ["textureSet"] order = pyblish.api.CollectorOrder @@ -55,7 +55,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): first_filepath = outputs[0]["filepath"] fnames = [os.path.basename(output["filepath"]) for output in outputs] ext = os.path.splitext(first_filepath)[1] - assert ext.lstrip('.'), f"No extension: {ext}" + assert ext.lstrip("."), f"No extension: {ext}" map_identifier = strip_template(template) # Define the suffix we want to give this particular texture @@ -78,9 +78,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Prepare representation representation = { - 'name': ext.lstrip("."), - 'ext': ext.lstrip("."), - 'files': fnames if len(fnames) > 1 else fnames[0], + "name": ext.lstrip("."), + "ext": ext.lstrip("."), + "files": fnames if len(fnames) > 1 else fnames[0], } # Mark as UDIM explicitly if it has UDIM tiles. @@ -105,7 +105,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): image_instance.data["subset"] = image_subset image_instance.data["family"] = "image" image_instance.data["families"] = ["image", "textures"] - image_instance.data['representations'] = [representation] + image_instance.data["representations"] = [representation] # Group the textures together in the loader image_instance.data["subsetGroup"] = instance.data["subset"] diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py b/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py index 563c2d4c07..8d98d0b014 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_workfile_representation.py @@ -7,7 +7,7 @@ class CollectWorkfileRepresentation(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Workfile representation" - hosts = ['substancepainter'] + hosts = ["substancepainter"] families = ["workfile"] def process(self, instance): @@ -18,9 +18,9 @@ class CollectWorkfileRepresentation(pyblish.api.InstancePlugin): folder, file = os.path.split(current_file) filename, ext = os.path.splitext(file) - instance.data['representations'] = [{ - 'name': ext.lstrip("."), - 'ext': ext.lstrip("."), - 'files': file, + instance.data["representations"] = [{ + "name": ext.lstrip("."), + "ext": ext.lstrip("."), + "files": file, "stagingDir": folder, }] diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index bd933610f4..b9654947db 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -15,7 +15,7 @@ class ExtractTextures(publish.Extractor, """ label = "Extract Texture Set" - hosts = ['substancepainter'] + hosts = ["substancepainter"] families = ["textureSet"] # Run before thumbnail extractors diff --git a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py index f19deccb0e..4874b5e5c7 100644 --- a/openpype/hosts/substancepainter/plugins/publish/save_workfile.py +++ b/openpype/hosts/substancepainter/plugins/publish/save_workfile.py @@ -16,7 +16,7 @@ class SaveCurrentWorkfile(pyblish.api.ContextPlugin): def process(self, context): host = registered_host() - if context.data['currentFile'] != host.get_current_workfile(): + if context.data["currentFile"] != host.get_current_workfile(): raise KnownPublishError("Workfile has changed during publishing!") if host.has_unsaved_changes(): From 35428df6b0942e779a0bbaa50578e0c0fbfa2921 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 12:00:51 +0200 Subject: [PATCH 186/428] Fix LoadError --- openpype/hosts/substancepainter/plugins/load/load_mesh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index a93b830de0..2450a9316e 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,6 +2,7 @@ from openpype.pipeline import ( load, get_representation_path, ) +from openpype.pipeline.load import LoadError from openpype.hosts.substancepainter.api.pipeline import ( imprint_container, set_container_metadata, @@ -105,7 +106,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if status == substance_painter.project.ReloadMeshStatus.SUCCESS: print("Reload succeeded") else: - raise LoaderError("Reload of mesh failed") + raise LoadError("Reload of mesh failed") substance_painter.project.reload_mesh(path, settings, on_mesh_reload) From 5c0dee53188e12b7ddb8eec364495596b36de29c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 12:01:24 +0200 Subject: [PATCH 187/428] Log instead of print --- openpype/hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 2450a9316e..822095641d 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -61,7 +61,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa - print("Reload succeeded") + self.log.info("Reload succeeded") else: raise LoadError("Reload of mesh failed") @@ -104,7 +104,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): if status == substance_painter.project.ReloadMeshStatus.SUCCESS: - print("Reload succeeded") + self.log.info("Reload succeeded") else: raise LoadError("Reload of mesh failed") From 4300939199f9cfcd4626c0bcbdafdf5a05926649 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 12:17:48 +0200 Subject: [PATCH 188/428] Allow formatting shelf path using anatomy data --- .../hosts/substancepainter/api/pipeline.py | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index b995c9030d..9406fb8edb 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -9,17 +9,23 @@ import substance_painter.ui import substance_painter.event import substance_painter.project -from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost -from openpype.settings import get_current_project_settings - import pyblish.api +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost +from openpype.settings import ( + get_current_project_settings, + get_system_settings +) + +from openpype.pipeline.template_data import get_template_data_with_names from openpype.pipeline import ( register_creator_plugin_path, register_loader_plugin_path, - AVALON_CONTAINER_ID + AVALON_CONTAINER_ID, + Anatomy ) from openpype.lib import ( + StringTemplate, register_event_callback, emit_event, ) @@ -234,9 +240,32 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def _install_shelves(self, project_settings): shelves = project_settings["substancepainter"].get("shelves", {}) + if not shelves: + return + + # Prepare formatting data if we detect any path which might have + # template tokens like {asset} in there. + formatting_data = {} + has_formatting_entries = any("{" in path for path in shelves.values()) + if has_formatting_entries: + project_name = self.get_current_project_name() + asset_name = self.get_current_asset_name() + task_name = self.get_current_asset_name() + system_settings = get_system_settings() + formatting_data = get_template_data_with_names(project_name, + asset_name, + task_name, + system_settings) + anatomy = Anatomy(project_name) + formatting_data["root"] = anatomy.roots + for name, path in shelves.items(): - # TODO: Allow formatting with anatomy for the paths shelf_name = None + + # Allow formatting with anatomy for the paths + if "{" in path: + path = StringTemplate.format_template(path, formatting_data) + try: shelf_name = lib.load_shelf(path, name=name) except ValueError as exc: From 7facba7905059d9bde7034e8a73bf1ce3b221733 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 12:28:52 +0200 Subject: [PATCH 189/428] Update documentation --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 2de9038f3f..c17f707830 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -255,7 +255,7 @@ suffix is **"client"** then the final suffix is **"h264_client"**. | resolution_height | Resolution height. | | fps | Fps of an output. | | timecode | Timecode by frame start and fps. | - | focalLength | **Only available in Maya**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | + | focalLength | **Only available in Maya and Houdini**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | :::warning `timecode` is a specific key that can be **only at the end of content**. (`"BOTTOM_RIGHT": "TC: {timecode}"`) From 6353251ee0f967aea5a7aa17b16e55ffcf7b1c04 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 12:29:58 +0200 Subject: [PATCH 190/428] Cosmetics --- website/docs/pype2/admin_presets_plugins.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index 0491ce70b3..6a057f4bb4 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -294,16 +294,16 @@ If source representation has suffix **"h264"** and burnin suffix is **"client"** - Additional keys in burnins: - | Burnin key | Description | - |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | - | frame_start | First frame number. | - | frame_end | Last frame number. | - | current_frame | Frame number for each frame. | - | duration | Count number of frames. | - | resolution_width | Resolution width. | - | resolution_height | Resolution height. | - | fps | Fps of an output. | - | timecode | Timecode by frame start and fps. | + | Burnin key | Description | + | --- | --- | + | frame_start | First frame number. | + | frame_end | Last frame number. | + | current_frame | Frame number for each frame. | + | duration | Count number of frames. | + | resolution_width | Resolution width. | + | resolution_height | Resolution height. | + | fps | Fps of an output. | + | timecode | Timecode by frame start and fps. | | focalLength | **Only available in Maya and Houdini**

Camera focal length per frame. Use syntax `{focalLength:.2f}` for decimal truncating. Eg. `35.234985` with `{focalLength:.2f}` would produce `35.23`, whereas `{focalLength:.0f}` would produce `35`. | :::warning From 87dc14fe9e91c202d4eefa82f85093a4a2814c76 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 3 Apr 2023 11:59:16 +0100 Subject: [PATCH 191/428] Default values for profiles. --- .../defaults/project_settings/maya.json | 6 +- openpype/settings/entities/color_entity.py | 6 +- openpype/settings/entities/input_entities.py | 4 +- .../schemas/schema_maya_capture.json | 267 ++++++++++++------ 4 files changed, 188 insertions(+), 95 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4044bdf5df..f6162182e8 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -829,8 +829,8 @@ "rendererName": "vp2Renderer" }, "Resolution": { - "width": 1920, - "height": 1080 + "width": 0, + "height": 0 }, "Viewport Options": { "override_viewport_options": true, @@ -896,7 +896,7 @@ "pivots": false, "planes": false, "pluginShapes": false, - "polymeshes": false, + "polymeshes": true, "strokes": false, "subdivSurfaces": false, "textures": false diff --git a/openpype/settings/entities/color_entity.py b/openpype/settings/entities/color_entity.py index bdaab6f583..a542f2fa38 100644 --- a/openpype/settings/entities/color_entity.py +++ b/openpype/settings/entities/color_entity.py @@ -11,7 +11,9 @@ class ColorEntity(InputEntity): def _item_initialization(self): self.valid_value_types = (list, ) - self.value_on_not_set = [0, 0, 0, 255] + self.value_on_not_set = self.convert_to_valid_type( + self.schema_data.get("default", [0, 0, 0, 255]) + ) self.use_alpha = self.schema_data.get("use_alpha", True) def set_override_state(self, *args, **kwargs): @@ -64,6 +66,6 @@ class ColorEntity(InputEntity): new_value.append(item) # Make sure - if not self.use_alpha: + if hasattr(self, "use_alpha") and not self.use_alpha: new_value[3] = 255 return new_value diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 89f12afd9b..842117ad48 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -442,7 +442,9 @@ class TextEntity(InputEntity): def _item_initialization(self): self.valid_value_types = (STRING_TYPE, ) - self.value_on_not_set = "" + self.value_on_not_set = self.convert_to_valid_type( + self.schema_data.get("default", "") + ) # GUI attributes self.multiline = self.schema_data.get("multiline", False) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 1d0f94e5b8..beaa7c442d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -46,12 +46,14 @@ { "type": "text", "key": "compression", - "label": "Encoding" + "label": "Encoding", + "default": "png" }, { "type": "text", "key": "format", - "label": "Format" + "label": "Format", + "default": "image" }, { "type": "number", @@ -59,7 +61,8 @@ "label": "Quality", "decimal": 0, "minimum": 0, - "maximum": 100 + "maximum": 100, + "default": 95 }, { @@ -79,22 +82,26 @@ { "type": "color", "key": "background", - "label": "Background Color: " + "label": "Background Color: ", + "default": [125, 125, 125, 255] }, { "type": "color", "key": "backgroundBottom", - "label": "Background Bottom: " + "label": "Background Bottom: ", + "default": [125, 125, 125, 255] }, { "type": "color", "key": "backgroundTop", - "label": "Background Top: " + "label": "Background Top: ", + "default": [125, 125, 125, 255] }, { "type": "boolean", "key": "override_display", - "label": "Override display options" + "label": "Override display options", + "default": true } ] }, @@ -112,17 +119,20 @@ { "type": "boolean", "key": "isolate_view", - "label": " Isolate view" + "label": " Isolate view", + "default": true }, { "type": "boolean", "key": "off_screen", - "label": " Off Screen" + "label": " Off Screen", + "default": true }, { "type": "boolean", "key": "pan_zoom", - "label": " 2D Pan/Zoom" + "label": " 2D Pan/Zoom", + "default": false } ] }, @@ -143,7 +153,8 @@ "label": "Renderer name", "enum_items": [ { "vp2Renderer": "Viewport 2.0" } - ] + ], + "default": "vp2Renderer" } ] }, @@ -164,7 +175,8 @@ "label": " Width", "decimal": 0, "minimum": 0, - "maximum": 99999 + "maximum": 99999, + "default": 0 }, { "type": "number", @@ -172,7 +184,8 @@ "label": "Height", "decimal": 0, "minimum": 0, - "maximum": 99999 + "maximum": 99999, + "default": 0 } ] }, @@ -188,7 +201,8 @@ { "type": "boolean", "key": "override_viewport_options", - "label": "Override Viewport Options" + "label": "Override Viewport Options", + "default": true }, { "type": "enum", @@ -200,18 +214,21 @@ { "selected": "Selected Lights"}, { "flat": "Flat Lighting"}, { "nolights": "No Lights"} - ] + ], + "default": "default" }, { "type": "boolean", "key": "displayTextures", - "label": "Display Textures" + "label": "Display Textures", + "default": true }, { "type": "number", "key": "textureMaxResolution", "label": "Texture Clamp Resolution", - "decimal": 0 + "decimal": 0, + "default": 1024 }, { "type": "splitter" @@ -223,7 +240,8 @@ { "type":"boolean", "key": "renderDepthOfField", - "label": "Depth of Field" + "label": "Depth of Field", + "default": true }, { "type": "splitter" @@ -231,12 +249,14 @@ { "type": "boolean", "key": "shadows", - "label": "Display Shadows" + "label": "Display Shadows", + "default": true }, { "type": "boolean", "key": "twoSidedLighting", - "label": "Two Sided Lighting" + "label": "Two Sided Lighting", + "default": true }, { "type": "splitter" @@ -244,7 +264,8 @@ { "type": "boolean", "key": "lineAAEnable", - "label": "Enable Anti-Aliasing" + "label": "Enable Anti-Aliasing", + "default": true }, { "type": "number", @@ -252,7 +273,8 @@ "label": "Anti Aliasing Samples", "decimal": 0, "minimum": 0, - "maximum": 32 + "maximum": 32, + "default": 8 }, { "type": "splitter" @@ -260,42 +282,50 @@ { "type": "boolean", "key": "useDefaultMaterial", - "label": "Use Default Material" + "label": "Use Default Material", + "default": false }, { "type": "boolean", "key": "wireframeOnShaded", - "label": "Wireframe On Shaded" + "label": "Wireframe On Shaded", + "default": false }, { "type": "boolean", "key": "xray", - "label": "X-Ray" + "label": "X-Ray", + "default": false }, { "type": "boolean", "key": "jointXray", - "label": "X-Ray Joints" + "label": "X-Ray Joints", + "default": false }, { "type": "boolean", "key": "backfaceCulling", - "label": "Backface Culling" + "label": "Backface Culling", + "default": false }, { "type": "boolean", "key": "ssaoEnable", - "label": "Screen Space Ambient Occlusion" + "label": "Screen Space Ambient Occlusion", + "default": false }, { "type": "number", "key": "ssaoAmount", - "label": "SSAO Amount" + "label": "SSAO Amount", + "default": 1 }, { "type": "number", "key": "ssaoRadius", - "label": "SSAO Radius" + "label": "SSAO Radius", + "default": 16 }, { "type": "number", @@ -303,7 +333,8 @@ "label": "SSAO Filter Radius", "decimal": 0, "minimum": 1, - "maximum": 32 + "maximum": 32, + "default": 16 }, { "type": "number", @@ -311,7 +342,8 @@ "label": "SSAO Samples", "decimal": 0, "minimum": 8, - "maximum": 32 + "maximum": 32, + "default": 16 }, { "type": "splitter" @@ -319,7 +351,8 @@ { "type": "boolean", "key": "fogging", - "label": "Enable Hardware Fog" + "label": "Enable Hardware Fog", + "default": false }, { "type": "enum", @@ -329,7 +362,8 @@ { "0": "Linear"}, { "1": "Exponential"}, { "2": "Exponential Squared"} - ] + ], + "default": "0" }, { "type": "number", @@ -337,22 +371,26 @@ "label": "Fog Density", "decimal": 2, "minimum": 0, - "maximum": 1 + "maximum": 1, + "default": 0 }, { "type": "number", "key": "hwFogStart", - "label": "Fog Start" + "label": "Fog Start", + "default": 0 }, { "type": "number", "key": "hwFogEnd", - "label": "Fog End" + "label": "Fog End", + "default": 100 }, { "type": "number", "key": "hwFogAlpha", - "label": "Fog Alpha" + "label": "Fog Alpha", + "default": 0 }, { "type": "number", @@ -360,7 +398,8 @@ "label": "Fog Color R", "decimal": 2, "minimum": 0, - "maximum": 1 + "maximum": 1, + "default": 1 }, { "type": "number", @@ -368,7 +407,8 @@ "label": "Fog Color G", "decimal": 2, "minimum": 0, - "maximum": 1 + "maximum": 1, + "default": 1 }, { "type": "number", @@ -376,7 +416,8 @@ "label": "Fog Color B", "decimal": 2, "minimum": 0, - "maximum": 1 + "maximum": 1, + "default": 1 }, { "type": "splitter" @@ -384,7 +425,8 @@ { "type": "boolean", "key": "motionBlurEnable", - "label": "Enable Motion Blur" + "label": "Enable Motion Blur", + "default": false }, { "type": "number", @@ -392,7 +434,8 @@ "label": "Motion Blur Sample Count", "decimal": 0, "minimum": 8, - "maximum": 32 + "maximum": 32, + "default": 8 }, { "type": "number", @@ -400,7 +443,8 @@ "label": "Shutter Open Fraction", "decimal": 3, "minimum": 0.01, - "maximum": 32 + "maximum": 32, + "default": 0.2 }, { "type": "splitter" @@ -412,182 +456,218 @@ { "type": "boolean", "key": "cameras", - "label": "Cameras" + "label": "Cameras", + "default": false }, { "type": "boolean", "key": "clipGhosts", - "label": "Clip Ghosts" + "label": "Clip Ghosts", + "default": false }, { "type": "boolean", "key": "deformers", - "label": "Deformers" + "label": "Deformers", + "default": false }, { "type": "boolean", "key": "dimensions", - "label": "Dimensions" + "label": "Dimensions", + "default": false }, { "type": "boolean", "key": "dynamicConstraints", - "label": "Dynamic Constraints" + "label": "Dynamic Constraints", + "default": false }, { "type": "boolean", "key": "dynamics", - "label": "Dynamics" + "label": "Dynamics", + "default": false }, { "type": "boolean", "key": "fluids", - "label": "Fluids" + "label": "Fluids", + "default": false }, { "type": "boolean", "key": "follicles", - "label": "Follicles" + "label": "Follicles", + "default": false }, { "type": "boolean", "key": "gpuCacheDisplayFilter", - "label": "GPU Cache" + "label": "GPU Cache", + "default": false }, { "type": "boolean", "key": "greasePencils", - "label": "Grease Pencil" + "label": "Grease Pencil", + "default": false }, { "type": "boolean", "key": "grid", - "label": "Grid" + "label": "Grid", + "default": false }, { "type": "boolean", "key": "hairSystems", - "label": "Hair Systems" + "label": "Hair Systems", + "default": true }, { "type": "boolean", "key": "handles", - "label": "Handles" + "label": "Handles", + "default": false }, { "type": "boolean", "key": "headsUpDisplay", - "label": "HUD" + "label": "HUD", + "default": false }, { "type": "boolean", "key": "ikHandles", - "label": "IK Handles" + "label": "IK Handles", + "default": false }, { "type": "boolean", "key": "imagePlane", - "label": "Image Planes" + "label": "Image Planes", + "default": true }, { "type": "boolean", "key": "joints", - "label": "Joints" + "label": "Joints", + "default": false }, { "type": "boolean", "key": "lights", - "label": "Lights" + "label": "Lights", + "default": false }, { "type": "boolean", "key": "locators", - "label": "Locators" + "label": "Locators", + "default": false }, { "type": "boolean", "key": "manipulators", - "label": "Manipulators" + "label": "Manipulators", + "default": false }, { "type": "boolean", "key": "motionTrails", - "label": "Motion Trails" + "label": "Motion Trails", + "default": false }, { "type": "boolean", "key": "nCloths", - "label": "nCloths" + "label": "nCloths", + "default": false }, { "type": "boolean", "key": "nParticles", - "label": "nParticles" + "label": "nParticles", + "default": false }, { "type": "boolean", "key": "nRigids", - "label": "nRigids" + "label": "nRigids", + "default": false }, { "type": "boolean", "key": "controlVertices", - "label": "NURBS CVs" + "label": "NURBS CVs", + "default": false }, { "type": "boolean", "key": "nurbsCurves", - "label": "NURBS Curves" + "label": "NURBS Curves", + "default": false }, { "type": "boolean", "key": "hulls", - "label": "NURBS Hulls" + "label": "NURBS Hulls", + "default": false }, { "type": "boolean", "key": "nurbsSurfaces", - "label": "NURBS Surfaces" + "label": "NURBS Surfaces", + "default": false }, { "type": "boolean", "key": "particleInstancers", - "label": "Particle Instancers" + "label": "Particle Instancers", + "default": false }, { "type": "boolean", "key": "pivots", - "label": "Pivots" + "label": "Pivots", + "default": false }, { "type": "boolean", "key": "planes", - "label": "Planes" + "label": "Planes", + "default": false }, { "type": "boolean", "key": "pluginShapes", - "label": "Plugin Shapes" + "label": "Plugin Shapes", + "default": false }, { "type": "boolean", "key": "polymeshes", - "label": "Polygons" + "label": "Polygons", + "default": true }, { "type": "boolean", "key": "strokes", - "label": "Strokes" + "label": "Strokes", + "default": false }, { "type": "boolean", "key": "subdivSurfaces", - "label": "Subdiv Surfaces" + "label": "Subdiv Surfaces", + "default": false }, { "type": "boolean", "key": "textures", - "label": "Texture Placements" + "label": "Texture Placements", + "default": false } ] }, @@ -600,42 +680,50 @@ { "type": "boolean", "key": "displayGateMask", - "label": "Display Gate Mask" + "label": "Display Gate Mask", + "default": false }, { "type": "boolean", "key": "displayResolution", - "label": "Display Resolution" + "label": "Display Resolution", + "default": false }, { "type": "boolean", "key": "displayFilmGate", - "label": "Display Film Gate" + "label": "Display Film Gate", + "default": false }, { "type": "boolean", "key": "displayFieldChart", - "label": "Display Field Chart" + "label": "Display Field Chart", + "default": false }, { "type": "boolean", "key": "displaySafeAction", - "label": "Display Safe Action" + "label": "Display Safe Action", + "default": false }, { "type": "boolean", "key": "displaySafeTitle", - "label": "Display Safe Title" + "label": "Display Safe Title", + "default": false }, { "type": "boolean", "key": "displayFilmPivot", - "label": "Display Film Pivot" + "label": "Display Film Pivot", + "default": false }, { "type": "boolean", "key": "displayFilmOrigin", - "label": "Display Film Origin" + "label": "Display Film Origin", + "default": false }, { "type": "number", @@ -643,7 +731,8 @@ "label": "Overscan", "decimal": 1, "minimum": 0, - "maximum": 10 + "maximum": 10, + "default": 1 } ] } From 655ae7e7f879cac7127fc754bd472426d09ce9b1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 3 Apr 2023 12:09:26 +0100 Subject: [PATCH 192/428] create review for profiles --- .../maya/plugins/create/create_review.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index e709239ae7..5a1afe9790 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -1,8 +1,14 @@ from collections import OrderedDict +import json + from openpype.hosts.maya.api import ( lib, plugin ) +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io +from openpype.lib.profiles_filtering import filter_profiles +from openpype.client import get_asset_by_name class CreateReview(plugin.Creator): @@ -32,6 +38,30 @@ class CreateReview(plugin.Creator): super(CreateReview, self).__init__(*args, **kwargs) data = OrderedDict(**self.data) + project_name = legacy_io.Session["AVALON_PROJECT"] + profiles = get_project_settings( + project_name + )["maya"]["publish"]["ExtractPlayblast"]["profiles"] + + preset = None + if not profiles: + self.log.warning("No profiles present for extract playblast.") + else: + asset_doc = get_asset_by_name(project_name, data["asset"]) + task_name = legacy_io.Session["AVALON_TASK"] + task_type = asset_doc["data"]["tasks"][task_name]["type"] + + filtering_criteria = { + "hosts": "maya", + "families": "review", + "task_names": task_name, + "task_types": task_type, + "subset": data["subset"] + } + preset = filter_profiles( + profiles, filtering_criteria, logger=self.log + )["capture_preset"] + # Option for using Maya or asset frame range in settings. frame_range = lib.get_frame_range() if self.useMayaTimeline: @@ -40,6 +70,7 @@ class CreateReview(plugin.Creator): data[key] = value data["fps"] = lib.collect_animation_data(fps=True)["fps"] + data["review_width"] = self.Width data["review_height"] = self.Height data["isolate"] = self.isolate @@ -48,4 +79,16 @@ class CreateReview(plugin.Creator): data["transparency"] = self.transparency data["panZoom"] = self.panZoom + if preset: + self.log.info( + "Using preset: {}".format( + json.dumps(preset, indent=4, sort_keys=True) + ) + ) + data["review_width"] = preset["Resolution"]["width"] + data["review_height"] = preset["Resolution"]["height"] + data["isolate"] = preset["Generic"]["isolate_view"] + data["imagePlane"] = preset["Viewport Options"]["imagePlane"] + data["panZoom"] = preset["Generic"]["pan_zoom"] + self.data = data From 9d68db0e16bc91a87f0b4fd4f7935426c70a8ffb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 3 Apr 2023 16:03:57 +0200 Subject: [PATCH 193/428] Validate the generated output maps for missing channels --- .../plugins/create/create_textures.py | 10 +- .../publish/collect_textureset_images.py | 2 +- .../plugins/publish/extract_textures.py | 18 ++- .../plugins/publish/validate_ouput_maps.py | 108 ++++++++++++++++++ 4 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py diff --git a/openpype/hosts/substancepainter/plugins/create/create_textures.py b/openpype/hosts/substancepainter/plugins/create/create_textures.py index 19133768a5..6070a06367 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_textures.py +++ b/openpype/hosts/substancepainter/plugins/create/create_textures.py @@ -5,7 +5,8 @@ from openpype.pipeline import CreatedInstance, Creator, CreatorError from openpype.lib import ( EnumDef, UILabelDef, - NumberDef + NumberDef, + BoolDef ) from openpype.hosts.substancepainter.api.pipeline import ( @@ -80,6 +81,13 @@ class CreateTextures(Creator): EnumDef("exportPresetUrl", items=get_export_presets(), label="Output Template"), + BoolDef("allowSkippedMaps", + label="Allow Skipped Output Maps", + tooltip="When enabled this allows the publish to ignore " + "output maps in the used output template if one " + "or more maps are skipped due to the required " + "channels not being present in the current file.", + default=True), EnumDef("exportFileFormat", items={ None: "Based on output template", diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 56694614eb..50a96b94ae 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -97,7 +97,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): representation["stagingDir"] = staging_dir # Clone the instance - image_instance = context.create_instance(instance.name) + image_instance = context.create_instance(image_subset) image_instance[:] = instance[:] image_instance.data.update(copy.deepcopy(instance.data)) image_instance.data["name"] = image_subset diff --git a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py index b9654947db..bb6f15ead9 100644 --- a/openpype/hosts/substancepainter/plugins/publish/extract_textures.py +++ b/openpype/hosts/substancepainter/plugins/publish/extract_textures.py @@ -1,6 +1,7 @@ -from openpype.pipeline import KnownPublishError, publish import substance_painter.export +from openpype.pipeline import KnownPublishError, publish + class ExtractTextures(publish.Extractor, publish.ColormanagedPyblishPluginMixin): @@ -31,21 +32,19 @@ class ExtractTextures(publish.Extractor, "Failed to export texture set: {}".format(result.message) ) + # Log what files we generated for (texture_set_name, stack_name), maps in result.textures.items(): # Log our texture outputs - self.log.info(f"Processing stack: {texture_set_name} {stack_name}") + self.log.info(f"Exported stack: {texture_set_name} {stack_name}") for texture_map in maps: self.log.info(f"Exported texture: {texture_map}") - # TODO: Confirm outputs match what we collected - # TODO: Confirm the files indeed exist - # TODO: make sure representations are registered - # We'll insert the color space data for each image instance that we # added into this texture set. The collector couldn't do so because # some anatomy and other instance data needs to be collected prior context = instance.context for image_instance in instance: + representation = next(iter(image_instance.data["representations"])) colorspace = image_instance.data.get("colorspace") if not colorspace: @@ -53,10 +52,9 @@ class ExtractTextures(publish.Extractor, f"{image_instance}") continue - for representation in image_instance.data["representations"]: - self.set_representation_colorspace(representation, - context=context, - colorspace=colorspace) + self.set_representation_colorspace(representation, + context=context, + colorspace=colorspace) # The TextureSet instance should not be integrated. It generates no # output data. Instead the separated texture instances are generated diff --git a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py new file mode 100644 index 0000000000..203cf7c5fe --- /dev/null +++ b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py @@ -0,0 +1,108 @@ +import copy +import os + +import pyblish.api + +import substance_painter.export + +from openpype.pipeline import PublishValidationError + + +class ValidateOutputMaps(pyblish.api.InstancePlugin): + """Validate all output maps for Output Template are generated. + + Output maps will be skipped by Substance Painter if it is an output + map in the Substance Output Template which uses channels that the current + substance painter project has not painted or generated. + + """ + + order = pyblish.api.ValidatorOrder + label = "Validate output maps" + hosts = ["substancepainter"] + families = ["textureSet"] + + def process(self, instance): + + config = instance.data["exportConfig"] + + # Substance Painter API does not allow to query the actual output maps + # it will generate without actually exporting the files. So we try to + # generate the smallest size / fastest export as possible + config = copy.deepcopy(config) + parameters = config["exportParameters"][0]["parameters"] + parameters["sizeLog2"] = [1, 1] # output 2x2 images (smallest) + parameters["paddingAlgorithm"] = "passthrough" # no dilation (faster) + parameters["dithering"] = False # no dithering (faster) + config["exportParameters"][0]["parameters"]["sizeLog2"] = [1, 1] + + result = substance_painter.export.export_project_textures(config) + if result.status != substance_painter.export.ExportStatus.Success: + raise PublishValidationError( + "Failed to export texture set: {}".format(result.message) + ) + + generated_files = set() + for texture_maps in result.textures.values(): + for texture_map in texture_maps: + generated_files.add(os.path.normpath(texture_map)) + # Directly clean up our temporary export + os.remove(texture_map) + + creator_attributes = instance.data.get("creator_attributes", {}) + allow_skipped_maps = creator_attributes.get("allowSkippedMaps", True) + error_report_missing = [] + for image_instance in instance: + + # Confirm whether the instance has its expected files generated. + # We assume there's just one representation and that it is + # the actual texture representation from the collector. + representation = next(iter(image_instance.data["representations"])) + staging_dir = representation["stagingDir"] + filenames = representation["files"] + if not isinstance(filenames, (list, tuple)): + # Convert single file to list + filenames = [filenames] + + missing = [] + for filename in filenames: + filepath = os.path.join(staging_dir, filename) + filepath = os.path.normpath(filepath) + if filepath not in generated_files: + self.log.warning(f"Missing texture: {filepath}") + missing.append(filepath) + + if allow_skipped_maps: + # TODO: This is changing state on the instance's which + # usually should not be done during validation. + self.log.warning(f"Disabling texture instance: " + f"{image_instance}") + image_instance.data["active"] = False + image_instance.data["integrate"] = False + representation.setdefault("tags", []).append("delete") + continue + + if missing: + error_report_missing.append((image_instance, missing)) + + if error_report_missing: + + message = ( + "The Texture Set skipped exporting some output maps which are " + "defined in the Output Template. This happens if the Output " + "Templates exports maps from channels which you do not " + "have in your current Substance Painter project.\n\n" + "To allow this enable the *Allow Skipped Output Maps* setting " + "on the instance.\n\n" + f"Instance {instance} skipped exporting output maps:\n" + "" + ) + + for image_instance, missing in error_report_missing: + missing_str = ", ".join(missing) + message += f"- **{image_instance}** skipped: {missing_str}\n" + + raise PublishValidationError( + message=message, + title="Missing output maps" + ) From 23568e5b060caff2a56d65ba3229cc74f588b62c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 4 Apr 2023 00:11:49 +0200 Subject: [PATCH 194/428] Fix allow skipped maps logic --- .../plugins/publish/validate_ouput_maps.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py index 203cf7c5fe..e3d4c733e1 100644 --- a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py +++ b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py @@ -72,17 +72,19 @@ class ValidateOutputMaps(pyblish.api.InstancePlugin): self.log.warning(f"Missing texture: {filepath}") missing.append(filepath) + if not missing: + continue + if allow_skipped_maps: # TODO: This is changing state on the instance's which - # usually should not be done during validation. + # should not be done during validation. self.log.warning(f"Disabling texture instance: " f"{image_instance}") image_instance.data["active"] = False image_instance.data["integrate"] = False representation.setdefault("tags", []).append("delete") continue - - if missing: + else: error_report_missing.append((image_instance, missing)) if error_report_missing: From 5059cf74b5bddfa85b4b9157fd2ffe7f346cc203 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 4 Apr 2023 00:13:50 +0200 Subject: [PATCH 195/428] Support multiple texture sets + stacks --- .../publish/collect_textureset_images.py | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 50a96b94ae..d11abd1019 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -41,10 +41,12 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for template, outputs in template_maps.items(): self.log.info(f"Processing {template}") self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc) + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, - asset_doc): + asset_doc, texture_set_name, stack_name): """Create a new instance per image or UDIM sequence. The new instances will be of family `image`. @@ -56,14 +58,27 @@ class CollectTextureSet(pyblish.api.InstancePlugin): fnames = [os.path.basename(output["filepath"]) for output in outputs] ext = os.path.splitext(first_filepath)[1] assert ext.lstrip("."), f"No extension: {ext}" - map_identifier = strip_template(template) + + always_include_texture_set_name = False # todo: make this configurable + all_texture_sets = substance_painter.textureset.all_texture_sets() + texture_set = substance_painter.textureset.TextureSet.from_name( + texture_set_name + ) # Define the suffix we want to give this particular texture # set and set up a remapped subset naming for it. - # TODO (Critical) Support needs to be added to have multiple materials - # with each their own maps. So we might need to include the - # material or alike in the variant suffix too? - suffix = f".{map_identifier}" + suffix = "" + if always_include_texture_set_name or len(all_texture_sets) > 1: + # More than one texture set, include texture set name + suffix += f".{texture_set_name}" + if texture_set.is_layered_material() and stack_name: + # More than one stack, include stack name + suffix += f".{stack_name}" + + # Always include the map identifier + map_identifier = strip_template(template) + suffix += f".{map_identifier}" + image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now # this is only done so the subset name starts with 'texture' @@ -110,6 +125,10 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Group the textures together in the loader image_instance.data["subsetGroup"] = instance.data["subset"] + # Store the texture set name and stack name on the instance + image_instance.data["textureSetName"] = texture_set_name + image_instance.data["textureStackName"] = stack_name + # Store color space with the instance # Note: The extractor will assign it to the representation colorspace = outputs[0].get("colorSpace") From 1cbcd66da80632ff654d7d54a4f0c2cf0025c64c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Apr 2023 16:54:36 +0200 Subject: [PATCH 196/428] removing older instance attributes --- openpype/hosts/nuke/api/plugin.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 3806f291c2..3566cb64c1 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -208,6 +208,12 @@ class NukeCreator(NewCreator): def collect_instances(self): cached_instances = _collect_and_cache_nodes(self) + attr_def_keys = { + attr_def.key + for attr_def in self.get_instance_attr_defs() + } + attr_def_keys.discard(None) + for (node, data) in cached_instances[self.identifier]: created_instance = CreatedInstance.from_existing( data, self @@ -215,6 +221,12 @@ class NukeCreator(NewCreator): created_instance.transient_data["node"] = node self._add_instance_to_context(created_instance) + for key in ( + set(created_instance["creator_attributes"].keys()) + - attr_def_keys + ): + created_instance["creator_attributes"].pop(key) + def update_instances(self, update_list): for created_inst, _changes in update_list: instance_node = created_inst.transient_data["node"] From 0ff0b6b645e1f7293347a24a638bb2afb80556e9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 07:39:53 +0100 Subject: [PATCH 197/428] Move launch logic to host module. --- openpype/hooks/pre_add_last_workfile_arg.py | 13 ---------- .../hosts/maya/hooks/pre_auto_load_plugins.py | 22 +++++++++++++--- .../pre_open_workfile_post_initialization.py | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 openpype/hosts/maya/hooks/pre_open_workfile_post_initialization.py diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index df4aa5cc5d..2a35db869a 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -42,18 +42,5 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): self.log.info("Current context does not have any workfile yet.") return - # Determine whether to open workfile post initialization. - if self.host_name == "maya": - keys = [ - "open_workfile_post_initialization", "explicit_plugins_loading" - ] - maya_settings = self.data["project_settings"]["maya"] - values = [maya_settings[k] for k in keys] - if any(values): - self.log.debug("Opening workfile post initialization.") - key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" - self.data["env"][key] = "1" - return - # Add path to workfile to arguments self.launch_context.launch_args.append(last_workfile) diff --git a/openpype/hosts/maya/hooks/pre_auto_load_plugins.py b/openpype/hosts/maya/hooks/pre_auto_load_plugins.py index 3c3ddbe4dc..689d7adb4f 100644 --- a/openpype/hosts/maya/hooks/pre_auto_load_plugins.py +++ b/openpype/hosts/maya/hooks/pre_auto_load_plugins.py @@ -1,15 +1,29 @@ from openpype.lib import PreLaunchHook -class PreAutoLoadPlugins(PreLaunchHook): +class MayaPreAutoLoadPlugins(PreLaunchHook): """Define -noAutoloadPlugins command flag.""" - # Execute before workfile argument. - order = 0 + # Before AddLastWorkfileToLaunchArgs + order = 9 app_groups = ["maya"] def execute(self): + + # Ignore if there's no last workfile to start. + if not self.data.get("start_last_workfile"): + return + maya_settings = self.data["project_settings"]["maya"] - if maya_settings["explicit_plugins_loading"]["enabled"]: + enabled = maya_settings["explicit_plugins_loading"]["enabled"] + if enabled: + # Force disable the `AddLastWorkfileToLaunchArgs`. + self.data.pop("start_last_workfile") + + # Force post initialization so our dedicated plug-in load can run + # prior to Maya opening a scene file. + key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" + self.launch_context.env[key] = "1" + self.log.debug("Explicit plugins loading.") self.launch_context.launch_args.append("-noAutoloadPlugins") diff --git a/openpype/hosts/maya/hooks/pre_open_workfile_post_initialization.py b/openpype/hosts/maya/hooks/pre_open_workfile_post_initialization.py new file mode 100644 index 0000000000..7582ce0591 --- /dev/null +++ b/openpype/hosts/maya/hooks/pre_open_workfile_post_initialization.py @@ -0,0 +1,25 @@ +from openpype.lib import PreLaunchHook + + +class MayaPreOpenWorkfilePostInitialization(PreLaunchHook): + """Define whether open last workfile should run post initialize.""" + + # Before AddLastWorkfileToLaunchArgs. + order = 9 + app_groups = ["maya"] + + def execute(self): + + # Ignore if there's no last workfile to start. + if not self.data.get("start_last_workfile"): + return + + maya_settings = self.data["project_settings"]["maya"] + enabled = maya_settings["open_workfile_post_initialization"] + if enabled: + # Force disable the `AddLastWorkfileToLaunchArgs`. + self.data.pop("start_last_workfile") + + self.log.debug("Opening workfile post initialization.") + key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" + self.launch_context.env[key] = "1" From 7444e33a941498bf040bacd6b3710a34f9f59e92 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 07:53:50 +0100 Subject: [PATCH 198/428] Move review camera validation to validator. --- .../maya/plugins/publish/collect_review.py | 11 +++----- .../maya/plugins/publish/validate_review.py | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_review.py diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 00565c5819..ab730db66e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -31,14 +31,11 @@ class CollectReview(pyblish.api.InstancePlugin): # get cameras members = instance.data['setMembers'] - cameras = cmds.ls(members, long=True, - dag=True, cameras=True) self.log.debug('members: {}'.format(members)) - - # validate required settings - assert len(cameras) == 1, "Not a single camera found in extraction" - camera = cameras[0] - self.log.debug('camera: {}'.format(camera)) + cameras = cmds.ls(members, long=True, dag=True, cameras=True) + camera = None + if cameras: + camera = cameras[0] objectset = instance.context.data['objectsets'] diff --git a/openpype/hosts/maya/plugins/publish/validate_review.py b/openpype/hosts/maya/plugins/publish/validate_review.py new file mode 100644 index 0000000000..fd11b2147b --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_review.py @@ -0,0 +1,25 @@ +from maya import cmds + +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishValidationError +) + + +class ValidateReview(pyblish.api.InstancePlugin): + """Validate review.""" + + order = ValidateContentsOrder + label = "Validate Review" + families = ["review"] + + def process(self, instance): + cameras = cmds.ls( + instance.data["setMembers"], long=True, dag=True, cameras=True + ) + + if len(cameras) != 1: + raise PublishValidationError( + "Not a single camera found in instance." + ) From c4b887597a3ad9318367553f0151675063ab9560 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 07:54:31 +0100 Subject: [PATCH 199/428] Support review profiles in extraction --- .../maya/plugins/publish/extract_playblast.py | 51 +++++++++++--- .../maya/plugins/publish/extract_thumbnail.py | 67 ++++++++++++++----- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 72b1489522..0556fd9eea 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -7,6 +7,7 @@ import capture from openpype.pipeline import publish from openpype.hosts.maya.api import lib +from openpype.lib.profiles_filtering import filter_profiles from maya import cmds import pymel.core as pm @@ -34,7 +35,7 @@ class ExtractPlayblast(publish.Extractor): hosts = ["maya"] families = ["review"] optional = True - capture_preset = {} + profiles = None def _capture(self, preset): self.log.info( @@ -47,6 +48,10 @@ class ExtractPlayblast(publish.Extractor): self.log.debug("playblast path {}".format(path)) def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for Extract Playblast") + return + self.log.info("Extracting capture..") # get scene fps @@ -66,12 +71,35 @@ class ExtractPlayblast(publish.Extractor): # get cameras camera = instance.data["review_camera"] - preset = lib.load_capture_preset(data=self.capture_preset) - # Grab capture presets from the project settings - capture_presets = self.capture_preset + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + capture_preset = filter_profiles( + self.profiles, filtering_criteria, logger=self.log + )["capture_preset"] + preset = lib.load_capture_preset( + data=capture_preset + ) + + # "isolate_view" will already have been applied at creation, so we'll + # ignore it here. + preset.pop("isolate_view") + # Set resolution variables from capture presets - width_preset = capture_presets["Resolution"]["width"] - height_preset = capture_presets["Resolution"]["height"] + width_preset = capture_preset["Resolution"]["width"] + height_preset = capture_preset["Resolution"]["height"] + # Set resolution variables from asset values asset_data = instance.data["assetEntity"]["data"] asset_width = asset_data.get("resolutionWidth") @@ -122,8 +150,9 @@ class ExtractPlayblast(publish.Extractor): preset["viewport2_options"]["transparencyAlgorithm"] = transparency # Isolate view is requested by having objects in the set besides a - # camera. - if preset.pop("isolate_view", False) and instance.data.get("isolate"): + # camera. If there is only 1 member it'll be the camera because we + # validate to have 1 camera only. + if instance.data["isolate"] and len(instance.data["setMembers"]) > 1: preset["isolate"] = instance.data["setMembers"] # Show/Hide image planes on request. @@ -158,7 +187,7 @@ class ExtractPlayblast(publish.Extractor): ) override_viewport_options = ( - capture_presets["Viewport Options"]["override_viewport_options"] + capture_preset["Viewport Options"]["override_viewport_options"] ) # Force viewer to False in call to capture because we have our own @@ -234,8 +263,8 @@ class ExtractPlayblast(publish.Extractor): collected_files = collected_files[0] representation = { - "name": self.capture_preset["Codec"]["compression"], - "ext": self.capture_preset["Codec"]["compression"], + "name": capture_preset["Codec"]["compression"], + "ext": capture_preset["Codec"]["compression"], "files": collected_files, "stagingDir": stagingdir, "frameStart": start, diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index f2d084b828..4672940254 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -1,11 +1,14 @@ import os import glob import tempfile +import json import capture -from openpype.pipeline import publish +from openpype.pipeline import publish, legacy_io from openpype.hosts.maya.api import lib +from openpype.lib.profiles_filtering import filter_profiles +from openpype.settings import get_project_settings from maya import cmds import pymel.core as pm @@ -24,26 +27,48 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): + project_name = legacy_io.Session["AVALON_PROJECT"] + profiles = get_project_settings( + project_name + )["maya"]["publish"]["ExtractPlayblast"]["profiles"] + + if not profiles: + self.log.warning("No profiles present for Extract Playblast") + return + self.log.info("Extracting capture..") camera = instance.data["review_camera"] - maya_setting = instance.context.data["project_settings"]["maya"] - plugin_setting = maya_setting["publish"]["ExtractPlayblast"] - capture_preset = plugin_setting["capture_preset"] + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + capture_preset = filter_profiles( + profiles, filtering_criteria, logger=self.log + )["capture_preset"] + preset = lib.load_capture_preset( + data=capture_preset + ) + + # "isolate_view" will already have been applied at creation, so we'll + # ignore it here. + preset.pop("isolate_view") + override_viewport_options = ( capture_preset["Viewport Options"]["override_viewport_options"] ) - try: - preset = lib.load_capture_preset(data=capture_preset) - except KeyError as ke: - self.log.error("Error loading capture presets: {}".format(str(ke))) - preset = {} - self.log.info("Using viewport preset: {}".format(preset)) - - # preset["off_screen"] = False - preset["camera"] = camera preset["start_frame"] = instance.data["frameStart"] preset["end_frame"] = instance.data["frameStart"] @@ -59,10 +84,9 @@ class ExtractThumbnail(publish.Extractor): "overscan": 1.0, "depthOfField": cmds.getAttr("{0}.depthOfField".format(camera)), } - capture_presets = capture_preset # Set resolution variables from capture presets - width_preset = capture_presets["Resolution"]["width"] - height_preset = capture_presets["Resolution"]["height"] + width_preset = capture_preset["Resolution"]["width"] + height_preset = capture_preset["Resolution"]["height"] # Set resolution variables from asset values asset_data = instance.data["assetEntity"]["data"] asset_width = asset_data.get("resolutionWidth") @@ -111,8 +135,9 @@ class ExtractThumbnail(publish.Extractor): preset["viewport2_options"]["transparencyAlgorithm"] = transparency # Isolate view is requested by having objects in the set besides a - # camera. - if preset.pop("isolate_view", False) and instance.data.get("isolate"): + # camera. If there is only 1 member it'll be the camera because we + # validate to have 1 camera only. + if instance.data["isolate"] and len(instance.data["setMembers"]) > 1: preset["isolate"] = instance.data["setMembers"] # Show or Hide Image Plane @@ -140,6 +165,12 @@ class ExtractThumbnail(publish.Extractor): preset.update(panel_preset) cmds.setFocus(panel) + self.log.info( + "Using preset: {}".format( + json.dumps(preset, indent=4, sort_keys=True) + ) + ) + path = capture.capture(**preset) playblast = self._fix_playblast_output_path(path) From b960b653300bff616918f14cb1b6f3a65d519056 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 08:13:35 +0100 Subject: [PATCH 200/428] Order display options better. --- .../schemas/schema_maya_capture.json | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index a8961b48dd..3fc92a1b05 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -77,13 +77,24 @@ "type": "label", "label": "Display Options" }, - + { + "type": "boolean", + "key": "override_display", + "label": "Override display options", + "default": true + }, { "type": "color", "key": "background", "label": "Background Color: ", "default": [125, 125, 125, 255] }, + { + "type": "boolean", + "key": "displayGradient", + "label": "Display background gradient", + "default": true + }, { "type": "color", "key": "backgroundBottom", @@ -95,18 +106,6 @@ "key": "backgroundTop", "label": "Background Top: ", "default": [125, 125, 125, 255] - }, - { - "type": "boolean", - "key": "override_display", - "label": "Override display options", - "default": true - }, - { - "type": "boolean", - "key": "displayGradient", - "label": "Display background gradient", - "default": true } ] }, From bc004453edd0cc42648ce228e1249c9eb05a2700 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 08:33:18 +0100 Subject: [PATCH 201/428] Update openpype/hosts/maya/startup/userSetup.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/startup/userSetup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 4932bf14c0..b28d89e7bd 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -38,8 +38,9 @@ if settings["maya"]["explicit_plugins_loading"]["enabled"]: key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" if bool(int(os.environ.get(key, "0"))): def _log_and_open(): - print("Opening \"{}\"".format(os.environ["AVALON_LAST_WORKFILE"])) - cmds.file(os.environ["AVALON_LAST_WORKFILE"], open=True, force=True) + path = os.environ["AVALON_LAST_WORKFILE"] + print("Opening \"{}\"".format(path)) + cmds.file(path, open=True, force=True) cmds.evalDeferred( _log_and_open, lowestPriority=True From b5e80e565b5de71625531beb7818d34d9b7da1df Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 08:34:42 +0100 Subject: [PATCH 202/428] Update openpype/hosts/maya/startup/userSetup.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/startup/userSetup.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index b28d89e7bd..4a00c3dce7 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -20,14 +20,13 @@ if settings["maya"]["explicit_plugins_loading"]["enabled"]: project_settings = get_project_settings(os.environ["AVALON_PROJECT"]) maya_settings = project_settings["maya"] explicit_plugins_loading = maya_settings["explicit_plugins_loading"] - if explicit_plugins_loading["enabled"]: - for plugin in explicit_plugins_loading["plugins_to_load"]: - if plugin["enabled"]: - print("Loading " + plugin["name"]) - try: - cmds.loadPlugin(plugin["name"], quiet=True) - except RuntimeError as e: - print(e) + for plugin in explicit_plugins_loading["plugins_to_load"]: + if plugin["enabled"]: + print("Loading plug-in: " + plugin["name"]) + try: + cmds.loadPlugin(plugin["name"], quiet=True) + except RuntimeError as e: + print(e) cmds.evalDeferred( _explicit_load_plugins, From 0c626f54c5aa69730692f50a6de3123a555d3419 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 08:52:53 +0100 Subject: [PATCH 203/428] Refactor settings variables. --- openpype/hosts/maya/startup/userSetup.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 4a00c3dce7..b58ebb0f7f 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -12,14 +12,12 @@ install_host(host) print("Starting OpenPype usersetup...") -settings = get_project_settings(os.environ['AVALON_PROJECT']) +project_settings = get_project_settings(os.environ['AVALON_PROJECT']) # Loading plugins explicitly. -if settings["maya"]["explicit_plugins_loading"]["enabled"]: +explicit_plugins_loading = project_settings["maya"]["explicit_plugins_loading"] +if explicit_plugins_loading["enabled"]: def _explicit_load_plugins(): - project_settings = get_project_settings(os.environ["AVALON_PROJECT"]) - maya_settings = project_settings["maya"] - explicit_plugins_loading = maya_settings["explicit_plugins_loading"] for plugin in explicit_plugins_loading["plugins_to_load"]: if plugin["enabled"]: print("Loading plug-in: " + plugin["name"]) @@ -46,7 +44,7 @@ if bool(int(os.environ.get(key, "0"))): ) # Build a shelf. -shelf_preset = settings['maya'].get('project_shelf') +shelf_preset = project_settings['maya'].get('project_shelf') if shelf_preset: project = os.environ["AVALON_PROJECT"] From 4b3f96af5e41ccc29461fd5e0fec9306a062edd1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 08:53:08 +0100 Subject: [PATCH 204/428] Comment deferred evaluation --- openpype/hosts/maya/startup/userSetup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index b58ebb0f7f..ae6a999d98 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -26,6 +26,8 @@ if explicit_plugins_loading["enabled"]: except RuntimeError as e: print(e) + # We need to load plugins deferred as loading them directly does not work + # correctly due to Maya's initialization. cmds.evalDeferred( _explicit_load_plugins, lowestPriority=True From 2d6d1ba88200fbf0cd9813dbe8b56553a86c6c55 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 10:11:20 +0100 Subject: [PATCH 205/428] Fix OP-5542 # Traceback (most recent call last): # File "C:\Users\florianbehr\AppData\Local\pypeclub\openpype\3.15\openpype-v3.15.4-thescope230404\openpype\hosts\maya\tools\mayalookassigner\app.py", line 272, in on_process_selected # nodes = list(set(item["nodes"]).difference(arnold_standins)) # UnboundLocalError: local variable 'arnold_standins' referenced before assignment --- openpype/hosts/maya/tools/mayalookassigner/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/tools/mayalookassigner/app.py b/openpype/hosts/maya/tools/mayalookassigner/app.py index 2a8775fff6..4619c80913 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/app.py +++ b/openpype/hosts/maya/tools/mayalookassigner/app.py @@ -263,14 +263,14 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): for standin in arnold_standins: if standin in nodes: arnold_standin.assign_look(standin, subset_name) + + nodes = list(set(item["nodes"]).difference(arnold_standins)) else: self.echo( "Could not assign to aiStandIn because mtoa plugin is not " "loaded." ) - nodes = list(set(item["nodes"]).difference(arnold_standins)) - # Assign look if nodes: assign_look_by_version(nodes, version_id=version["_id"]) From 94cd27fbc27d1c11a1b2fd0edb193257fc2717e9 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 10:25:35 +0100 Subject: [PATCH 206/428] Update openpype/hosts/maya/tools/mayalookassigner/app.py --- openpype/hosts/maya/tools/mayalookassigner/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/tools/mayalookassigner/app.py b/openpype/hosts/maya/tools/mayalookassigner/app.py index 4619c80913..a8d0f243e9 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/app.py +++ b/openpype/hosts/maya/tools/mayalookassigner/app.py @@ -263,7 +263,6 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): for standin in arnold_standins: if standin in nodes: arnold_standin.assign_look(standin, subset_name) - nodes = list(set(item["nodes"]).difference(arnold_standins)) else: self.echo( From 1944643d780a4e711c473963102f0a67af06ecc0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Apr 2023 11:39:28 +0200 Subject: [PATCH 207/428] make label key optional on instance.data --- openpype/plugins/publish/collect_otio_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_review.py b/openpype/plugins/publish/collect_otio_review.py index 4d8147e70d..f0157282a1 100644 --- a/openpype/plugins/publish/collect_otio_review.py +++ b/openpype/plugins/publish/collect_otio_review.py @@ -87,7 +87,9 @@ class CollectOtioReview(pyblish.api.InstancePlugin): otio_review_clips.append(otio_gap) if otio_review_clips: - instance.data["label"] += " (review)" + # add review track to instance and change label to reflect it + label = instance.data.get("label", instance.data["subset"]) + instance.data["label"] = label + " (review)" instance.data["families"] += ["review", "ftrack"] instance.data["otioReviewClips"] = otio_review_clips self.log.info( From 68caaf65c8a2e486d6a86b85012ad1493f6f3608 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Apr 2023 12:14:54 +0200 Subject: [PATCH 208/428] fixing skipping of project actions --- .github/workflows/project_actions.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/project_actions.yml b/.github/workflows/project_actions.yml index b21946f0ee..3589b4acc2 100644 --- a/.github/workflows/project_actions.yml +++ b/.github/workflows/project_actions.yml @@ -25,7 +25,11 @@ jobs: if: | (github.event_name == 'issue_comment' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.comment.user.id != 82967070) || (github.event_name == 'pull_request_review_comment' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.comment.user.type != 'Bot') || - (github.event_name == 'pull_request_review' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.review.state != 'changes_requested' && github.event.review.user.type != 'Bot') + (github.event_name == 'pull_request_review' && + github.event.pull_request.head.repo.owner.login == 'ynput' && + github.event.review.state != 'changes_requested' && + github.event.review.state != 'approved' && + github.event.review.user.type != 'Bot') steps: - name: Move PR to 'Review In Progress' uses: leonsteinhaeuser/project-beta-automations@v2.1.0 @@ -70,10 +74,7 @@ jobs: size-label: name: pr_size_label runs-on: ubuntu-latest - if: | - (github.event_name == 'pull_request' && github.event.action == 'assigned') || - (github.event_name == 'pull_request' && github.event.action == 'opened') - + if: github.event.action == 'assigned' || github.event.action == 'opened' steps: - name: Add size label uses: "pascalgn/size-label-action@v0.4.3" @@ -94,9 +95,7 @@ jobs: label_prs_branch: name: pr_branch_label runs-on: ubuntu-latest - if: | - (github.event_name == 'pull_request' && github.event.action == 'assigned') || - (github.event_name == 'pull_request' && github.event.action == 'opened') + if: github.event.action == 'assigned' || github.event.action == 'opened' steps: - name: Label PRs - Branch name detection uses: ffittschen/pr-branch-labeler@v1 @@ -106,9 +105,7 @@ jobs: label_prs_globe: name: pr_globe_label runs-on: ubuntu-latest - if: | - (github.event_name == 'pull_request' && github.event.action == 'assigned') || - (github.event_name == 'pull_request' && github.event.action == 'opened') + if: github.event.action == 'assigned' || github.event.action == 'opened' steps: - name: Label PRs - Globe detection uses: actions/labeler@v4.0.3 From 912f757390a93df678bb964c6beb3665bc5fb08d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:31:27 +0100 Subject: [PATCH 209/428] Update openpype/hosts/maya/plugins/create/create_review.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 5a1afe9790..eeccc5b21e 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -38,7 +38,7 @@ class CreateReview(plugin.Creator): super(CreateReview, self).__init__(*args, **kwargs) data = OrderedDict(**self.data) - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = get_current_project_name() profiles = get_project_settings( project_name )["maya"]["publish"]["ExtractPlayblast"]["profiles"] From 462d3247e82725c9bfaf083b63c240441de5ef40 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:42:40 +0100 Subject: [PATCH 210/428] Update openpype/hosts/maya/plugins/create/create_review.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index eeccc5b21e..1eb8e421a1 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -48,7 +48,7 @@ class CreateReview(plugin.Creator): self.log.warning("No profiles present for extract playblast.") else: asset_doc = get_asset_by_name(project_name, data["asset"]) - task_name = legacy_io.Session["AVALON_TASK"] + task_name = get_current_task_name() task_type = asset_doc["data"]["tasks"][task_name]["type"] filtering_criteria = { From 54a135a1284e72330dcf57e7c70d45fef3de5ce8 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:43:01 +0100 Subject: [PATCH 211/428] Update openpype/hosts/maya/plugins/publish/extract_thumbnail.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 2daea7f3eb..ca08016fab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -26,7 +26,7 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = instance.context.data["projectName"] profiles = get_project_settings( project_name )["maya"]["publish"]["ExtractPlayblast"]["profiles"] From c969120d15a9dab03548f538c2ba662ced596166 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:44:11 +0100 Subject: [PATCH 212/428] Update openpype/hosts/maya/plugins/publish/extract_thumbnail.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/plugins/publish/extract_thumbnail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index ca08016fab..038a3c0c7f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -27,7 +27,8 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): project_name = instance.context.data["projectName"] - profiles = get_project_settings( + project_settings = instance.context.data["project_settings"] + profiles = project_settings["maya"]["publish"]["ExtractPlayblast"]["profiles"] project_name )["maya"]["publish"]["ExtractPlayblast"]["profiles"] From a3d358a661b9c32dea6cce3c4b6b0e4dcd010bbb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:44:35 +0100 Subject: [PATCH 213/428] Update openpype/settings/entities/color_entity.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/settings/entities/color_entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/color_entity.py b/openpype/settings/entities/color_entity.py index a542f2fa38..e9a2136754 100644 --- a/openpype/settings/entities/color_entity.py +++ b/openpype/settings/entities/color_entity.py @@ -11,10 +11,10 @@ class ColorEntity(InputEntity): def _item_initialization(self): self.valid_value_types = (list, ) + self.use_alpha = self.schema_data.get("use_alpha", True) self.value_on_not_set = self.convert_to_valid_type( self.schema_data.get("default", [0, 0, 0, 255]) ) - self.use_alpha = self.schema_data.get("use_alpha", True) def set_override_state(self, *args, **kwargs): super(ColorEntity, self).set_override_state(*args, **kwargs) From c290422fcde6077f744e7d14593076412c79f991 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:44:48 +0100 Subject: [PATCH 214/428] Update openpype/settings/entities/color_entity.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/settings/entities/color_entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/color_entity.py b/openpype/settings/entities/color_entity.py index e9a2136754..f838a6b0ad 100644 --- a/openpype/settings/entities/color_entity.py +++ b/openpype/settings/entities/color_entity.py @@ -66,6 +66,6 @@ class ColorEntity(InputEntity): new_value.append(item) # Make sure - if hasattr(self, "use_alpha") and not self.use_alpha: + if not self.use_alpha: new_value[3] = 255 return new_value From d8d2a317ac24c3618d0a99ee5235b3b71e1718d7 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 5 Apr 2023 11:45:20 +0100 Subject: [PATCH 215/428] Update openpype/settings/entities/color_entity.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> From 890d88908c20e97b384e02bb88d605c22804934f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 11:46:45 +0100 Subject: [PATCH 216/428] BigRoy feedback --- .../hosts/maya/plugins/publish/collect_review.py | 9 ++++----- .../hosts/maya/plugins/publish/validate_review.py | 7 ++----- .../settings/defaults/project_settings/maya.json | 6 ++++-- .../projects_schema/schemas/schema_maya_capture.json | 12 ++++++------ 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 858ee24026..0b3799ac13 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -31,9 +31,6 @@ class CollectReview(pyblish.api.InstancePlugin): members = instance.data['setMembers'] self.log.debug('members: {}'.format(members)) cameras = cmds.ls(members, long=True, dag=True, cameras=True) - camera = None - if cameras: - camera = cameras[0] context = instance.context objectset = context.data['objectsets'] @@ -64,7 +61,8 @@ class CollectReview(pyblish.api.InstancePlugin): else: data['families'] = ['review'] - data['review_camera'] = camera + data["cameras"] = cameras + data['review_camera'] = cameras[0] if cameras else None data['frameStartFtrack'] = instance.data["frameStartHandle"] data['frameEndFtrack'] = instance.data["frameEndHandle"] data['frameStartHandle'] = instance.data["frameStartHandle"] @@ -98,7 +96,8 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug("Existing subsets found, keep legacy name.") instance.data['subset'] = legacy_subset_name - instance.data['review_camera'] = camera + instance.data["cameras"] = cameras + instance.data['review_camera'] = cameras[0] if cameras else None instance.data['frameStartFtrack'] = \ instance.data["frameStartHandle"] instance.data['frameEndFtrack'] = \ diff --git a/openpype/hosts/maya/plugins/publish/validate_review.py b/openpype/hosts/maya/plugins/publish/validate_review.py index 7e9b86c64f..68e8c4a74a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_review.py +++ b/openpype/hosts/maya/plugins/publish/validate_review.py @@ -15,9 +15,7 @@ class ValidateReview(pyblish.api.InstancePlugin): families = ["review"] def process(self, instance): - cameras = cmds.ls( - instance.data["setMembers"], long=True, dag=True, cameras=True - ) + cameras = instance.data["cameras"] # validate required settings if len(cameras) == 0: @@ -31,5 +29,4 @@ class ValidateReview(pyblish.api.InstancePlugin): "Cameras found: {}".format(instance, ", ".join(cameras)) ) - camera = cameras[0] - self.log.debug('camera: {}'.format(camera)) + self.log.debug('camera: {}'.format(instance.data["review_camera"])) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index a54c869939..24d55de1fd 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -874,7 +874,6 @@ "dynamics": false, "fluids": false, "follicles": false, - "gpuCacheDisplayFilter": false, "greasePencils": false, "grid": false, "hairSystems": true, @@ -901,7 +900,10 @@ "polymeshes": true, "strokes": false, "subdivSurfaces": false, - "textures": false + "textures": false, + "pluginObjects": { + "gpuCacheDisplayFilter": false + } }, "Camera Options": { "displayGateMask": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 3fc92a1b05..1909a20cf5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -505,12 +505,6 @@ "label": "Follicles", "default": false }, - { - "type": "boolean", - "key": "gpuCacheDisplayFilter", - "label": "GPU Cache", - "default": false - }, { "type": "boolean", "key": "greasePencils", @@ -672,6 +666,12 @@ "key": "textures", "label": "Texture Placements", "default": false + }, + { + "type": "dict-modifiable", + "key": "pluginObjects", + "label": "Plugin Objects", + "object_type": "boolean" } ] }, From a5918bc3f8116309c2a5bd2d790686eacb2e63bb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 11:50:05 +0100 Subject: [PATCH 217/428] Move preset debug log behind OPENPYPE_DEBUG --- openpype/hosts/maya/plugins/create/create_review.py | 10 ++++++---- .../hosts/maya/plugins/publish/extract_playblast.py | 9 +++++---- .../hosts/maya/plugins/publish/extract_thumbnail.py | 9 +++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 5a1afe9790..786c795a1a 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -1,3 +1,4 @@ +import os from collections import OrderedDict import json @@ -80,11 +81,12 @@ class CreateReview(plugin.Creator): data["panZoom"] = self.panZoom if preset: - self.log.info( - "Using preset: {}".format( - json.dumps(preset, indent=4, sort_keys=True) + if os.environ.get("OPENPYPE_DEBUG") == "1": + self.log.debug( + "Using preset: {}".format( + json.dumps(preset, indent=4, sort_keys=True) + ) ) - ) data["review_width"] = preset["Resolution"]["width"] data["review_height"] = preset["Resolution"]["height"] data["isolate"] = preset["Generic"]["isolate_view"] diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 7787c1df7f..81007520a8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -37,11 +37,12 @@ class ExtractPlayblast(publish.Extractor): profiles = None def _capture(self, preset): - self.log.info( - "Using preset:\n{}".format( - json.dumps(preset, sort_keys=True, indent=4) + if os.environ.get("OPENPYPE_DEBUG") == "1": + self.log.debug( + "Using preset: {}".format( + json.dumps(preset, indent=4, sort_keys=True) + ) ) - ) path = capture.capture(log=self.log, **preset) self.log.debug("playblast path {}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 2daea7f3eb..ee64c11ca4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -164,11 +164,12 @@ class ExtractThumbnail(publish.Extractor): preset.update(panel_preset) cmds.setFocus(panel) - self.log.info( - "Using preset: {}".format( - json.dumps(preset, indent=4, sort_keys=True) + if os.environ.get("OPENPYPE_DEBUG") == "1": + self.log.debug( + "Using preset: {}".format( + json.dumps(preset, indent=4, sort_keys=True) + ) ) - ) path = capture.capture(**preset) playblast = self._fix_playblast_output_path(path) From 70c9c534f017fe3281547f9248a9bb41c1bcb765 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 11:57:06 +0100 Subject: [PATCH 218/428] Hound --- openpype/hosts/maya/plugins/create/create_review.py | 2 +- .../hosts/maya/plugins/publish/extract_thumbnail.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 75a1a5bf08..594faa7978 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api import ( plugin ) from openpype.settings import get_project_settings -from openpype.pipeline import legacy_io +from openpype.pipeline import get_current_project_name, get_current_task_name from openpype.lib.profiles_filtering import filter_profiles from openpype.client import get_asset_by_name diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 67d085e2f5..cf0f80fa15 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -5,10 +5,9 @@ import json import capture -from openpype.pipeline import publish, legacy_io +from openpype.pipeline import publish from openpype.hosts.maya.api import lib from openpype.lib.profiles_filtering import filter_profiles -from openpype.settings import get_project_settings from maya import cmds @@ -26,11 +25,8 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): - project_name = instance.context.data["projectName"] - project_settings = instance.context.data["project_settings"] - profiles = project_settings["maya"]["publish"]["ExtractPlayblast"]["profiles"] - project_name - )["maya"]["publish"]["ExtractPlayblast"]["profiles"] + maya_settings = instance.context.data["project_settings"]["maya"] + profiles = maya_settings["publish"]["ExtractPlayblast"]["profiles"] if not profiles: self.log.warning("No profiles present for Extract Playblast") From 56fc69a9c98639be06a18c2a6a6e74ee05386744 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 11:58:40 +0100 Subject: [PATCH 219/428] Hound --- openpype/hosts/maya/plugins/publish/validate_review.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_review.py b/openpype/hosts/maya/plugins/publish/validate_review.py index 68e8c4a74a..346fb54ac4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_review.py +++ b/openpype/hosts/maya/plugins/publish/validate_review.py @@ -1,5 +1,3 @@ -from maya import cmds - import pyblish.api from openpype.pipeline.publish import ( From 105e03344c28ba44f61c6ade2c1c82450cc93ad6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Apr 2023 14:34:26 +0200 Subject: [PATCH 220/428] nuke: capture exception so popup is not rised --- openpype/hosts/nuke/api/lib.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 157a02b9aa..fe3a2d2bd1 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -23,6 +23,9 @@ from openpype.client import ( from openpype.host import HostDirmap from openpype.tools.utils import host_tools +from openpype.pipeline.workfile.workfile_template_builder import ( + TemplateProfileNotFound +) from openpype.lib import ( env_value_to_bool, Logger, @@ -2684,7 +2687,10 @@ def start_workfile_template_builder(): # to avoid looping of the callback, remove it! log.info("Starting workfile template builder...") - build_workfile_template(workfile_creation_enabled=True) + try: + build_workfile_template(workfile_creation_enabled=True) + except TemplateProfileNotFound: + log.warning("Template profile not found. Skipping...") # remove callback since it would be duplicating the workfile nuke.removeOnCreate(start_workfile_template_builder, nodeClass="Root") From 17bfa7f628a1d19e5754047b3ba9598cc4576f8c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Apr 2023 14:34:57 +0200 Subject: [PATCH 221/428] add positional knobs only if keep_placeholder --- .../nuke/api/workfile_template_builder.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index cf85a5ea05..72d4ffb476 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -219,14 +219,17 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): # fix the problem of z_order for backdrops self._fix_z_order(placeholder) - self._imprint_siblings(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) if placeholder.data["nb_children"] == 0: # save initial nodes positions and dimensions, update them # and set inputs and outputs of loaded nodes + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) self._set_loaded_connections(placeholder) elif placeholder.data["siblings"]: @@ -629,14 +632,18 @@ class NukePlaceholderCreatePlugin( # fix the problem of z_order for backdrops self._fix_z_order(placeholder) - self._imprint_siblings(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) if placeholder.data["nb_children"] == 0: # save initial nodes positions and dimensions, update them # and set inputs and outputs of created nodes - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_created) + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_created) + self._set_created_connections(placeholder) elif placeholder.data["siblings"]: From 199f70e1caabfc2f59ae563bd4f12c4bb04f8d3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:35:07 +0200 Subject: [PATCH 222/428] add missing variable 'handles' to loader (#4781) --- openpype/tools/loader/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 39e0bd98c3..14671e341f 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -361,10 +361,11 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): version_data.get("endFrame", None) ) + handles_label = None handle_start = version_data.get("handleStart", None) handle_end = version_data.get("handleEnd", None) if handle_start is not None and handle_end is not None: - handles = "{}-{}".format(str(handle_start), str(handle_end)) + handles_label = "{}-{}".format(str(handle_start), str(handle_end)) if frame_start is not None and frame_end is not None: # Remove superfluous zeros from numbers (3.0 -> 3) to improve @@ -401,7 +402,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "frameStart": frame_start, "frameEnd": frame_end, "duration": duration, - "handles": handles, + "handles": handles_label, "frames": frames, "step": version_data.get("step", None), }) From 09f5e3ecc1eb067b60524c66618cbce0e6514e86 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 31 Mar 2023 14:43:10 +0200 Subject: [PATCH 223/428] remove placeholder parent to root at cleanup --- .../maya/api/workfile_template_builder.py | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 4bee0664ef..d65e4c74d2 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -234,26 +234,10 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): return self.get_load_plugin_options(options) def cleanup_placeholder(self, placeholder, failed): - """Hide placeholder, parent them to root - add them to placeholder set and register placeholder's parent - to keep placeholder info available for future use + """Hide placeholder, add them to placeholder set """ - node = placeholder._scene_identifier - node_parent = placeholder.data["parent"] - if node_parent: - cmds.setAttr(node + ".parent", node_parent, type="string") - if cmds.getAttr(node + ".index") < 0: - cmds.setAttr(node + ".index", placeholder.data["index"]) - - holding_sets = cmds.listSets(object=node) - if holding_sets: - for set in holding_sets: - cmds.sets(node, remove=set) - - if cmds.listRelatives(node, p=True): - node = cmds.parent(node, world=True)[0] cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) @@ -286,8 +270,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): elif not cmds.sets(root, q=True): return - if placeholder.data["parent"]: - cmds.parent(nodes_to_parent, placeholder.data["parent"]) # Move loaded nodes to correct index in outliner hierarchy placeholder_form = cmds.xform( placeholder.scene_identifier, From 9aa8aa469fc3a81e714207809af786b520043bf6 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 31 Mar 2023 14:44:18 +0200 Subject: [PATCH 224/428] fix missing var standard --- openpype/client/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 7054658c64..376157d210 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1216,7 +1216,7 @@ def get_representations( version_ids=version_ids, context_filters=context_filters, names_by_version_ids=names_by_version_ids, - standard=True, + standard=standard, archived=archived, fields=fields ) From 9be576c2147e712db4d257f95c187e951eca40ec Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 31 Mar 2023 14:45:49 +0200 Subject: [PATCH 225/428] fix linked asset import --- .../workfile/workfile_template_builder.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 0ce59de8ad..a3d7340367 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -158,7 +158,7 @@ class AbstractTemplateBuilder(object): def linked_asset_docs(self): if self._linked_asset_docs is None: self._linked_asset_docs = get_linked_assets( - self.current_asset_doc + self.project_name, self.current_asset_doc ) return self._linked_asset_docs @@ -1151,13 +1151,10 @@ class PlaceholderItem(object): return self._log def __repr__(self): - name = None - if hasattr("name", self): - name = self.name - if hasattr("_scene_identifier ", self): - name = self._scene_identifier - - return "< {} {} >".format(self.__class__.__name__, name) + return "< {} {} >".format( + self.__class__.__name__, + self._scene_identifier + ) @property def order(self): @@ -1419,16 +1416,7 @@ class PlaceholderLoadMixin(object): "family": [placeholder.data["family"]] } - elif builder_type != "linked_asset": - context_filters = { - "asset": [re.compile(placeholder.data["asset"])], - "subset": [re.compile(placeholder.data["subset"])], - "hierarchy": [re.compile(placeholder.data["hierarchy"])], - "representation": [placeholder.data["representation"]], - "family": [placeholder.data["family"]] - } - - else: + elif builder_type == "linked_asset": asset_regex = re.compile(placeholder.data["asset"]) linked_asset_names = [] for asset_doc in linked_asset_docs: @@ -1444,6 +1432,15 @@ class PlaceholderLoadMixin(object): "family": [placeholder.data["family"]], } + else: + context_filters = { + "asset": [re.compile(placeholder.data["asset"])], + "subset": [re.compile(placeholder.data["subset"])], + "hierarchy": [re.compile(placeholder.data["hierarchy"])], + "representation": [placeholder.data["representation"]], + "family": [placeholder.data["family"]] + } + return list(get_representations( project_name, context_filters=context_filters From 70087468c346ad3e91b9980e7c5a5920cd44320b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:59:36 +0200 Subject: [PATCH 226/428] General: Anatomy templates formatting (#4773) * TemplatesDict can create different type of template * anatomy templates can be formatted on their own * return objected templates on get item * '_rootless_path' is public classmethod 'rootless_path_from_result' * 'AnatomyStringTemplate' expect anatomy templates * remove key getters * fix typo 'create_ojected_templates' -> 'create_objected_templates' * Fix type of argument * Fix long line --- openpype/lib/path_templates.py | 26 ++++++++++---- openpype/pipeline/anatomy.py | 64 +++++++++++++++++++++++++--------- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index 0f99efb430..9be1736abf 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -256,17 +256,18 @@ class TemplatesDict(object): elif isinstance(templates, dict): self._raw_templates = copy.deepcopy(templates) self._templates = templates - self._objected_templates = self.create_ojected_templates(templates) + self._objected_templates = self.create_objected_templates( + templates) else: raise TypeError("<{}> argument must be a dict, not {}.".format( self.__class__.__name__, str(type(templates)) )) def __getitem__(self, key): - return self.templates[key] + return self.objected_templates[key] def get(self, key, *args, **kwargs): - return self.templates.get(key, *args, **kwargs) + return self.objected_templates.get(key, *args, **kwargs) @property def raw_templates(self): @@ -280,8 +281,21 @@ class TemplatesDict(object): def objected_templates(self): return self._objected_templates - @classmethod - def create_ojected_templates(cls, templates): + def _create_template_object(self, template): + """Create template object from a template string. + + Separated into method to give option change class of templates. + + Args: + template (str): Template string. + + Returns: + StringTemplate: Object of template. + """ + + return StringTemplate(template) + + def create_objected_templates(self, templates): if not isinstance(templates, dict): raise TypeError("Expected dict object, got {}".format( str(type(templates)) @@ -297,7 +311,7 @@ class TemplatesDict(object): for key in tuple(item.keys()): value = item[key] if isinstance(value, six.string_types): - item[key] = StringTemplate(value) + item[key] = self._create_template_object(value) elif isinstance(value, dict): inner_queue.append(value) return objected_templates diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 683960f3d8..30748206a3 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -19,6 +19,7 @@ from openpype.client import get_project from openpype.lib.path_templates import ( TemplateUnsolved, TemplateResult, + StringTemplate, TemplatesDict, FormatObject, ) @@ -606,6 +607,32 @@ class AnatomyTemplateResult(TemplateResult): return self.__class__(tmp, self.rootless) +class AnatomyStringTemplate(StringTemplate): + """String template which has access to anatomy.""" + + def __init__(self, anatomy_templates, template): + self.anatomy_templates = anatomy_templates + super(AnatomyStringTemplate, self).__init__(template) + + def format(self, data): + """Format template and add 'root' key to data if not available. + + Args: + data (dict[str, Any]): Formatting data for template. + + Returns: + AnatomyTemplateResult: Formatting result. + """ + + anatomy_templates = self.anatomy_templates + if not data.get("root"): + data = copy.deepcopy(data) + data["root"] = anatomy_templates.anatomy.roots + result = StringTemplate.format(self, data) + rootless_path = anatomy_templates.rootless_path_from_result(result) + return AnatomyTemplateResult(result, rootless_path) + + class AnatomyTemplates(TemplatesDict): inner_key_pattern = re.compile(r"(\{@.*?[^{}0]*\})") inner_key_name_pattern = re.compile(r"\{@(.*?[^{}0]*)\}") @@ -615,12 +642,6 @@ class AnatomyTemplates(TemplatesDict): self.anatomy = anatomy self.loaded_project = None - def __getitem__(self, key): - return self.templates[key] - - def get(self, key, default=None): - return self.templates.get(key, default) - def reset(self): self._raw_templates = None self._templates = None @@ -655,12 +676,7 @@ class AnatomyTemplates(TemplatesDict): def _format_value(self, value, data): if isinstance(value, RootItem): return self._solve_dict(value, data) - - result = super(AnatomyTemplates, self)._format_value(value, data) - if isinstance(result, TemplateResult): - rootless_path = self._rootless_path(result, data) - result = AnatomyTemplateResult(result, rootless_path) - return result + return super(AnatomyTemplates, self)._format_value(value, data) def set_templates(self, templates): if not templates: @@ -689,10 +705,13 @@ class AnatomyTemplates(TemplatesDict): solved_templates = self.solve_template_inner_links(templates) self._templates = solved_templates - self._objected_templates = self.create_ojected_templates( + self._objected_templates = self.create_objected_templates( solved_templates ) + def _create_template_object(self, template): + return AnatomyStringTemplate(self, template) + def default_templates(self): """Return default templates data with solved inner keys.""" return self.solve_template_inner_links( @@ -886,7 +905,8 @@ class AnatomyTemplates(TemplatesDict): return keys_by_subkey - def _dict_to_subkeys_list(self, subdict, pre_keys=None): + @classmethod + def _dict_to_subkeys_list(cls, subdict, pre_keys=None): if pre_keys is None: pre_keys = [] output = [] @@ -895,7 +915,7 @@ class AnatomyTemplates(TemplatesDict): result = list(pre_keys) result.append(key) if isinstance(value, dict): - for item in self._dict_to_subkeys_list(value, result): + for item in cls._dict_to_subkeys_list(value, result): output.append(item) else: output.append(result) @@ -908,7 +928,17 @@ class AnatomyTemplates(TemplatesDict): return {key_list[0]: value} return {key_list[0]: self._keys_to_dicts(key_list[1:], value)} - def _rootless_path(self, result, final_data): + @classmethod + def rootless_path_from_result(cls, result): + """Calculate rootless path from formatting result. + + Args: + result (TemplateResult): Result of StringTemplate formatting. + + Returns: + str: Rootless path if result contains one of anatomy roots. + """ + used_values = result.used_values missing_keys = result.missing_keys template = result.template @@ -924,7 +954,7 @@ class AnatomyTemplates(TemplatesDict): if "root" in invalid_type: return - root_keys = self._dict_to_subkeys_list({"root": used_values["root"]}) + root_keys = cls._dict_to_subkeys_list({"root": used_values["root"]}) if not root_keys: return From 6f8f61fb4a47e26c67180bf84a169baff45df4f2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 15:10:34 +0100 Subject: [PATCH 227/428] Reinstate settings backwards compatibility. --- .../defaults/project_settings/maya.json | 123 ++++ .../schemas/schema_maya_capture.json | 625 ++++++++++++++++++ 2 files changed, 748 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 24d55de1fd..234a02c6d4 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -789,6 +789,129 @@ "validate_shapes": true }, "ExtractPlayblast": { + "capture_preset": { + "Codec": { + "compression": "png", + "format": "image", + "quality": 95 + }, + "Display Options": { + "override_display": true, + "background": [ + 125, + 125, + 125, + 255 + ], + "backgroundBottom": [ + 125, + 125, + 125, + 255 + ], + "backgroundTop": [ + 125, + 125, + 125, + 255 + ], + "displayGradient": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true, + "pan_zoom": false + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1920, + "height": 1080 + }, + "Viewport Options": { + "override_viewport_options": true, + "displayLights": "default", + "displayTextures": true, + "textureMaxResolution": 1024, + "renderDepthOfField": true, + "shadows": true, + "twoSidedLighting": true, + "lineAAEnable": true, + "multiSample": 8, + "useDefaultMaterial": false, + "wireframeOnShaded": false, + "xray": false, + "jointXray": false, + "backfaceCulling": false, + "ssaoEnable": false, + "ssaoAmount": 1, + "ssaoRadius": 16, + "ssaoFilterRadius": 16, + "ssaoSamples": 16, + "fogging": false, + "hwFogFalloff": "0", + "hwFogDensity": 0.0, + "hwFogStart": 0, + "hwFogEnd": 100, + "hwFogAlpha": 0, + "hwFogColorR": 1.0, + "hwFogColorG": 1.0, + "hwFogColorB": 1.0, + "motionBlurEnable": false, + "motionBlurSampleCount": 8, + "motionBlurShutterOpenFraction": 0.2, + "cameras": false, + "clipGhosts": false, + "deformers": false, + "dimensions": false, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "greasePencils": false, + "grid": false, + "hairSystems": true, + "handles": false, + "headsUpDisplay": false, + "ikHandles": false, + "imagePlane": true, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "controlVertices": false, + "nurbsCurves": false, + "hulls": false, + "nurbsSurfaces": false, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "strokes": false, + "subdivSurfaces": false, + "textures": false, + "pluginObjects": { + "gpuCacheDisplayFilter": false + } + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } + }, "profiles": [ { "task_types": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 1909a20cf5..19c169df9c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -4,6 +4,631 @@ "key": "ExtractPlayblast", "label": "Extract Playblast settings", "children": [ + { + "type": "dict", + "key": "capture_preset", + "label": "DEPRECATED! Please use \"Profiles\" below.", + "collapsed": false, + "children": [ + { + "type": "dict", + "key": "Codec", + "children": [ + { + "type": "label", + "label": "Codec" + }, + { + "type": "text", + "key": "compression", + "label": "Encoding" + }, + { + "type": "text", + "key": "format", + "label": "Format" + }, + { + "type": "number", + "key": "quality", + "label": "Quality", + "decimal": 0, + "minimum": 0, + "maximum": 100 + }, + + { + "type": "splitter" + } + ] + }, + { + "type": "dict", + "key": "Display Options", + "children": [ + { + "type": "label", + "label": "Display Options" + }, + { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + }, + { + "type": "color", + "key": "background", + "label": "Background Color: " + }, + { + "type": "color", + "key": "backgroundBottom", + "label": "Background Bottom: " + }, + { + "type": "color", + "key": "backgroundTop", + "label": "Background Top: " + }, + { + "type": "boolean", + "key": "displayGradient", + "label": "Display background gradient" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + }, + { + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + }, + { + "type": "boolean", + "key": "pan_zoom", + "label": " 2D Pan/Zoom" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "key": "Renderer", + "children": [ + { + "type": "label", + "label": "Renderer" + }, + { + "type": "enum", + "key": "rendererName", + "label": "Renderer name", + "enum_items": [ + { "vp2Renderer": "Viewport 2.0" } + ] + } + ] + }, + { + "type": "dict", + "key": "Resolution", + "children": [ + { + "type": "splitter" + }, + { + "type": "label", + "label": "Resolution" + }, + { + "type": "number", + "key": "width", + "label": " Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + }, + { + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict", + "collapsible": true, + "key": "Viewport Options", + "label": "Viewport Options", + "children": [ + { + "type": "boolean", + "key": "override_viewport_options", + "label": "Override Viewport Options" + }, + { + "type": "enum", + "key": "displayLights", + "label": "Display Lights", + "enum_items": [ + { "default": "Default Lighting"}, + { "all": "All Lights"}, + { "selected": "Selected Lights"}, + { "flat": "Flat Lighting"}, + { "nolights": "No Lights"} + ] + }, + { + "type": "boolean", + "key": "displayTextures", + "label": "Display Textures" + }, + { + "type": "number", + "key": "textureMaxResolution", + "label": "Texture Clamp Resolution", + "decimal": 0 + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Display" + }, + { + "type":"boolean", + "key": "renderDepthOfField", + "label": "Depth of Field" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "shadows", + "label": "Display Shadows" + }, + { + "type": "boolean", + "key": "twoSidedLighting", + "label": "Two Sided Lighting" + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "lineAAEnable", + "label": "Enable Anti-Aliasing" + }, + { + "type": "number", + "key": "multiSample", + "label": "Anti Aliasing Samples", + "decimal": 0, + "minimum": 0, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "useDefaultMaterial", + "label": "Use Default Material" + }, + { + "type": "boolean", + "key": "wireframeOnShaded", + "label": "Wireframe On Shaded" + }, + { + "type": "boolean", + "key": "xray", + "label": "X-Ray" + }, + { + "type": "boolean", + "key": "jointXray", + "label": "X-Ray Joints" + }, + { + "type": "boolean", + "key": "backfaceCulling", + "label": "Backface Culling" + }, + { + "type": "boolean", + "key": "ssaoEnable", + "label": "Screen Space Ambient Occlusion" + }, + { + "type": "number", + "key": "ssaoAmount", + "label": "SSAO Amount" + }, + { + "type": "number", + "key": "ssaoRadius", + "label": "SSAO Radius" + }, + { + "type": "number", + "key": "ssaoFilterRadius", + "label": "SSAO Filter Radius", + "decimal": 0, + "minimum": 1, + "maximum": 32 + }, + { + "type": "number", + "key": "ssaoSamples", + "label": "SSAO Samples", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "fogging", + "label": "Enable Hardware Fog" + }, + { + "type": "enum", + "key": "hwFogFalloff", + "label": "Hardware Falloff", + "enum_items": [ + { "0": "Linear"}, + { "1": "Exponential"}, + { "2": "Exponential Squared"} + ] + }, + { + "type": "number", + "key": "hwFogDensity", + "label": "Fog Density", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogStart", + "label": "Fog Start" + }, + { + "type": "number", + "key": "hwFogEnd", + "label": "Fog End" + }, + { + "type": "number", + "key": "hwFogAlpha", + "label": "Fog Alpha" + }, + { + "type": "number", + "key": "hwFogColorR", + "label": "Fog Color R", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorG", + "label": "Fog Color G", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "number", + "key": "hwFogColorB", + "label": "Fog Color B", + "decimal": 2, + "minimum": 0, + "maximum": 1 + }, + { + "type": "splitter" + }, + { + "type": "boolean", + "key": "motionBlurEnable", + "label": "Enable Motion Blur" + }, + { + "type": "number", + "key": "motionBlurSampleCount", + "label": "Motion Blur Sample Count", + "decimal": 0, + "minimum": 8, + "maximum": 32 + }, + { + "type": "number", + "key": "motionBlurShutterOpenFraction", + "label": "Shutter Open Fraction", + "decimal": 3, + "minimum": 0.01, + "maximum": 32 + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Show" + }, + { + "type": "boolean", + "key": "cameras", + "label": "Cameras" + }, + { + "type": "boolean", + "key": "clipGhosts", + "label": "Clip Ghosts" + }, + { + "type": "boolean", + "key": "deformers", + "label": "Deformers" + }, + { + "type": "boolean", + "key": "dimensions", + "label": "Dimensions" + }, + { + "type": "boolean", + "key": "dynamicConstraints", + "label": "Dynamic Constraints" + }, + { + "type": "boolean", + "key": "dynamics", + "label": "Dynamics" + }, + { + "type": "boolean", + "key": "fluids", + "label": "Fluids" + }, + { + "type": "boolean", + "key": "follicles", + "label": "Follicles" + }, + { + "type": "boolean", + "key": "greasePencils", + "label": "Grease Pencil" + }, + { + "type": "boolean", + "key": "grid", + "label": "Grid" + }, + { + "type": "boolean", + "key": "hairSystems", + "label": "Hair Systems" + }, + { + "type": "boolean", + "key": "handles", + "label": "Handles" + }, + { + "type": "boolean", + "key": "headsUpDisplay", + "label": "HUD" + }, + { + "type": "boolean", + "key": "ikHandles", + "label": "IK Handles" + }, + { + "type": "boolean", + "key": "imagePlane", + "label": "Image Planes" + }, + { + "type": "boolean", + "key": "joints", + "label": "Joints" + }, + { + "type": "boolean", + "key": "lights", + "label": "Lights" + }, + { + "type": "boolean", + "key": "locators", + "label": "Locators" + }, + { + "type": "boolean", + "key": "manipulators", + "label": "Manipulators" + }, + { + "type": "boolean", + "key": "motionTrails", + "label": "Motion Trails" + }, + { + "type": "boolean", + "key": "nCloths", + "label": "nCloths" + }, + { + "type": "boolean", + "key": "nParticles", + "label": "nParticles" + }, + { + "type": "boolean", + "key": "nRigids", + "label": "nRigids" + }, + { + "type": "boolean", + "key": "controlVertices", + "label": "NURBS CVs" + }, + { + "type": "boolean", + "key": "nurbsCurves", + "label": "NURBS Curves" + }, + { + "type": "boolean", + "key": "hulls", + "label": "NURBS Hulls" + }, + { + "type": "boolean", + "key": "nurbsSurfaces", + "label": "NURBS Surfaces" + }, + { + "type": "boolean", + "key": "particleInstancers", + "label": "Particle Instancers" + }, + { + "type": "boolean", + "key": "pivots", + "label": "Pivots" + }, + { + "type": "boolean", + "key": "planes", + "label": "Planes" + }, + { + "type": "boolean", + "key": "pluginShapes", + "label": "Plugin Shapes" + }, + { + "type": "boolean", + "key": "polymeshes", + "label": "Polygons" + }, + { + "type": "boolean", + "key": "strokes", + "label": "Strokes" + }, + { + "type": "boolean", + "key": "subdivSurfaces", + "label": "Subdiv Surfaces" + }, + { + "type": "boolean", + "key": "textures", + "label": "Texture Placements" + }, + { + "type": "dict-modifiable", + "key": "pluginObjects", + "label": "Plugin Objects", + "object_type": "boolean" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "Camera Options", + "label": "Camera Options", + "children": [ + { + "type": "boolean", + "key": "displayGateMask", + "label": "Display Gate Mask" + }, + { + "type": "boolean", + "key": "displayResolution", + "label": "Display Resolution" + }, + { + "type": "boolean", + "key": "displayFilmGate", + "label": "Display Film Gate" + }, + { + "type": "boolean", + "key": "displayFieldChart", + "label": "Display Field Chart" + }, + { + "type": "boolean", + "key": "displaySafeAction", + "label": "Display Safe Action" + }, + { + "type": "boolean", + "key": "displaySafeTitle", + "label": "Display Safe Title" + }, + { + "type": "boolean", + "key": "displayFilmPivot", + "label": "Display Film Pivot" + }, + { + "type": "boolean", + "key": "displayFilmOrigin", + "label": "Display Film Origin" + }, + { + "type": "number", + "key": "overscan", + "label": "Overscan", + "decimal": 1, + "minimum": 0, + "maximum": 10 + } + ] + } + ] + }, { "type": "list", "key": "profiles", From c2fede9798b45985f05ae02dd27f4f6cff0ecce7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 5 Apr 2023 16:59:27 +0200 Subject: [PATCH 228/428] Handle Harmony zip files with deeper structure (#4782) External Harmony zip files might contain one additional level with scene name. --- openpype/hosts/harmony/api/lib.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py index 8048705dc8..b009dabb44 100644 --- a/openpype/hosts/harmony/api/lib.py +++ b/openpype/hosts/harmony/api/lib.py @@ -242,9 +242,15 @@ def launch_zip_file(filepath): print(f"Localizing {filepath}") temp_path = get_local_harmony_path(filepath) + scene_name = os.path.basename(temp_path) + if os.path.exists(os.path.join(temp_path, scene_name)): + # unzipped with duplicated scene_name + temp_path = os.path.join(temp_path, scene_name) + scene_path = os.path.join( - temp_path, os.path.basename(temp_path) + ".xstage" + temp_path, scene_name + ".xstage" ) + unzip = False if os.path.exists(scene_path): # Check remote scene is newer than local. @@ -262,6 +268,10 @@ def launch_zip_file(filepath): with _ZipFile(filepath, "r") as zip_ref: zip_ref.extractall(temp_path) + if os.path.exists(os.path.join(temp_path, scene_name)): + # unzipped with duplicated scene_name + temp_path = os.path.join(temp_path, scene_name) + # Close existing scene. if ProcessContext.pid: os.kill(ProcessContext.pid, signal.SIGTERM) @@ -309,7 +319,7 @@ def launch_zip_file(filepath): ) if not os.path.exists(scene_path): - print("error: cannot determine scene file") + print("error: cannot determine scene file {}".format(scene_path)) ProcessContext.server.stop() return From 6dd6cc54394f5294b5472cc5e2cee61cbb324d9d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 5 Apr 2023 17:17:55 +0200 Subject: [PATCH 229/428] :recycle: modify UE launching to use executable from settings --- .../unreal/hooks/pre_workfile_preparation.py | 29 ++++--------------- openpype/hosts/unreal/lib.py | 8 +++++ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index da12bc75de..5dae7eef09 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -24,7 +24,7 @@ class UnrealPrelaunchHook(PreLaunchHook): """Hook to handle launching Unreal. This hook will check if current workfile path has Unreal - project inside. IF not, it initialize it and finally it pass + project inside. IF not, it initializes it, and finally it pass path to the project by environment variable to Unreal launcher shell script. @@ -141,6 +141,7 @@ class UnrealPrelaunchHook(PreLaunchHook): def execute(self): """Hook entry method.""" workdir = self.launch_context.env["AVALON_WORKDIR"] + executable = str(self.launch_context.executable) engine_version = self.app_name.split("/")[-1].replace("-", ".") try: if int(engine_version.split(".")[0]) < 4 and \ @@ -152,7 +153,7 @@ class UnrealPrelaunchHook(PreLaunchHook): # there can be string in minor version and in that case # int cast is failing. This probably happens only with # early access versions and is of no concert for this check - # so lets keep it quite. + # so let's keep it quiet. ... unreal_project_filename = self._get_work_filename() @@ -183,26 +184,6 @@ class UnrealPrelaunchHook(PreLaunchHook): f"[ {engine_version} ]" )) - detected = unreal_lib.get_engine_versions(self.launch_context.env) - detected_str = ', '.join(detected.keys()) or 'none' - self.log.info(( - f"{self.signature} detected UE versions: " - f"[ {detected_str} ]" - )) - if not detected: - raise ApplicationNotFound("No Unreal Engines are found.") - - engine_version = ".".join(engine_version.split(".")[:2]) - if engine_version not in detected.keys(): - raise ApplicationLaunchFailed(( - f"{self.signature} requested version not " - f"detected [ {engine_version} ]" - )) - - ue_path = unreal_lib.get_editor_exe_path( - Path(detected[engine_version]), engine_version) - - self.launch_context.launch_args = [ue_path.as_posix()] project_path.mkdir(parents=True, exist_ok=True) # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for @@ -217,7 +198,9 @@ class UnrealPrelaunchHook(PreLaunchHook): if self.launch_context.env.get(env_key): os.environ[env_key] = self.launch_context.env[env_key] - engine_path: Path = Path(detected[engine_version]) + # engine_path points to the specific Unreal Engine root + # so, we are going up from the executable itself 3 levels. + engine_path: Path = Path(executable).parents[3] if not unreal_lib.check_plugin_existence(engine_path): self.exec_plugin_install(engine_path) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 86ce0bb033..05fc87b318 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -23,6 +23,8 @@ def get_engine_versions(env=None): Location can be overridden by `UNREAL_ENGINE_LOCATION` environment variable. + .. deprecated:: 3.15.4 + Args: env (dict, optional): Environment to use. @@ -103,6 +105,8 @@ def _win_get_engine_versions(): This file is JSON file listing installed stuff, Unreal engines are marked with `"AppName" = "UE_X.XX"`` like `UE_4.24` + .. deprecated:: 3.15.4 + Returns: dict: version as a key and path as a value. @@ -122,6 +126,8 @@ def _darwin_get_engine_version() -> dict: It works the same as on Windows, just JSON file location is different. + .. deprecated:: 3.15.4 + Returns: dict: version as a key and path as a value. @@ -144,6 +150,8 @@ def _darwin_get_engine_version() -> dict: def _parse_launcher_locations(install_json_path: str) -> dict: """This will parse locations from json file. + .. deprecated:: 3.15.4 + Args: install_json_path (str): Path to `LauncherInstalled.dat`. From cc99791e3eb9c5f92f084fa075b634cccd7ff5fd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 5 Apr 2023 17:18:44 +0200 Subject: [PATCH 230/428] :art: add settings for unreal executable --- .../system_settings/applications.json | 120 +++++++++++++----- .../host_settings/schema_unreal.json | 7 +- 2 files changed, 92 insertions(+), 35 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index eb3a88ce66..d25e21a66e 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -133,7 +133,9 @@ "linux": [] }, "arguments": { - "windows": ["-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms"], + "windows": [ + "-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms" + ], "darwin": [], "linux": [] }, @@ -361,9 +363,15 @@ ] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -379,9 +387,15 @@ ] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -397,9 +411,15 @@ ] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -415,9 +435,15 @@ ] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -433,9 +459,15 @@ ] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -449,9 +481,15 @@ "linux": [] }, "arguments": { - "windows": ["--nukeassist"], - "darwin": ["--nukeassist"], - "linux": ["--nukeassist"] + "windows": [ + "--nukeassist" + ], + "darwin": [ + "--nukeassist" + ], + "linux": [ + "--nukeassist" + ] }, "environment": {} }, @@ -1450,21 +1488,45 @@ "label": "Unreal Editor", "icon": "{}/app_icons/ue4.png", "host_name": "unreal", - "environment": {}, + "environment": { + "UE_PYTHONPATH": "{PYTHONPATH}" + }, "variants": { - "4-27": { - "use_python_2": false, - "environment": {} - }, "5-0": { "use_python_2": false, - "environment": { - "UE_PYTHONPATH": "{PYTHONPATH}" - } + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.0\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "5-1": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Epic Games\\UE_5.1\\Engine\\Binaries\\Win64\\UnrealEditor.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} }, "__dynamic_keys_labels__": { - "4-27": "4.27", - "5-0": "5.0" + "5-1": "Unreal 5.1", + "5-0": "Unreal 5.0" } } }, diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json index 133d6c9eaf..df5ec0e6fa 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json @@ -30,12 +30,7 @@ "children": [ { "type": "schema_template", - "name": "template_host_variant_items", - "skip_paths": [ - "executables", - "separator", - "arguments" - ] + "name": "template_host_variant_items" } ] } From 9ed7e00254f42544a51702bcc3fe86e4169f05ff Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 16:59:55 +0100 Subject: [PATCH 231/428] Fix missing camera variable. --- openpype/hosts/maya/plugins/publish/collect_review.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 0b3799ac13..3652c0aa40 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -31,6 +31,7 @@ class CollectReview(pyblish.api.InstancePlugin): members = instance.data['setMembers'] self.log.debug('members: {}'.format(members)) cameras = cmds.ls(members, long=True, dag=True, cameras=True) + camera = cameras[0] if cameras else None context = instance.context objectset = context.data['objectsets'] @@ -62,7 +63,7 @@ class CollectReview(pyblish.api.InstancePlugin): data['families'] = ['review'] data["cameras"] = cameras - data['review_camera'] = cameras[0] if cameras else None + data['review_camera'] = camera data['frameStartFtrack'] = instance.data["frameStartHandle"] data['frameEndFtrack'] = instance.data["frameEndHandle"] data['frameStartHandle'] = instance.data["frameStartHandle"] @@ -97,7 +98,7 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['subset'] = legacy_subset_name instance.data["cameras"] = cameras - instance.data['review_camera'] = cameras[0] if cameras else None + instance.data['review_camera'] = camera instance.data['frameStartFtrack'] = \ instance.data["frameStartHandle"] instance.data['frameEndFtrack'] = \ @@ -145,6 +146,9 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data["audio"] = audio_data # Collect focal length. + if camera is None: + return + attr = camera + ".focalLength" if get_attribute_input(attr): start = instance.data["frameStart"] From abea98091aa22f1e6949b70832fd654f953375d5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 17:00:14 +0100 Subject: [PATCH 232/428] Code cosmetics --- openpype/hosts/maya/plugins/create/create_review.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 594faa7978..156f1e3461 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -45,9 +45,7 @@ class CreateReview(plugin.Creator): )["maya"]["publish"]["ExtractPlayblast"]["profiles"] preset = None - if not profiles: - self.log.warning("No profiles present for extract playblast.") - else: + if profiles: asset_doc = get_asset_by_name(project_name, data["asset"]) task_name = get_current_task_name() task_type = asset_doc["data"]["tasks"][task_name]["type"] @@ -62,6 +60,8 @@ class CreateReview(plugin.Creator): preset = filter_profiles( profiles, filtering_criteria, logger=self.log )["capture_preset"] + else: + self.log.warning("No profiles present for extract playblast.") # Option for using Maya or asset frame range in settings. frame_range = lib.get_frame_range() From f2f42fad308eebdcad7d9ee3267e26a1772bfb33 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 17:03:57 +0100 Subject: [PATCH 233/428] Reinstate backwards compatibility for publishing. --- .../maya/plugins/publish/extract_playblast.py | 20 +++++------ .../maya/plugins/publish/extract_thumbnail.py | 33 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 81007520a8..a9f5062c48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -34,6 +34,7 @@ class ExtractPlayblast(publish.Extractor): hosts = ["maya"] families = ["review"] optional = True + capture_preset = {} profiles = None def _capture(self, preset): @@ -48,10 +49,6 @@ class ExtractPlayblast(publish.Extractor): self.log.debug("playblast path {}".format(path)) def process(self, instance): - if not self.profiles: - self.log.warning("No profiles present for Extract Playblast") - return - self.log.info("Extracting capture..") # get scene fps @@ -85,12 +82,15 @@ class ExtractPlayblast(publish.Extractor): "task_types": task_type, "subset": subset } - capture_preset = filter_profiles( - self.profiles, filtering_criteria, logger=self.log - )["capture_preset"] - preset = lib.load_capture_preset( - data=capture_preset - ) + capture_preset = self.capture_preset + preset = lib.load_capture_preset(data=self.capture_preset) + if self.profiles: + capture_preset = filter_profiles( + self.profiles, filtering_criteria, logger=self.log + )["capture_preset"] + preset = lib.load_capture_preset(data=capture_preset) + else: + self.log.warning("No profiles present for Extract Playblast") # "isolate_view" will already have been applied at creation, so we'll # ignore it here. diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index cf0f80fa15..8d635d0df2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -25,13 +25,6 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): - maya_settings = instance.context.data["project_settings"]["maya"] - profiles = maya_settings["publish"]["ExtractPlayblast"]["profiles"] - - if not profiles: - self.log.warning("No profiles present for Extract Playblast") - return - self.log.info("Extracting capture..") camera = instance.data["review_camera"] @@ -50,12 +43,26 @@ class ExtractThumbnail(publish.Extractor): "task_types": task_type, "subset": subset } - capture_preset = filter_profiles( - profiles, filtering_criteria, logger=self.log - )["capture_preset"] - preset = lib.load_capture_preset( - data=capture_preset - ) + + maya_settings = instance.context.data["project_settings"]["maya"] + plugin_settings = maya_settings["publish"]["ExtractPlayblast"] + + capture_preset = plugin_settings["capture_preset"] + preset = {} + try: + preset = lib.load_capture_preset(data=capture_preset) + except KeyError as ke: + self.log.error("Error loading capture presets: {}".format(str(ke))) + + if plugin_settings["profiles"]: + capture_preset = filter_profiles( + plugin_settings["profiles"], + filtering_criteria, + logger=self.log + )["capture_preset"] + preset = lib.load_capture_preset(data=capture_preset) + else: + self.log.warning("No profiles present for Extract Playblast") # "isolate_view" will already have been applied at creation, so we'll # ignore it here. From 0b3802d9f27c1def5a8dd07553ca205f88d02a85 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 5 Apr 2023 17:08:19 +0100 Subject: [PATCH 234/428] Remove default profile. --- .../defaults/project_settings/maya.json | 131 +----------------- 1 file changed, 1 insertion(+), 130 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 234a02c6d4..8c817b5ba0 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -912,136 +912,7 @@ "overscan": 1.0 } }, - "profiles": [ - { - "task_types": [], - "task_names": [], - "subsets": [], - "capture_preset": { - "Codec": { - "compression": "png", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 125, - 125, - 125, - 255 - ], - "backgroundBottom": [ - 125, - 125, - 125, - 255 - ], - "backgroundTop": [ - 125, - 125, - 125, - 255 - ], - "override_display": true, - "displayGradient": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true, - "pan_zoom": false - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "width": 0, - "height": 0 - }, - "Viewport Options": { - "override_viewport_options": true, - "displayLights": "default", - "displayTextures": true, - "textureMaxResolution": 1024, - "renderDepthOfField": true, - "shadows": true, - "twoSidedLighting": true, - "lineAAEnable": true, - "multiSample": 8, - "useDefaultMaterial": false, - "wireframeOnShaded": false, - "xray": false, - "jointXray": false, - "backfaceCulling": false, - "ssaoEnable": false, - "ssaoAmount": 1, - "ssaoRadius": 16, - "ssaoFilterRadius": 16, - "ssaoSamples": 16, - "fogging": false, - "hwFogFalloff": "0", - "hwFogDensity": 0.0, - "hwFogStart": 0, - "hwFogEnd": 100, - "hwFogAlpha": 0, - "hwFogColorR": 1.0, - "hwFogColorG": 1.0, - "hwFogColorB": 1.0, - "motionBlurEnable": false, - "motionBlurSampleCount": 0, - "motionBlurShutterOpenFraction": 0.2, - "cameras": false, - "clipGhosts": false, - "deformers": false, - "dimensions": false, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "greasePencils": false, - "grid": false, - "hairSystems": true, - "handles": false, - "headsUpDisplay": false, - "ikHandles": false, - "imagePlane": true, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "controlVertices": false, - "nurbsCurves": false, - "hulls": false, - "nurbsSurfaces": false, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "strokes": false, - "subdivSurfaces": false, - "textures": false, - "pluginObjects": { - "gpuCacheDisplayFilter": false - } - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 - } - } - } - ] + "profiles": [] }, "ExtractMayaSceneRaw": { "enabled": true, From 7d9af1fdb1d8bf4a4d21af61ab45cc051e6c5d45 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Apr 2023 18:09:09 +0200 Subject: [PATCH 235/428] Add addition family filter 'review' to burnin profile with focal length --- openpype/settings/defaults/project_settings/global.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 30e56300d1..88a211b512 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -267,7 +267,9 @@ "BOTTOM_CENTERED": "{asset}", "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", "filter": { - "families": [], + "families": [ + "review" + ], "tags": [] } } From 6048e6e32732f59ebd900492b2aaa7a7a6e5edd7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 6 Apr 2023 08:12:48 +0100 Subject: [PATCH 236/428] Change family attribute to use. --- openpype/settings/defaults/project_settings/global.json | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 88a211b512..3dcd57ce43 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -251,7 +251,7 @@ } }, { - "families": [], + "families": ["review"], "hosts": [ "maya" ], @@ -259,7 +259,7 @@ "task_names": [], "subsets": [], "burnins": { - "maya_burnin": { + "maya_review_burnin": { "TOP_LEFT": "{yy}-{mm}-{dd}", "TOP_CENTERED": "{focalLength:.2f} mm", "TOP_RIGHT": "{anatomy[version]}", @@ -267,9 +267,7 @@ "BOTTOM_CENTERED": "{asset}", "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", "filter": { - "families": [ - "review" - ], + "families": [], "tags": [] } } From 7cca37993e5da4cf2a572f1731be8d2843018fab Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 6 Apr 2023 17:32:37 +0800 Subject: [PATCH 237/428] add farm instance to the render colletor --- openpype/hosts/max/plugins/publish/collect_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 63e4108c84..bc5987da9a 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -62,6 +62,7 @@ class CollectRender(pyblish.api.InstancePlugin): "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], "version": version_int, + "farm" : True } self.log.info("data: {0}".format(data)) instance.data.update(data) From 27c957d3f0649e9fe5f734411ade452e95ca651a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 6 Apr 2023 17:36:55 +0800 Subject: [PATCH 238/428] hound fix --- openpype/hosts/max/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index bc5987da9a..b040467522 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -62,7 +62,7 @@ class CollectRender(pyblish.api.InstancePlugin): "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], "version": version_int, - "farm" : True + "farm": True } self.log.info("data: {0}".format(data)) instance.data.update(data) From 25295c86772d468fbc98b8a22fda5b39370917c5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Apr 2023 11:38:28 +0200 Subject: [PATCH 239/428] Merge remote-tracking branch 'upstream/develop' into deadline_remove_framespertask # Conflicts: # openpype/modules/deadline/plugins/publish/submit_max_deadline.py --- .github/pr-branch-labeler.yml | 15 + .github/pr-glob-labeler.yml | 102 + .github/workflows/project_actions.yml | 115 + ARCHITECTURE.md | 77 + CHANGELOG.md | 926 ++ openpype/client/entities.py | 8 +- openpype/client/notes.md | 4 +- openpype/client/operations.py | 10 +- .../hooks/pre_create_extra_workdir_folders.py | 9 +- openpype/hooks/pre_foundry_apps.py | 4 +- openpype/host/dirmap.py | 4 +- openpype/host/host.py | 6 +- openpype/host/interfaces.py | 10 +- .../api/extension/jsx/hostscript.jsx | 2 +- openpype/hosts/aftereffects/api/ws_stub.py | 2 +- openpype/hosts/blender/api/ops.py | 6 +- .../plugins/publish/extract_playblast.py | 3 +- .../celaction/hooks/pre_celaction_setup.py | 36 +- .../publish/collect_celaction_cli_kwargs.py | 2 +- openpype/hosts/flame/api/lib.py | 4 +- openpype/hosts/flame/api/pipeline.py | 2 +- openpype/hosts/flame/api/plugin.py | 54 +- .../hosts/flame/api/scripts/wiretap_com.py | 2 +- openpype/hosts/flame/api/utils.py | 2 +- openpype/hosts/flame/hooks/pre_flame_setup.py | 2 +- .../flame/plugins/create/create_shot_clip.py | 2 +- .../hosts/flame/plugins/load/load_clip.py | 6 +- .../flame/plugins/load/load_clip_batch.py | 8 +- .../publish/collect_timeline_instances.py | 4 +- .../publish/extract_subset_resources.py | 4 +- .../plugins/publish/integrate_batch_group.py | 2 +- openpype/hosts/fusion/api/action.py | 13 +- openpype/hosts/fusion/api/menu.py | 13 - .../fusion/plugins/create/create_saver.py | 83 +- openpype/hosts/fusion/plugins/load/actions.py | 5 +- .../publish/collect_expected_frames.py | 50 + .../plugins/publish/collect_render_target.py | 44 - .../fusion/plugins/publish/collect_renders.py | 25 + .../plugins/publish/extract_render_local.py | 109 + .../fusion/plugins/publish/render_local.py | 100 - .../publish/validate_create_folder_checked.py | 14 +- .../validate_expected_frames_existence.py | 78 + .../hosts/fusion/scripts/set_rendermode.py | 112 - openpype/hosts/harmony/api/README.md | 8 +- openpype/hosts/harmony/api/TB_sceneOpened.js | 29 +- openpype/hosts/harmony/api/lib.py | 20 +- openpype/hosts/harmony/api/pipeline.py | 2 +- openpype/hosts/harmony/api/server.py | 2 +- .../harmony/plugins/publish/extract_render.py | 13 +- .../publish/validate_scene_settings.py | 25 +- .../harmony/vendor/OpenHarmony/README.md | 6 +- .../harmony/vendor/OpenHarmony/openHarmony.js | 8 +- .../openHarmony/openHarmony_actions.js | 6 +- .../openHarmony/openHarmony_application.js | 6 +- .../openHarmony/openHarmony_attribute.js | 8 +- .../openHarmony/openHarmony_backdrop.js | 4 +- .../openHarmony/openHarmony_color.js | 6 +- .../openHarmony/openHarmony_column.js | 4 +- .../openHarmony/openHarmony_database.js | 4 +- .../openHarmony/openHarmony_dialog.js | 18 +- .../openHarmony/openHarmony_drawing.js | 18 +- .../openHarmony/openHarmony_element.js | 4 +- .../openHarmony/openHarmony_file.js | 6 +- .../openHarmony/openHarmony_frame.js | 10 +- .../openHarmony/openHarmony_list.js | 6 +- .../openHarmony/openHarmony_math.js | 16 +- .../openHarmony/openHarmony_metadata.js | 4 +- .../openHarmony/openHarmony_misc.js | 6 +- .../openHarmony/openHarmony_network.js | 8 +- .../openHarmony/openHarmony_node.js | 22 +- .../openHarmony/openHarmony_nodeLink.js | 24 +- .../vendor/OpenHarmony/openHarmony_tools.js | 8 +- .../harmony/vendor/OpenHarmony/package.json | 2 +- openpype/hosts/hiero/api/__init__.py | 2 +- openpype/hosts/hiero/api/lib.py | 6 +- openpype/hosts/hiero/api/pipeline.py | 4 +- openpype/hosts/hiero/api/plugin.py | 46 +- openpype/hosts/houdini/api/lib.py | 14 +- openpype/hosts/houdini/api/pipeline.py | 11 +- openpype/hosts/houdini/api/plugin.py | 2 +- openpype/hosts/houdini/api/shelves.py | 2 +- .../houdini/plugins/create/convert_legacy.py | 4 +- .../plugins/publish/collect_current_file.py | 5 +- openpype/hosts/max/api/lib.py | 104 + openpype/hosts/max/api/menu.py | 20 + openpype/hosts/max/api/pipeline.py | 5 + .../max/plugins/publish/collect_render.py | 2 +- .../publish/increment_workfile_version.py | 19 + openpype/hosts/maya/api/commands.py | 2 +- openpype/hosts/maya/api/lib.py | 215 +- openpype/hosts/maya/api/lib_renderproducts.py | 31 +- openpype/hosts/maya/api/lib_rendersetup.py | 15 +- .../maya/api/workfile_template_builder.py | 4 +- .../hosts/maya/plugins/create/create_look.py | 4 +- .../maya/plugins/create/create_review.py | 2 + openpype/hosts/maya/plugins/load/actions.py | 2 +- .../maya/plugins/load/load_arnold_standin.py | 6 +- .../hosts/maya/plugins/load/load_audio.py | 22 +- .../hosts/maya/plugins/load/load_gpucache.py | 23 +- .../maya/plugins/load/load_image_plane.py | 145 +- .../hosts/maya/plugins/load/load_reference.py | 157 +- .../publish/collect_arnold_scene_source.py | 14 +- .../maya/plugins/publish/collect_instances.py | 49 +- .../maya/plugins/publish/collect_look.py | 2 +- .../publish/collect_multiverse_look.py | 2 +- .../maya/plugins/publish/collect_review.py | 162 +- .../publish/extract_arnold_scene_source.py | 84 +- .../maya/plugins/publish/extract_gpu_cache.py | 65 + .../maya/plugins/publish/extract_look.py | 890 +- .../publish/extract_multiverse_usd_over.py | 2 +- .../maya/plugins/publish/extract_playblast.py | 122 +- .../maya/plugins/publish/extract_thumbnail.py | 61 +- .../maya/plugins/publish/extract_vrayproxy.py | 4 +- .../plugins/publish/reset_xgen_attributes.py | 2 +- .../publish/validate_arnold_scene_source.py | 38 +- .../validate_arnold_scene_source_cbid.py | 74 + .../plugins/publish/validate_attributes.py | 110 +- .../publish/validate_camera_attributes.py | 2 +- .../plugins/publish/validate_frame_range.py | 4 +- .../publish/validate_look_color_space.py | 26 - .../plugins/publish/validate_look_contents.py | 22 + .../plugins/publish/validate_maya_units.py | 4 +- .../validate_mesh_arnold_attributes.py | 82 +- .../publish/validate_mesh_overlapping_uvs.py | 60 +- .../publish/validate_mvlook_contents.py | 3 +- .../plugins/publish/validate_no_namespace.py | 19 +- .../publish/validate_renderlayer_aovs.py | 4 +- .../publish/validate_rendersettings.py | 20 +- .../publish/validate_rig_output_ids.py | 75 +- .../validate_transform_naming_suffix.py | 2 +- .../hosts/maya/tools/mayalookassigner/app.py | 34 +- .../tools/mayalookassigner/arnold_standin.py | 247 + .../maya/tools/mayalookassigner/commands.py | 51 +- .../hosts/maya/tools/mayalookassigner/lib.py | 87 + .../tools/mayalookassigner/vray_proxies.py | 101 +- openpype/hosts/nuke/api/lib.py | 26 +- openpype/hosts/nuke/api/plugin.py | 23 +- openpype/hosts/nuke/api/utils.py | 2 +- .../nuke/api/workfile_template_builder.py | 29 +- .../nuke/plugins/create/convert_legacy.py | 2 +- .../nuke/plugins/create/create_source.py | 2 +- .../nuke/plugins/create/create_write_image.py | 7 - .../plugins/create/create_write_prerender.py | 7 - .../plugins/create/create_write_render.py | 7 - openpype/hosts/nuke/plugins/load/actions.py | 5 +- .../hosts/nuke/plugins/load/load_backdrop.py | 22 +- .../nuke/plugins/load/load_script_precomp.py | 1 - .../nuke/plugins/publish/collect_backdrop.py | 32 +- .../plugins/publish/collect_context_data.py | 2 - .../nuke/plugins/publish/collect_gizmo.py | 1 - .../nuke/plugins/publish/collect_model.py | 1 - .../nuke/plugins/publish/collect_reads.py | 4 +- .../nuke/plugins/publish/collect_writes.py | 2 +- .../plugins/publish/extract_review_data.py | 2 +- .../publish/extract_review_data_lut.py | 7 +- .../publish/extract_review_data_mov.py | 5 +- .../nuke/plugins/publish/extract_thumbnail.py | 2 +- .../nuke/plugins/publish/validate_backdrop.py | 2 +- .../photoshop/api/extension/host/index.jsx | 6 +- openpype/hosts/photoshop/api/launch_logic.py | 4 +- .../plugins/publish/extract_review.py | 1 - openpype/hosts/resolve/api/lib.py | 6 +- openpype/hosts/resolve/api/menu_style.qss | 2 +- openpype/hosts/resolve/api/plugin.py | 17 +- .../publish/collect_bulk_mov_instances.py | 4 +- .../plugins/publish/collect_context.py | 8 +- .../plugins/publish/collect_editorial.py | 2 +- .../plugins/publish/validate_frame_ranges.py | 2 +- openpype/hosts/traypublisher/api/editorial.py | 38 +- .../plugins/create/create_editorial.py | 14 +- .../publish/collect_simple_instances.py | 2 +- .../hosts/tvpaint/api/communication_server.py | 4 +- openpype/hosts/tvpaint/api/pipeline.py | 5 - .../tvpaint/plugins/create/convert_legacy.py | 4 +- .../tvpaint/plugins/create/create_render.py | 15 +- .../publish/collect_instance_frames.py | 2 + .../help/validate_layers_visibility.xml | 2 +- .../help/validate_workfile_metadata.xml | 2 +- .../help/validate_workfile_project_name.xml | 2 +- .../plugins/publish/validate_asset_name.py | 14 +- .../publish/validate_layers_visibility.py | 2 +- .../tvpaint/plugins/publish/validate_marks.py | 13 +- .../publish/validate_scene_settings.py | 13 +- .../plugins/publish/validate_start_frame.py | 13 +- openpype/hosts/unreal/api/pipeline.py | 2 +- .../Source/OpenPype/Private/OpenPypeLib.cpp | 2 +- .../Public/Commandlets/OPActionResult.h | 12 +- .../Source/OpenPype/Private/OpenPypeLib.cpp | 2 +- .../Public/Commandlets/OPActionResult.h | 12 +- .../hosts/unreal/plugins/load/load_camera.py | 2 +- openpype/hosts/unreal/ue_workers.py | 142 +- openpype/hosts/webpublisher/lib.py | 2 +- openpype/lib/applications.py | 20 +- openpype/lib/attribute_definitions.py | 6 +- openpype/lib/events.py | 2 +- openpype/lib/execute.py | 25 +- openpype/lib/file_transaction.py | 24 +- openpype/lib/path_templates.py | 26 +- openpype/lib/transcoding.py | 6 +- openpype/lib/vendor_bin_utils.py | 16 +- openpype/modules/base.py | 14 +- openpype/modules/clockify/clockify_api.py | 2 +- openpype/modules/clockify/clockify_module.py | 2 +- openpype/modules/clockify/widgets.py | 2 +- .../deadline/plugins/publish/collect_pools.py | 47 +- .../publish/submit_celaction_deadline.py | 2 +- .../plugins/publish/submit_nuke_deadline.py | 21 +- .../plugins/publish/submit_publish_job.py | 43 +- .../publish/validate_deadline_pools.py | 10 +- .../example_addons/example_addon/addon.py | 2 +- .../action_clone_review_session.py | 2 +- .../action_create_review_session.py | 2 +- .../action_prepare_project.py | 2 +- .../action_push_frame_values_to_task.py | 316 +- .../action_tranfer_hierarchical_values.py | 4 +- .../event_next_task_update.py | 2 +- .../event_push_frame_values_to_task.py | 1096 +- .../event_radio_buttons.py | 2 +- .../event_sync_to_avalon.py | 14 +- .../event_task_to_parent_status.py | 4 +- .../event_user_assigment.py | 4 +- .../event_version_to_task_statuses.py | 2 +- .../action_applications.py | 9 + .../action_batch_task_creation.py | 2 +- .../action_create_cust_attrs.py | 6 +- .../action_create_folders.py | 2 +- .../action_delete_asset.py | 2 +- .../action_delete_old_versions.py | 4 +- .../event_handlers_user/action_delivery.py | 2 +- .../action_fill_workfile_attr.py | 2 +- .../event_handlers_user/action_job_killer.py | 6 +- .../action_prepare_project.py | 4 +- .../ftrack/event_handlers_user/action_rv.py | 2 +- .../ftrack/event_handlers_user/action_seed.py | 10 +- .../action_store_thumbnails_to_avalon.py | 2 +- .../ftrack/ftrack_server/event_server_cli.py | 6 +- openpype/modules/ftrack/lib/avalon_sync.py | 14 +- .../modules/ftrack/lib/custom_attributes.py | 2 +- .../ftrack/lib/ftrack_action_handler.py | 2 +- .../modules/ftrack/lib/ftrack_base_handler.py | 4 +- .../publish/collect_custom_attributes_data.py | 2 +- .../plugins/publish/integrate_ftrack_api.py | 2 +- .../publish/integrate_hierarchy_ftrack.py | 2 +- openpype/modules/ftrack/tray/ftrack_tray.py | 2 +- openpype/modules/interfaces.py | 2 +- .../plugins/publish/collect_kitsu_entities.py | 2 +- .../plugins/publish/integrate_kitsu_note.py | 109 +- .../plugins/publish/integrate_kitsu_review.py | 8 +- .../modules/kitsu/utils/update_op_with_zou.py | 56 +- openpype/modules/settings_action.py | 6 +- openpype/pipeline/anatomy.py | 64 +- openpype/pipeline/colorspace.py | 5 +- openpype/pipeline/create/context.py | 100 +- openpype/pipeline/publish/contants.py | 1 + openpype/pipeline/publish/lib.py | 80 +- openpype/plugins/publish/cleanup.py | 4 + openpype/plugins/publish/cleanup_farm.py | 2 +- .../publish/collect_context_entities.py | 19 +- .../publish/collect_custom_staging_dir.py | 70 + .../plugins/publish/collect_otio_review.py | 4 +- openpype/plugins/publish/extract_burnin.py | 51 +- openpype/plugins/publish/extract_review.py | 403 +- openpype/plugins/publish/integrate.py | 20 +- .../plugins/publish/integrate_hero_version.py | 33 +- openpype/plugins/publish/integrate_legacy.py | 2 +- .../preintegrate_thumbnail_representation.py | 2 + openpype/scripts/otio_burnin.py | 125 +- .../defaults/project_anatomy/templates.json | 6 +- .../project_settings/applications.json | 3 + .../defaults/project_settings/celaction.json | 7 + .../defaults/project_settings/deadline.json | 5 +- .../defaults/project_settings/global.json | 26 +- .../defaults/project_settings/kitsu.json | 10 +- .../defaults/project_settings/maya.json | 33 +- .../defaults/project_settings/tvpaint.json | 1 + .../system_settings/applications.json | 2 +- .../schemas/projects_schema/schema_main.json | 4 + .../schema_project_applications.json | 14 + .../schema_project_celaction.json | 25 + .../schema_project_deadline.json | 17 +- .../projects_schema/schema_project_kitsu.json | 84 +- .../schema_project_tvpaint.json | 6 + .../schemas/schema_global_publish.json | 2 +- .../schemas/schema_global_tools.json | 65 + .../schemas/schema_maya_capture.json | 32 +- .../schemas/schema_maya_create.json | 5 + .../schemas/schema_maya_publish.json | 88 + openpype/tools/launcher/models.py | 6 + openpype/tools/loader/model.py | 7 +- .../project_manager/project_manager/view.py | 4 +- openpype/tools/publisher/constants.py | 5 +- openpype/tools/publisher/control.py | 185 +- openpype/tools/publisher/widgets/__init__.py | 6 +- .../publisher/widgets/card_view_widgets.py | 19 + .../tools/publisher/widgets/images/save.png | Bin 0 -> 3961 bytes .../publisher/widgets/list_view_widgets.py | 27 + .../publisher/widgets/overview_widget.py | 38 +- .../publisher/widgets/validations_widget.py | 4 +- openpype/tools/publisher/widgets/widgets.py | 34 +- openpype/tools/publisher/window.py | 129 +- openpype/tools/sceneinventory/model.py | 16 +- openpype/tools/sceneinventory/view.py | 7 + openpype/tools/traypublisher/window.py | 2 +- openpype/tools/utils/delegates.py | 8 +- openpype/tools/utils/lib.py | 4 +- openpype/tools/workfiles/files_widget.py | 22 +- openpype/tools/workfiles/save_as_dialog.py | 6 +- openpype/vendor/python/common/capture.py | 25 +- openpype/version.py | 2 +- openpype/widgets/splash_screen.py | 11 +- pyproject.toml | 2 +- .../nuke/test_deadline_publish_in_nuke.py | 18 + .../hosts/nuke/test_publish_in_nuke.py | 20 +- website/docs/admin_hosts_maya.md | 68 +- website/docs/artist_hosts_aftereffects.md | 36 +- .../assets/integrate_kitsu_note_settings.png | Bin 0 -> 48874 bytes .../maya-admin_extract_playblast_settings.png | Bin 0 -> 26814 bytes ...ract_playblast_settings_camera_options.png | Bin 0 -> 16732 bytes ...ct_playblast_settings_viewport_options.png | Bin 0 -> 1064191 bytes website/docs/assets/maya-admin_gpu_cache.png | Bin 0 -> 20248 bytes website/docs/module_kitsu.md | 15 +- .../global_tools_custom_staging_dir.png | Bin 0 -> 9940 bytes .../settings_project_global.md | 107 +- website/docs/pype2/admin_presets_plugins.md | 4 +- website/yarn.lock | 9690 ++++++++--------- 325 files changed, 11287 insertions(+), 8998 deletions(-) create mode 100644 .github/pr-branch-labeler.yml create mode 100644 .github/pr-glob-labeler.yml create mode 100644 .github/workflows/project_actions.yml create mode 100644 ARCHITECTURE.md create mode 100644 openpype/hosts/fusion/plugins/publish/collect_expected_frames.py delete mode 100644 openpype/hosts/fusion/plugins/publish/collect_render_target.py create mode 100644 openpype/hosts/fusion/plugins/publish/collect_renders.py create mode 100644 openpype/hosts/fusion/plugins/publish/extract_render_local.py delete mode 100644 openpype/hosts/fusion/plugins/publish/render_local.py create mode 100644 openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py delete mode 100644 openpype/hosts/fusion/scripts/set_rendermode.py create mode 100644 openpype/hosts/max/plugins/publish/increment_workfile_version.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_gpu_cache.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py delete mode 100644 openpype/hosts/maya/plugins/publish/validate_look_color_space.py create mode 100644 openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py create mode 100644 openpype/hosts/maya/tools/mayalookassigner/lib.py create mode 100644 openpype/plugins/publish/collect_custom_staging_dir.py create mode 100644 openpype/settings/defaults/project_settings/applications.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schema_project_applications.json create mode 100644 openpype/tools/publisher/widgets/images/save.png create mode 100644 website/docs/assets/integrate_kitsu_note_settings.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings_camera_options.png create mode 100644 website/docs/assets/maya-admin_extract_playblast_settings_viewport_options.png create mode 100644 website/docs/assets/maya-admin_gpu_cache.png create mode 100644 website/docs/project_settings/assets/global_tools_custom_staging_dir.png diff --git a/.github/pr-branch-labeler.yml b/.github/pr-branch-labeler.yml new file mode 100644 index 0000000000..b434326236 --- /dev/null +++ b/.github/pr-branch-labeler.yml @@ -0,0 +1,15 @@ +# Apply label "feature" if head matches "feature/*" +'type: feature': + head: "feature/*" + +# Apply label "feature" if head matches "feature/*" +'type: enhancement': + head: "enhancement/*" + +# Apply label "bugfix" if head matches one of "bugfix/*" or "hotfix/*" +'type: bug': + head: ["bugfix/*", "hotfix/*"] + +# Apply label "release" if base matches "release/*" +'Bump Minor': + base: "release/next-minor" diff --git a/.github/pr-glob-labeler.yml b/.github/pr-glob-labeler.yml new file mode 100644 index 0000000000..286e7768b5 --- /dev/null +++ b/.github/pr-glob-labeler.yml @@ -0,0 +1,102 @@ +# Add type: unittest label if any changes in tests folders +'type: unittest': +- '*/*tests*/**/*' + +# any changes in documentation structure +'type: documentation': +- '*/**/*website*/**/*' +- '*/**/*docs*/**/*' + +# hosts triage +'host: Nuke': +- '*/**/*nuke*' +- '*/**/*nuke*/**/*' + +'host: Photoshop': +- '*/**/*photoshop*' +- '*/**/*photoshop*/**/*' + +'host: Harmony': +- '*/**/*harmony*' +- '*/**/*harmony*/**/*' + +'host: UE': +- '*/**/*unreal*' +- '*/**/*unreal*/**/*' + +'host: Houdini': +- '*/**/*houdini*' +- '*/**/*houdini*/**/*' + +'host: Maya': +- '*/**/*maya*' +- '*/**/*maya*/**/*' + +'host: Resolve': +- '*/**/*resolve*' +- '*/**/*resolve*/**/*' + +'host: Blender': +- '*/**/*blender*' +- '*/**/*blender*/**/*' + +'host: Hiero': +- '*/**/*hiero*' +- '*/**/*hiero*/**/*' + +'host: Fusion': +- '*/**/*fusion*' +- '*/**/*fusion*/**/*' + +'host: Flame': +- '*/**/*flame*' +- '*/**/*flame*/**/*' + +'host: TrayPublisher': +- '*/**/*traypublisher*' +- '*/**/*traypublisher*/**/*' + +'host: 3dsmax': +- '*/**/*max*' +- '*/**/*max*/**/*' + +'host: TV Paint': +- '*/**/*tvpaint*' +- '*/**/*tvpaint*/**/*' + +'host: CelAction': +- '*/**/*celaction*' +- '*/**/*celaction*/**/*' + +'host: After Effects': +- '*/**/*aftereffects*' +- '*/**/*aftereffects*/**/*' + +'host: Substance Painter': +- '*/**/*substancepainter*' +- '*/**/*substancepainter*/**/*' + +# modules triage +'module: Deadline': +- '*/**/*deadline*' +- '*/**/*deadline*/**/*' + +'module: RoyalRender': +- '*/**/*royalrender*' +- '*/**/*royalrender*/**/*' + +'module: Sitesync': +- '*/**/*sync_server*' +- '*/**/*sync_server*/**/*' + +'module: Ftrack': +- '*/**/*ftrack*' +- '*/**/*ftrack*/**/*' + +'module: Shotgrid': +- '*/**/*shotgrid*' +- '*/**/*shotgrid*/**/*' + +'module: Kitsu': +- '*/**/*kitsu*' +- '*/**/*kitsu*/**/*' diff --git a/.github/workflows/project_actions.yml b/.github/workflows/project_actions.yml new file mode 100644 index 0000000000..3589b4acc2 --- /dev/null +++ b/.github/workflows/project_actions.yml @@ -0,0 +1,115 @@ +name: project-actions + +on: + pull_request_target: + types: [opened, assigned] + pull_request_review: + types: [submitted] + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + + pr_review_started: + name: pr_review_started + runs-on: ubuntu-latest + # ----------------------------- + # conditions are: + # - PR issue comment which is not form Ynbot + # - PR review comment which is not Hound (or any other bot) + # - PR review submitted which is not from Hound (or any other bot) and is not 'Changes requested' + # - make sure it only runs if not forked repo + # ----------------------------- + if: | + (github.event_name == 'issue_comment' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.comment.user.id != 82967070) || + (github.event_name == 'pull_request_review_comment' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.comment.user.type != 'Bot') || + (github.event_name == 'pull_request_review' && + github.event.pull_request.head.repo.owner.login == 'ynput' && + github.event.review.state != 'changes_requested' && + github.event.review.state != 'approved' && + github.event.review.user.type != 'Bot') + steps: + - name: Move PR to 'Review In Progress' + uses: leonsteinhaeuser/project-beta-automations@v2.1.0 + with: + gh_token: ${{ secrets.YNPUT_BOT_TOKEN }} + organization: ynput + project_id: 11 + resource_node_id: ${{ github.event.pull_request.node_id || github.event.issue.node_id }} + status_value: Review In Progress + + pr_review_requested: + # ----------------------------- + # Resets Clickup Task status to 'In Progress' after 'Changes Requested' were submitted to PR + # It only runs if custom clickup task id was found in ref branch of PR + # ----------------------------- + name: pr_review_requested + runs-on: ubuntu-latest + if: github.event_name == 'pull_request_review' && github.event.pull_request.head.repo.owner.login == 'ynput' && github.event.review.state == 'changes_requested' + steps: + - name: Set branch env + run: echo "BRANCH_NAME=${{ github.event.pull_request.head.ref}}" >> $GITHUB_ENV + - name: Get ClickUp ID from ref head name + id: get_cuID + run: | + echo ${{ env.BRANCH_NAME }} + echo "cuID=$(echo $BRANCH_NAME | sed 's/.*\/\(OP\-[0-9]\{4\}\).*/\1/')" >> $GITHUB_OUTPUT + + - name: Print ClickUp ID + run: echo ${{ steps.get_cuID.outputs.cuID }} + + - name: Move found Clickup task to 'Review in Progress' + if: steps.get_cuID.outputs.cuID + run: | + curl -i -X PUT \ + 'https://api.clickup.com/api/v2/task/${{ steps.get_cuID.outputs.cuID }}?custom_task_ids=true&team_id=${{secrets.CLICKUP_TEAM_ID}}' \ + -H 'Authorization: ${{secrets.CLICKUP_API_KEY}}' \ + -H 'Content-Type: application/json' \ + -d '{ + "status": "in progress" + }' + + size-label: + name: pr_size_label + runs-on: ubuntu-latest + if: github.event.action == 'assigned' || github.event.action == 'opened' + steps: + - name: Add size label + uses: "pascalgn/size-label-action@v0.4.3" + env: + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" + IGNORED: ".gitignore\n*.md\n*.json" + with: + sizes: > + { + "0": "XS", + "100": "S", + "500": "M", + "1000": "L", + "1500": "XL", + "2500": "XXL" + } + + label_prs_branch: + name: pr_branch_label + runs-on: ubuntu-latest + if: github.event.action == 'assigned' || github.event.action == 'opened' + steps: + - name: Label PRs - Branch name detection + uses: ffittschen/pr-branch-labeler@v1 + with: + repo-token: ${{ secrets.YNPUT_BOT_TOKEN }} + + label_prs_globe: + name: pr_globe_label + runs-on: ubuntu-latest + if: github.event.action == 'assigned' || github.event.action == 'opened' + steps: + - name: Label PRs - Globe detection + uses: actions/labeler@v4.0.3 + with: + repo-token: ${{ secrets.YNPUT_BOT_TOKEN }} + configuration-path: ".github/pr-glob-labeler.yml" + sync-labels: false diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..912780d803 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# Architecture + +OpenPype is a monolithic Python project that bundles several parts, this document will try to give a birds eye overview of the project and, to a certain degree, each of the sub-projects. +The current file structure looks like this: + +``` +. +├── common - Code in this folder is backend portion of Addon distribution logic for v4 server. +├── docs - Documentation of the source code. +├── igniter - The OpenPype bootstrapper, deals with running version resolution and setting up the connection to the mongodb. +├── openpype - The actual OpenPype core package. +├── schema - Collection of JSON files describing schematics of objects. This follows Avalon's convention. +├── tests - Integration and unit tests. +├── tools - Conveninece scripts to perform common actions (in both bash and ps1). +├── vendor - When using the igniter, it deploys third party tools in here, such as ffmpeg. +└── website - Source files for https://openpype.io/ which is Docusaursus (https://docusaurus.io/). +``` + +The core functionality of the pipeline can be found in `igniter` and `openpype`, which in turn rely on the `schema` files, whenever you build (or download a pre-built) version of OpenPype, these two are bundled in there, and `Igniter` is the entry point. + + +## Igniter + +It's the setup and update tool for OpenPype, unless you want to package `openpype` separately and deal with all the config manually, this will most likely be your entry point. + +``` +igniter/ +├── bootstrap_repos.py - Module that will find or install OpenPype versions in the system. +├── __init__.py - Igniter entry point. +├── install_dialog.py- Show dialog for choosing central pype repository. +├── install_thread.py - Threading helpers for the install process. +├── __main__.py - Like `__init__.py` ? +├── message_dialog.py - Qt Dialog with a message and "Ok" button. +├── nice_progress_bar.py - Fancy Qt progress bar. +├── splash.txt - ASCII art for the terminal installer. +├── stylesheet.css - Installer Qt styles. +├── terminal_splash.py - Terminal installer animation, relies in `splash.txt`. +├── tools.py - Collection of methods that don't fit in other modules. +├── update_thread.py - Threading helper to update existing OpenPype installs. +├── update_window.py - Qt UI to update OpenPype installs. +├── user_settings.py - Interface for the OpenPype user settings. +└── version.py - Igniter's version number. +``` + +## OpenPype + +This is the main package of the OpenPype logic, it could be loosely described as a combination of [Avalon](https://getavalon.github.io), [Pyblish](https://pyblish.com/) and glue around those with custom OpenPype only elements, things are in progress of being moved around to better prepare for V4, which will be released under a new name AYON. + +``` +openpype/ +├── client - Interface for the MongoDB. +├── hooks - Hooks to be executed on certain OpenPype Applications defined in `openpype.lib.applications`. +├── host - Base class for the different hosts. +├── hosts - Integration with the different DCCs (hosts) using the `host` base class. +├── lib - Libraries that stitch together the package, some have been moved into other parts. +├── modules - OpenPype modules should contain separated logic of specific kind of implementation, such as Ftrack connection and its python API. +├── pipeline - Core of the OpenPype pipeline, handles creation of data, publishing, etc. +├── plugins - Global/core plugins for loader and publisher tool. +├── resources - Icons, fonts, etc. +├── scripts - Loose scipts that get run by tools/publishers. +├── settings - OpenPype settings interface. +├── style - Qt styling. +├── tests - Unit tests. +├── tools - Core tools, check out https://openpype.io/docs/artist_tools. +├── vendor - Vendoring of needed required Python packes. +├── widgets - Common re-usable Qt Widgets. +├── action.py - LEGACY: Lives now in `openpype.pipeline.publish.action` Pyblish actions. +├── cli.py - Command line interface, leverages `click`. +├── __init__.py - Sets two constants. +├── __main__.py - Entry point, calls the `cli.py` +├── plugin.py - Pyblish plugins. +├── pype_commands.py - Implementation of OpenPype commands. +└── version.py - Current version number. +``` + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 145c2e2c1a..4e22b783c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,931 @@ # Changelog +## [3.15.3](https://github.com/ynput/OpenPype/tree/3.15.3) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.2...3.15.3) + +### **🆕 New features** + + +

+Blender: Extract Review #3616 + +Added Review to Blender. + +This implementation is based on #3508 but made compatible for the current implementation of OpenPype for Blender. + + +___ + +
+ + +
+Data Exchanges: Point Cloud for 3dsMax #4532 + +Publish PRT format with tyFlow in 3dsmax + +Publish PRT format with tyFlow in 3dsmax and possibly set up loader to load the format too. +- [x] creator +- [x] extractor +- [x] validator +- [x] loader + + +___ + +
+ + +
+Global: persistent staging directory for renders #4583 + +Allows configure if staging directory (`stagingDir`) should be persistent with use of profiles. + +With this feature, users can specify a transient data folder path based on presets, which can be used during the creation and publishing stages. In some cases, these DCCs automatically add a rendering path during the creation stage, which is then used in publishing.One of the key advantages of this feature is that it allows users to take advantage of faster storages for rendering, which can help improve workflow efficiency. Additionally, this feature allows users to keep their rendered data persistent, and use their own infrastructure for regular cleaning.However, it should be noted that some productions may want to use this feature without persistency. Furthermore, there may be a need for retargeting the rendering folder to faster storages, which is also not supported at the moment.It is studio responsibility to clean up obsolete folders with data.Location of the folder is configured in `project_anatomy/templates/others`. ('transient' key is expected, with 'folder' key, could be more templates)Which family/task type/subset is applicable is configured in:`project_settings/global/tools/publish/transient_dir_profiles` + + +___ + +
+ + +
+Kitsu custom comment template #4599 + +Kitsu allows to write markdown in its comment field. This can be something very powerful to deliver dynamic comments with the help the data from the instance.This feature is defaults to off so the admin have to manually set up the comment field the way they want.I have added a basic example on how the comment can look like as the comment-fields default value.To this I want to add some documentation also but that's on its way when the code itself looks good for the reviewers. + + +___ + +
+ + +
+MaxScene Family #4615 + +Introduction of the Max Scene Family + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Multiple values on single render attribute - OP-4131 #4631 + +When validating render attributes, this adds support for multiple values. When repairing first value in list is used. + + +___ + +
+ + +
+Maya: enable 2D Pan/Zoom for playblasts - OP-5213 #4687 + +Setting for enabling 2D Pan/Zoom on reviews. + + +___ + +
+ + +
+Copy existing or generate new Fusion profile on prelaunch #4572 + +Fusion preferences will be copied to the predefined `~/.openpype/hosts/fusion/prefs` folder (or any other folder set in system settings) on launch. + +The idea is to create a copy of existing Fusion profile, adding an OpenPype menu to the Fusion instance.By default the copy setting is turned off, so no file copying is performed. Instead the clean Fusion profile is created by Fusion in the predefined folder. The default locaion is set to `~/.openpype/hosts/fusion/prefs`, to better comply with the other os platforms. After creating the default profile, some modifications are applied: +- forced Python3 +- forced English interface +- setup Openpype specific path maps.If the `copy_prefs` checkbox is toggled, a copy of existing Fusion profile folder will be placed in the mentioned location. Then they are altered the same way as described above. The operation is run only once, on the first launch, unless the `force_sync [Resync profile on each launch]` is toggled.English interface is forced because the `FUSION16_PROFILE_DIR` environment variable is not read otherwise (seems to be a Fusion bug). + + +___ + +
+ + +
+Houdini: Create button open new publisher's "create" tab #4601 + +During a talk with @maxpareschi he mentioned that the new publisher in Houdini felt super confusing due to "Create" going to the older creator but now being completely empty and the publish button directly went to the publish tab.This resolves that by fixing the Create button to now open the new publisher but on the Create tab.Also made publish button enforce going to the "publish" tab for consistency in usage.@antirotor I think changing the Create button's callback was just missed in this commit or was there a specific reason to not change that around yet? + + +___ + +
+ + +
+Clockify: refresh and fix the integration #4607 + +Due to recent API changes, Clockify requires `user_id` to operate with the timers. I updated this part and currently it is a WIP for making it fully functional. Most functions, such as start and stop timer, and projects sync are currently working. For the rate limiting task new dependency is added: https://pypi.org/project/ratelimiter/ + + +___ + +
+ + +
+Fusion publish existing frames #4611 + +This PR adds the function to publish existing frames instead of having to re-render all of them for each new publish.I have split the render_locally plugin so the review-part is its own plugin now.I also change the saver-creator-plugin's label from Saver to Render (saver) as I intend to add a Prerender creator like in Nuke. + + +___ + +
+ + +
+Resolution settings referenced from DB record for 3dsMax #4652 + +- Add Callback for setting the resolution according to DB after the new scene is created. +- Add a new Action into openpype menu which allows the user to reset the resolution in 3dsMax + + +___ + +
+ + +
+3dsmax: render instance settings in Publish tab #4658 + +Allows user preset the pools, group and use_published settings in Render Creator in the Max Hosts.User can set the settings before or after creating instance in the new publisher + + +___ + +
+ + +
+scene length setting referenced from DB record for 3dsMax #4665 + +Setting the timeline length based on DB record in 3dsMax Hosts + + +___ + +
+ + +
+Publisher: Windows reduce command window pop-ups during Publishing #4672 + +Reduce the command line pop-ups that show on Windows during publishing. + + +___ + +
+ + +
+Publisher: Explicit save #4676 + +Publisher have explicit button to save changes, so reset can happen without saving any changes. Save still happens automatically when publishing is started or on publisher window close. But a popup is shown if context of host has changed. Important context was enhanced by workfile path (if host integration supports it) so workfile changes are captured too. In that case a dialog with confirmation is shown to user. All callbacks that may require save of context were moved to main window to be able handle dialog show at one place. Save changes now returns success so the rest of logic is skipped -> publishing won't start, when save of instances fails.Save and reset buttons have shortcuts (Ctrl + s and Ctrls + r). + + +___ + +
+ + +
+CelAction: conditional workfile parameters from settings #4677 + +Since some productions were requesting excluding some workfile parameters from publishing submission, we needed to move them to settings so those could be altered per project. + + +___ + +
+ + +
+Improve logging of used app + tool envs on application launch #4682 + +Improve logging of what apps + tool environments got loaded for an application launch. + + +___ + +
+ + +
+Fix name and docstring for Create Workdir Extra Folders prelaunch hook #4683 + +Fix class name and docstring for Create Workdir Extra Folders prelaunch hookThe class name and docstring were originally copied from another plug-in and didn't match the plug-in logic.This also fixes potentially seeing this twice in your logs. Before:After:Where it was actually running both this prelaunch hook and the actual `AddLastWorkfileToLaunchArgs` plugin. + + +___ + +
+ + +
+Application launch context: Include app group name in logger #4684 + +Clarify in logs better what app group the ApplicationLaunchContext belongs to and what application is being launched.Before:After: + + +___ + +
+ + +
+increment workfile version 3dsmax #4685 + +increment workfile version in 3dsmax as if in blender and maya hosts. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Fix getting non-active model panel. #2968 + +When capturing multiple cameras with image planes that have file sequences playing, only the active (first) camera will play through the file sequence. + + +___ + +
+ + +
+Maya: Fix broken review publishing. #4549 + +Resolves #4547 + + +___ + +
+ + +
+Maya: Avoid error on right click in Loader if `mtoa` is not loaded #4616 + +Fix an error on right clicking in the Loader when `mtoa` is not a loaded plug-in.Additionally if `mtoa` isn't loaded the loader will now load the plug-in before trying to create the arnold standin. + + +___ + +
+ + +
+Maya: Fix extract look colorspace detection #4618 + +Fix the logic which guesses the colorspace using `arnold` python library. +- Previously it'd error if `mtoa` was not available on path so it still required `mtoa` to be available. +- The guessing colorspace logic doesn't actually require `mtoa` to be loaded, but just the `arnold` python library to be available. This changes the logic so it doesn't require the `mtoa` plugin to get loaded to guess the colorspace. +- The if/else branch was likely not doing what was intended `cmds.loadPlugin("mtoa", quiet=True)` returns None if the plug-in was already loaded. So this would only ever be true if it ends up loading the `mtoa` plugin the first time. +```python +# Tested in Maya 2022.1 +print(cmds.loadPlugin("mtoa", quiet=True)) +# ['mtoa'] +print(cmds.loadPlugin("mtoa", quiet=True)) +# None +``` + + +___ + +
+ + +
+Maya: Maya Playblast Options overrides - OP-3847 #4634 + +When publishing a review in Maya, the extractor would fail due to wrong (long) panel name. + + +___ + +
+ + +
+Bugfix/op 2834 fix extract playblast #4701 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Bugfix/op 2834 fix extract playblast #4704 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Maya: bug fix for passing zoom settings if review is attached to subset #4716 + +Fix for attaching review to subset with pan/zoom option. + + +___ + +
+ + +
+Maya: tile assembly fail in draft - OP-4820 #4416 + +Tile assembly in Deadline was broken. + +Initial bug report revealed other areas of the tile assembly that needed fixing. + + +___ + +
+ + +
+Maya: Yeti Validate Rig Input - OP-3454 #4554 + +Fix Yeti Validate Rig Input + +Existing workflow was broken due to this #3297. + + +___ + +
+ + +
+Scene inventory: Fix code errors when "not found" entries are found #4594 + +Whenever a "NOT FOUND" entry is present a lot of errors happened in the Scene Inventory: +- It started spamming a lot of errors for the VersionDelegate since it had no numeric version (no version at all).Error reported on Discord: +```python +Traceback (most recent call last): + File "C:\Users\videopro\Documents\github\OpenPype\openpype\tools\utils\delegates.py", line 65, in paint + text = self.displayText( + File "C:\Users\videopro\Documents\github\OpenPype\openpype\tools\utils\delegates.py", line 33, in displayText + assert isinstance(value, numbers.Integral), ( +AssertionError: Version is not integer. "None" +``` +- Right click menu would error on NOT FOUND entries, and thus not show. With this PR it will now _disregard_ not found items for "Set version" and "Remove" but still allow actions.This PR resolves those. + + +___ + +
+ + +
+Kitsu: Sync OP with zou, make sure value-data is int or float #4596 + +Currently the data zou pulls is a string and not a value causing some bugs in the pipe where a value is expected (like `Set frame range` in Fusion). + + + +This PR makes sure each value is set with int() or float() so these bugs can't happen later on. + + + +_(A request to cgwire has also bin sent to allow force values only for some metadata columns, but currently the user can enter what ever they want in there)_ + + +___ + +
+ + +
+Max: fix the bug of removing an instance #4617 + +fix the bug of removing an instance in 3dsMax + + +___ + +
+ + +
+Global | Nuke: fixing farm publishing workflow #4623 + +After Nuke had adopted new publisher with new creators new issues were introduced. Those issues were addressed with this PR. Those are for example broken reviewable video files publishing if published via farm. Also fixed local publishing. + + +___ + +
+ + +
+Ftrack: Ftrack additional families filtering #4633 + +Ftrack family collector makes sure the subset family is also in instance families for additional families filtering. + + +___ + +
+ + +
+Ftrack: Hierarchical <> Non-Hierarchical attributes sync fix #4635 + +Sync between hierarchical and non-hierarchical attributes should be fixed and work as expected. Action should sync the values as expected and event handler should do it too and only on newly created entities. + + +___ + +
+ + +
+bugfix for 3dsmax publishing error #4637 + +fix the bug of failing publishing job in 3dsMax + + +___ + +
+ + +
+General: Use right validation for ffmpeg executable #4640 + +Use ffmpeg exec validation for ffmpeg executables instead of oiio exec validation. The validation is used as last possible source of ffmpeg from `PATH` environment variables, which is an edge case but can cause issues. + + +___ + +
+ + +
+3dsmax: opening last workfile #4644 + +Supports opening last saved workfile in 3dsmax host. + + +___ + +
+ + +
+Fixed a bug where a QThread in the splash screen could be destroyed before finishing execution #4647 + +This should fix the occasional behavior of the QThread being destroyed before even its worker returns from the `run()` function.After quiting, it should wait for the QThread object to properly close itself. + + +___ + +
+ + +
+General: Use right plugin class for Collect Comment #4653 + +Collect Comment plugin is instance plugin so should inherit from `InstancePlugin` instead of `ContextPlugin`. + + +___ + +
+ + +
+Global: add tags field to thumbnail representation #4660 + +Thumbnail representation might be missing tags field. + + +___ + +
+ + +
+Integrator: Enforce unique destination transfers, disallow overwrites in queued transfers #4662 + +Fix #4656 by enforcing unique destination transfers in the Integrator. It's now disallowed to a destination in the file transaction queue with a new source path during the publish. + + +___ + +
+ + +
+Hiero: Creator with correct workfile numeric padding input #4666 + +Creator was showing 99 in workfile input for long time, even if users set default value to 1001 in studio settings. This has been fixed now. + + +___ + +
+ + +
+Nuke: Nukenodes family instance without frame range #4669 + +No need to add frame range data into `nukenodes` (backdrop) family publishes - since those are timeless. + + +___ + +
+ + +
+TVPaint: Optional Validation plugins can be de/activated by user #4674 + +Added `OptionalPyblishPluginMixin` to TVpaint plugins that can be optional. + + +___ + +
+ + +
+Kitsu: Slightly less strict with instance data #4678 + +- Allow to take task name from context if asset doesn't have any. Fixes an issue with Photoshop's review instance not having `task` in data. +- Allow to match "review" against both `instance.data["family"]` and `instance.data["families"]` because some instances don't have the primary family in families, e.g. in Photoshop and TVPaint. +- Do not error on Integrate Kitsu Review whenever for whatever reason Integrate Kitsu Note did not created a comment but just log the message that it was unable to connect a review. + + +___ + +
+ + +
+Publisher: Fix reset shortcut sequence #4694 + +Fix bug created in https://github.com/ynput/OpenPype/pull/4676 where key sequence is checked using unsupported method. The check was changed to convert event into `QKeySequence` object which can be compared to prepared sequence. + + +___ + +
+ + +
+Refactor _capture #4702 + +Paragraphs contain detailed information on the changes made to the product or service, providing an in-depth description of the updates and enhancements. They can be used to explain the reasoning behind the changes, or to highlight the importance of the new features. Paragraphs can often include links to further information or support documentation. + + +___ + +
+ + +
+Hiero: correct container colors if UpToDate #4708 + +Colors on loaded containers are now correctly identifying real state of version. `Red` for out of date and `green` for up to date. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Look Assigner: Move Look Assigner tool since it's Maya only #4604 + +Fix #4357: Move Look Assigner tool to maya since it's Maya only + + +___ + +
+ + +
+Maya: Remove unused functions from Extract Look #4671 + +Remove unused functions from Maya Extract Look plug-in + + +___ + +
+ + +
+Extract Review code refactor #3930 + +Trying to reduce complexity of Extract Review plug-in +- Re-use profile filtering from lib +- Remove "combination families" additional filtering which supposedly was from OP v2 +- Simplify 'formatting' for filling gaps +- Use `legacy_io.Session` over `os.environ` + + +___ + +
+ + +
+Maya: Replace last usages of Qt module #4610 + +Replace last usage of `Qt` module with `qtpy`. This change is needed for `PySide6` support. All changes happened in Maya loader plugins. + + +___ + +
+ + +
+Update tests and documentation for `ColormanagedPyblishPluginMixin` #4612 + +Refactor `ExtractorColormanaged` to `ColormanagedPyblishPluginMixin` in tests and documentation. + + +___ + +
+ + +
+Improve logging of used app + tool envs on application launch (minor tweak) #4686 + +Use `app.full_name` for change done in #4682 + + +___ + +
+ +### **📃 Documentation** + + +
+Docs/add architecture document #4344 + +Add `ARCHITECTURE.md` document. + +his document attemps to give a quick overview of the project to help onboarding, it's not an extensive documentation but more of a elevator pitch one-line descriptions of files/directories and what the attempt to do. + + +___ + +
+ + +
+Documentation: Tweak grammar and fix some typos #4613 + +This resolves some grammar and typos in the documentation.Also fixes the extension of some images in after effects docs which used uppercase extension even though files were lowercase extension. + + +___ + +
+ + +
+Docs: Fix some minor grammar/typos #4680 + +Typo/grammar fixes in documentation. + + +___ + +
+ +### **Merged pull requests** + + +
+Maya: Implement image file node loader #4313 + +Implements a loader for loading texture image into a `file` node in Maya. + +Similar to Maya's hypershade creation of textures on load you have the option to choose for three modes of creating: +- Texture +- Projection +- StencilThese should match what Maya generates if you create those in Maya. +- [x] Load and manage file nodes +- [x] Apply color spaces after #4195 +- [x] Support for _either_ UDIM or image sequence - currently it seems to always load sequences as UDIM automatically. +- [ ] Add support for animation sequences of UDIM textures using the `..exr` path format? + + +___ + +
+ + +
+Maya Look Assigner: Don't rely on containers for get all assets #4600 + +This resolves #4044 by not actually relying on containers in the scene but instead just rely on finding nodes with `cbId` attributes. As such, imported nodes would also be found and a shader can be assigned (similar to when using get from selection).**Please take into consideration the potential downsides below**Potential downsides would be: +- IF an already loaded look has any dagNodes, say a 3D Projection node - then that will also show up as a loaded asset where previously nodes from loaded looks were ignored. +- If any dag nodes were created locally - they would have gotten `cbId` attributes on scene save and thus the current asset would almost always show? + + +___ + +
+ + +
+Maya: Unify menu labels for "Set Frame Range" and "Set Resolution" #4605 + +Fix #4109: Unify menu labels for "Set Frame Range" and "Set Resolution"This also tweaks it in Houdini from Reset Frame Range to Set Frame Range. + + +___ + +
+ + +
+Resolve missing OPENPYPE_MONGO in deadline global job preload #4484 + +In the GlobalJobPreLoad plugin, we propose to replace the SpawnProcess by a sub-process and to pass the environment variables in the parameters, since the SpawnProcess under Centos Linux does not pass the environment variables. + +In the GlobalJobPreLoad plugin, the Deadline SpawnProcess is used to start the OpenPype process. The problem is that the SpawnProcess does not pass environment variables, including OPENPYPE_MONGO, to the process when it is under Centos7 linux, and the process gets stuck. We propose to replace it by a subprocess and to pass the variable in the parameters. + + +___ + +
+ + +
+Tests: Added setup_only to tests #4591 + +Allows to download test zip, unzip and restore DB in preparation for new test. + + +___ + +
+ + +
+Maya: Arnold don't reset maya timeline frame range on render creation (or setting render settings) #4603 + +Fix #4429: Do not reset fps or playback timeline on applying or creating render settings + + +___ + +
+ + +
+Bump @sideway/formula from 3.0.0 to 3.0.1 in /website #4609 + +Bumps [@sideway/formula](https://github.com/sideway/formula) from 3.0.0 to 3.0.1. +
+Commits + +
+
+Maintainer changes +

This version was pushed to npm by marsup, a new releaser for @​sideway/formula since your current version.

+
+
+ + +[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sideway/formula&package-manager=npm_and_yarn&previous-version=3.0.0&new-version=3.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. + +[//]: # (dependabot-automerge-start) +[//]: # (dependabot-automerge-end) + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually +- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) +- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language +- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language +- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language +- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language + +You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ynput/OpenPype/network/alerts). + +
+___ + +
+ + +
+Update artist_hosts_maya_arnold.md #4626 + +Correct Arnold docs. +___ + +
+ + +
+Maya: Add "Include Parent Hierarchy" option in animation creator plugin #4645 + +Add an option in Project Settings > Maya > Creator Plugins > Create Animation to include (or not) parent hierarchy. This is to avoid artists to check manually the option for all create animation. + + +___ + +
+ + +
+General: Filter available applications #4667 + +Added option to filter applications that don't have valid executable available in settings in launcher and ftrack actions. This option can be disabled in new settings category `Applications`. The filtering is by default disabled. + + +___ + +
+ + +
+3dsmax: make sure that startup script executes #4695 + +Fixing reliability of OpenPype startup in 3dsmax. + + +___ + +
+ + +
+Project Manager: Change minimum frame start/end to '0' #4719 + +Project manager can have frame start/end set to `0`. + + +___ + +
+ + + +## [3.15.2](https://github.com/ynput/OpenPype/tree/3.15.2) [Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.1...3.15.2) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index c415be8816..7054658c64 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -3,7 +3,7 @@ Goal is that most of functions here are called on (or with) an object that has project name as a context (e.g. on 'ProjectEntity'?). -+ We will need more specific functions doing wery specific queires really fast. ++ We will need more specific functions doing very specific queries really fast. """ import re @@ -193,7 +193,7 @@ def _get_assets( be found. asset_names (Iterable[str]): Name assets that should be found. parent_ids (Iterable[Union[str, ObjectId]]): Parent asset ids. - standard (bool): Query standart assets (type 'asset'). + standard (bool): Query standard assets (type 'asset'). archived (bool): Query archived assets (type 'archived_asset'). fields (Iterable[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -1185,7 +1185,7 @@ def get_representations( standard=True, fields=None ): - """Representaion entities data from one project filtered by filters. + """Representation entities data from one project filtered by filters. Filters are additive (all conditions must pass to return subset). @@ -1231,7 +1231,7 @@ def get_archived_representations( names_by_version_ids=None, fields=None ): - """Archived representaion entities data from project with applied filters. + """Archived representation entities data from project with applied filters. Filters are additive (all conditions must pass to return subset). diff --git a/openpype/client/notes.md b/openpype/client/notes.md index a261b86eca..59743892eb 100644 --- a/openpype/client/notes.md +++ b/openpype/client/notes.md @@ -2,7 +2,7 @@ ## Reason Preparation for OpenPype v4 server. Goal is to remove direct mongo calls in code to prepare a little bit for different source of data for code before. To start think about database calls less as mongo calls but more universally. To do so was implemented simple wrapper around database calls to not use pymongo specific code. -Current goal is not to make universal database model which can be easily replaced with any different source of data but to make it close as possible. Current implementation of OpenPype is too tighly connected to pymongo and it's abilities so we're trying to get closer with long term changes that can be used even in current state. +Current goal is not to make universal database model which can be easily replaced with any different source of data but to make it close as possible. Current implementation of OpenPype is too tightly connected to pymongo and it's abilities so we're trying to get closer with long term changes that can be used even in current state. ## Queries Query functions don't use full potential of mongo queries like very specific queries based on subdictionaries or unknown structures. We try to avoid these calls as much as possible because they'll probably won't be available in future. If it's really necessary a new function can be added but only if it's reasonable for overall logic. All query functions were moved to `~/client/entities.py`. Each function has arguments with available filters and possible reduce of returned keys for each entity. @@ -14,7 +14,7 @@ Changes are a little bit complicated. Mongo has many options how update can happ Create operations expect already prepared document data, for that are prepared functions creating skeletal structures of documents (do not fill all required data), except `_id` all data should be right. Existence of entity is not validated so if the same creation operation is send n times it will create the entity n times which can cause issues. ### Update -Update operation require entity id and keys that should be changed, update dictionary must have {"key": value}. If value should be set in nested dictionary the key must have also all subkeys joined with dot `.` (e.g. `{"data": {"fps": 25}}` -> `{"data.fps": 25}`). To simplify update dictionaries were prepared functions which does that for you, their name has template `prepare__update_data` - they work on comparison of previous document and new document. If there is missing function for requested entity type it is because we didn't need it yet and require implementaion. +Update operation require entity id and keys that should be changed, update dictionary must have {"key": value}. If value should be set in nested dictionary the key must have also all subkeys joined with dot `.` (e.g. `{"data": {"fps": 25}}` -> `{"data.fps": 25}`). To simplify update dictionaries were prepared functions which does that for you, their name has template `prepare__update_data` - they work on comparison of previous document and new document. If there is missing function for requested entity type it is because we didn't need it yet and require implementation. ### Delete Delete operation need entity id. Entity will be deleted from mongo. diff --git a/openpype/client/operations.py b/openpype/client/operations.py index fd639c34a7..ef48f2a1c4 100644 --- a/openpype/client/operations.py +++ b/openpype/client/operations.py @@ -368,7 +368,7 @@ def prepare_workfile_info_update_data(old_doc, new_doc, replace=True): class AbstractOperation(object): """Base operation class. - Opration represent a call into database. The call can create, change or + Operation represent a call into database. The call can create, change or remove data. Args: @@ -409,7 +409,7 @@ class AbstractOperation(object): pass def to_data(self): - """Convert opration to data that can be converted to json or others. + """Convert operation to data that can be converted to json or others. Warning: Current state returns ObjectId objects which cannot be parsed by @@ -428,7 +428,7 @@ class AbstractOperation(object): class CreateOperation(AbstractOperation): - """Opeartion to create an entity. + """Operation to create an entity. Args: project_name (str): On which project operation will happen. @@ -485,7 +485,7 @@ class CreateOperation(AbstractOperation): class UpdateOperation(AbstractOperation): - """Opeartion to update an entity. + """Operation to update an entity. Args: project_name (str): On which project operation will happen. @@ -552,7 +552,7 @@ class UpdateOperation(AbstractOperation): class DeleteOperation(AbstractOperation): - """Opeartion to delete an entity. + """Operation to delete an entity. Args: project_name (str): On which project operation will happen. diff --git a/openpype/hooks/pre_create_extra_workdir_folders.py b/openpype/hooks/pre_create_extra_workdir_folders.py index c5af620c87..8856281120 100644 --- a/openpype/hooks/pre_create_extra_workdir_folders.py +++ b/openpype/hooks/pre_create_extra_workdir_folders.py @@ -3,10 +3,13 @@ from openpype.lib import PreLaunchHook from openpype.pipeline.workfile import create_workdir_extra_folders -class AddLastWorkfileToLaunchArgs(PreLaunchHook): - """Add last workfile path to launch arguments. +class CreateWorkdirExtraFolders(PreLaunchHook): + """Create extra folders for the work directory. + + Based on setting `project_settings/global/tools/Workfiles/extra_folders` + profile filtering will decide whether extra folders need to be created in + the work directory. - This is not possible to do for all applications the same way. """ # Execute after workfile template copy diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 2092d5025d..21ec8e7881 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -7,7 +7,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): Nuke is executed "like" python process so it is required to pass `CREATE_NEW_CONSOLE` flag on windows to trigger creation of new console. - At the same time the newly created console won't create it's own stdout + At the same time the newly created console won't create its own stdout and stderr handlers so they should not be redirected to DEVNULL. """ @@ -18,7 +18,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): def execute(self): # Change `creationflags` to CREATE_NEW_CONSOLE - # - on Windows will nuke create new window using it's console + # - on Windows nuke will create new window using its console # Set `stdout` and `stderr` to None so new created console does not # have redirected output to DEVNULL in build self.launch_context.kwargs.update({ diff --git a/openpype/host/dirmap.py b/openpype/host/dirmap.py index 1d084cccad..42bf80ecec 100644 --- a/openpype/host/dirmap.py +++ b/openpype/host/dirmap.py @@ -2,7 +2,7 @@ Idea for current dirmap implementation was used from Maya where is possible to enter source and destination roots and maya will try each found source -in referenced file replace with each destionation paths. First path which +in referenced file replace with each destination paths. First path which exists is used. """ @@ -183,7 +183,7 @@ class HostDirmap(object): project_name, remote_site ) # dirmap has sense only with regular disk provider, in the workfile - # wont be root on cloud or sftp provider + # won't be root on cloud or sftp provider if remote_provider != "local_drive": remote_site = "studio" for root_name, active_site_dir in active_overrides.items(): diff --git a/openpype/host/host.py b/openpype/host/host.py index d2335c0062..630fb873a8 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -18,7 +18,7 @@ class HostBase(object): Compared to 'avalon' concept: What was before considered as functions in host implementation folder. The host implementation should primarily care about adding ability of creation - (mark subsets to be published) and optionaly about referencing published + (mark subsets to be published) and optionally about referencing published representations as containers. Host may need extend some functionality like working with workfiles @@ -129,9 +129,9 @@ class HostBase(object): """Get current context information. This method should be used to get current context of host. Usage of - this method can be crutial for host implementations in DCCs where + this method can be crucial for host implementations in DCCs where can be opened multiple workfiles at one moment and change of context - can't be catched properly. + can't be caught properly. Default implementation returns values from 'legacy_io.Session'. diff --git a/openpype/host/interfaces.py b/openpype/host/interfaces.py index 999aefd254..7c6057acf0 100644 --- a/openpype/host/interfaces.py +++ b/openpype/host/interfaces.py @@ -81,7 +81,7 @@ class ILoadHost: @abstractmethod def get_containers(self): - """Retreive referenced containers from scene. + """Retrieve referenced containers from scene. This can be implemented in hosts where referencing can be used. @@ -191,7 +191,7 @@ class IWorkfileHost: @abstractmethod def get_current_workfile(self): - """Retreive path to current opened file. + """Retrieve path to current opened file. Returns: str: Path to file which is currently opened. @@ -220,8 +220,8 @@ class IWorkfileHost: Default implementation keeps workdir untouched. Warnings: - We must handle this modification with more sofisticated way because - this can't be called out of DCC so opening of last workfile + We must handle this modification with more sophisticated way + because this can't be called out of DCC so opening of last workfile (calculated before DCC is launched) is complicated. Also breaking defined work template is not a good idea. Only place where it's really used and can make sense is Maya. There @@ -302,7 +302,7 @@ class IPublishHost: required methods. Returns: - list[str]: Missing method implementations for new publsher + list[str]: Missing method implementations for new publisher workflow. """ diff --git a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx index 9b211207de..5c1d163439 100644 --- a/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx +++ b/openpype/hosts/aftereffects/api/extension/jsx/hostscript.jsx @@ -504,7 +504,7 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){ * Args: * comp_id (int): id of target composition * item_id (int): FootageItem.id - * found_comp (CompItem, optional): to limit quering if + * found_comp (CompItem, optional): to limit querying if * comp already found previously */ var comp = found_comp || app.project.itemByID(comp_id); diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py index e5d6d9ed89..f094c7fa2a 100644 --- a/openpype/hosts/aftereffects/api/ws_stub.py +++ b/openpype/hosts/aftereffects/api/ws_stub.py @@ -80,7 +80,7 @@ class AfterEffectsServerStub(): Get complete stored JSON with metadata from AE.Metadata.Label field. - It contains containers loaded by any Loader OR instances creted + It contains containers loaded by any Loader OR instances created by Creator. Returns: diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index b1fa13acb9..91cbfe524f 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -24,7 +24,7 @@ from .workio import OpenFileCacher PREVIEW_COLLECTIONS: Dict = dict() # This seems like a good value to keep the Qt app responsive and doesn't slow -# down Blender. At least on macOS I the interace of Blender gets very laggy if +# down Blender. At least on macOS I the interface of Blender gets very laggy if # you make it smaller. TIMER_INTERVAL: float = 0.01 if platform.system() == "Windows" else 0.1 @@ -84,11 +84,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ print("Executing process in main thread") if self.done: diff --git a/openpype/hosts/blender/plugins/publish/extract_playblast.py b/openpype/hosts/blender/plugins/publish/extract_playblast.py index 8dc2f66c22..196e75b8cc 100644 --- a/openpype/hosts/blender/plugins/publish/extract_playblast.py +++ b/openpype/hosts/blender/plugins/publish/extract_playblast.py @@ -50,7 +50,7 @@ class ExtractPlayblast(publish.Extractor): # get isolate objects list isolate = instance.data("isolate", None) - # get ouput path + # get output path stagingdir = self.staging_dir(instance) filename = instance.name path = os.path.join(stagingdir, filename) @@ -116,7 +116,6 @@ class ExtractPlayblast(publish.Extractor): "frameStart": start, "frameEnd": end, "fps": fps, - "preview": True, "tags": tags, "camera_name": camera } diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index 62cebf99ed..96e784875c 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -38,8 +38,9 @@ class CelactionPrelaunchHook(PreLaunchHook): ) path_to_cli = os.path.join(CELACTION_SCRIPTS_DIR, "publish_cli.py") - subproces_args = get_openpype_execute_args("run", path_to_cli) - openpype_executable = subproces_args.pop(0) + subprocess_args = get_openpype_execute_args("run", path_to_cli) + openpype_executable = subprocess_args.pop(0) + workfile_settings = self.get_workfile_settings() winreg.SetValueEx( hKey, @@ -49,20 +50,34 @@ class CelactionPrelaunchHook(PreLaunchHook): openpype_executable ) - parameters = subproces_args + [ - "--currentFile", "*SCENE*", - "--chunk", "*CHUNK*", - "--frameStart", "*START*", - "--frameEnd", "*END*", - "--resolutionWidth", "*X*", - "--resolutionHeight", "*Y*" + # add required arguments for workfile path + parameters = subprocess_args + [ + "--currentFile", "*SCENE*" ] + # Add custom parameters from workfile settings + if "render_chunk" in workfile_settings["submission_overrides"]: + parameters += [ + "--chunk", "*CHUNK*" + ] + if "resolution" in workfile_settings["submission_overrides"]: + parameters += [ + "--resolutionWidth", "*X*", + "--resolutionHeight", "*Y*" + ] + if "frame_range" in workfile_settings["submission_overrides"]: + parameters += [ + "--frameStart", "*START*", + "--frameEnd", "*END*" + ] + winreg.SetValueEx( hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, subprocess.list2cmdline(parameters) ) + self.log.debug(f"__ parameters: \"{parameters}\"") + # setting resolution parameters path_submit = "\\".join([ path_user_settings, "Dialogs", "SubmitOutput" @@ -135,3 +150,6 @@ class CelactionPrelaunchHook(PreLaunchHook): self.log.info(f"Workfile to open: \"{workfile_path}\"") return workfile_path + + def get_workfile_settings(self): + return self.data["project_settings"]["celaction"]["workfile"] diff --git a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py index 43b81b83e7..54dea15dff 100644 --- a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py +++ b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py @@ -39,7 +39,7 @@ class CollectCelactionCliKwargs(pyblish.api.Collector): passing_kwargs[key] = value if missing_kwargs: - raise RuntimeError("Missing arguments {}".format( + self.log.debug("Missing arguments {}".format( ", ".join( [f'"{key}"' for key in missing_kwargs] ) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 6aca5c5ce6..ab713aed84 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -773,7 +773,7 @@ class MediaInfoFile(object): if logger: self.log = logger - # test if `dl_get_media_info` paht exists + # test if `dl_get_media_info` path exists self._validate_media_script_path() # derivate other feed variables @@ -993,7 +993,7 @@ class MediaInfoFile(object): def _validate_media_script_path(self): if not os.path.isfile(self.MEDIA_SCRIPT_PATH): - raise IOError("Media Scirpt does not exist: `{}`".format( + raise IOError("Media Script does not exist: `{}`".format( self.MEDIA_SCRIPT_PATH)) def _generate_media_info_file(self, fpath, feed_ext, feed_dir): diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 3a23389961..d6fbf750ba 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -38,7 +38,7 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) - log.info("OpenPype Flame plug-ins registred ...") + log.info("OpenPype Flame plug-ins registered ...") # register callback for switching publishable pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 983d7486b3..df8c1ac887 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -157,7 +157,7 @@ class CreatorWidget(QtWidgets.QDialog): # convert label text to normal capitalized text with spaces label_text = self.camel_case_split(text) - # assign the new text to lable widget + # assign the new text to label widget label = QtWidgets.QLabel(label_text) label.setObjectName("LineLabel") @@ -345,8 +345,8 @@ class PublishableClip: "track": "sequence", } - # parents search patern - parents_search_patern = r"\{([a-z]*?)\}" + # parents search pattern + parents_search_pattern = r"\{([a-z]*?)\}" # default templates for non-ui use rename_default = False @@ -445,7 +445,7 @@ class PublishableClip: return self.current_segment def _populate_segment_default_data(self): - """ Populate default formating data from segment. """ + """ Populate default formatting data from segment. """ self.current_segment_default_data = { "_folder_": "shots", @@ -538,7 +538,7 @@ class PublishableClip: if not self.index_from_segment: self.count_steps *= self.rename_index - hierarchy_formating_data = {} + hierarchy_formatting_data = {} hierarchy_data = deepcopy(self.hierarchy_data) _data = self.current_segment_default_data.copy() if self.ui_inputs: @@ -552,7 +552,7 @@ class PublishableClip: # mark review layer if self.review_track and ( self.review_track not in self.review_track_default): - # if review layer is defined and not the same as defalut + # if review layer is defined and not the same as default self.review_layer = self.review_track # shot num calculate @@ -578,13 +578,13 @@ class PublishableClip: # fill up pythonic expresisons in hierarchy data for k, _v in hierarchy_data.items(): - hierarchy_formating_data[k] = _v["value"].format(**_data) + hierarchy_formatting_data[k] = _v["value"].format(**_data) else: # if no gui mode then just pass default data - hierarchy_formating_data = hierarchy_data + hierarchy_formatting_data = hierarchy_data tag_hierarchy_data = self._solve_tag_hierarchy_data( - hierarchy_formating_data + hierarchy_formatting_data ) tag_hierarchy_data.update({"heroTrack": True}) @@ -615,27 +615,27 @@ class PublishableClip: # in case track name and subset name is the same then add if self.subset_name == self.track_name: _hero_data["subset"] = self.subset - # assing data to return hierarchy data to tag + # assign data to return hierarchy data to tag tag_hierarchy_data = _hero_data break # add data to return data dict self.marker_data.update(tag_hierarchy_data) - def _solve_tag_hierarchy_data(self, hierarchy_formating_data): + def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve marker data from hierarchy data and templates. """ # fill up clip name and hierarchy keys - hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) - clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + hierarchy_filled = self.hierarchy.format(**hierarchy_formatting_data) + clip_name_filled = self.clip_name.format(**hierarchy_formatting_data) # remove shot from hierarchy data: is not needed anymore - hierarchy_formating_data.pop("shot") + hierarchy_formatting_data.pop("shot") return { "newClipName": clip_name_filled, "hierarchy": hierarchy_filled, "parents": self.parents, - "hierarchyData": hierarchy_formating_data, + "hierarchyData": hierarchy_formatting_data, "subset": self.subset, "family": self.subset_family, "families": [self.family] @@ -650,17 +650,17 @@ class PublishableClip: type ) - # first collect formating data to use for formating template - formating_data = {} + # first collect formatting data to use for formatting template + formatting_data = {} for _k, _v in self.hierarchy_data.items(): value = _v["value"].format( **self.current_segment_default_data) - formating_data[_k] = value + formatting_data[_k] = value return { "entity_type": entity_type, "entity_name": template.format( - **formating_data + **formatting_data ) } @@ -668,9 +668,9 @@ class PublishableClip: """ Create parents and return it in list. """ self.parents = [] - patern = re.compile(self.parents_search_patern) + pattern = re.compile(self.parents_search_pattern) - par_split = [(patern.findall(t).pop(), t) + par_split = [(pattern.findall(t).pop(), t) for t in self.hierarchy.split("/")] for type, template in par_split: @@ -902,22 +902,22 @@ class OpenClipSolver(flib.MediaInfoFile): ): return - formating_data = self._update_formating_data( + formatting_data = self._update_formatting_data( layerName=layer_name, layerUID=layer_uid ) name_obj.text = StringTemplate( self.layer_rename_template - ).format(formating_data) + ).format(formatting_data) - def _update_formating_data(self, **kwargs): - """ Updating formating data for layer rename + def _update_formatting_data(self, **kwargs): + """ Updating formatting data for layer rename Attributes: - key=value (optional): will be included to formating data + key=value (optional): will be included to formatting data as {key: value} Returns: - dict: anatomy context data for formating + dict: anatomy context data for formatting """ self.log.debug(">> self.clip_data: {}".format(self.clip_data)) clip_name_obj = self.clip_data.find("name") diff --git a/openpype/hosts/flame/api/scripts/wiretap_com.py b/openpype/hosts/flame/api/scripts/wiretap_com.py index 4825ff4386..a74172c405 100644 --- a/openpype/hosts/flame/api/scripts/wiretap_com.py +++ b/openpype/hosts/flame/api/scripts/wiretap_com.py @@ -203,7 +203,7 @@ class WireTapCom(object): list: all available volumes in server Rises: - AttributeError: unable to get any volumes childs from server + AttributeError: unable to get any volumes children from server """ root = WireTapNodeHandle(self._server, "/volumes") children_num = WireTapInt(0) diff --git a/openpype/hosts/flame/api/utils.py b/openpype/hosts/flame/api/utils.py index fb8bdee42d..80a5c47e89 100644 --- a/openpype/hosts/flame/api/utils.py +++ b/openpype/hosts/flame/api/utils.py @@ -108,7 +108,7 @@ def _sync_utility_scripts(env=None): shutil.copy2(src, dst) except (PermissionError, FileExistsError) as msg: log.warning( - "Not able to coppy to: `{}`, Problem with: `{}`".format( + "Not able to copy to: `{}`, Problem with: `{}`".format( dst, msg ) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 713daf1031..8034885c47 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -153,7 +153,7 @@ class FlamePrelaunch(PreLaunchHook): def _add_pythonpath(self): pythonpath = self.launch_context.env.get("PYTHONPATH") - # separate it explicity by `;` that is what we use in settings + # separate it explicitly by `;` that is what we use in settings new_pythonpath = self.flame_pythonpath.split(os.pathsep) new_pythonpath += pythonpath.split(os.pathsep) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index 4fb041a4b2..b01354c313 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -209,7 +209,7 @@ class CreateShotClip(opfapi.Creator): "type": "QComboBox", "label": "Subset Name", "target": "ui", - "toolTip": "chose subset name patern, if [ track name ] is selected, name of track layer will be used", # noqa + "toolTip": "chose subset name pattern, if [ track name ] is selected, name of track layer will be used", # noqa "order": 0}, "subsetFamily": { "value": ["plate", "take"], diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 25b31c94a3..dfb2d2b6f0 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -61,9 +61,9 @@ class LoadClip(opfapi.ClipLoader): self.layer_rename_template = self.layer_rename_template.replace( "output", "representation") - formating_data = deepcopy(context["representation"]["context"]) + formatting_data = deepcopy(context["representation"]["context"]) clip_name = StringTemplate(self.clip_name_template).format( - formating_data) + formatting_data) # convert colorspace with ocio to flame mapping # in imageio flame section @@ -88,7 +88,7 @@ class LoadClip(opfapi.ClipLoader): "version": "v{:0>3}".format(version_name), "layer_rename_template": self.layer_rename_template, "layer_rename_patterns": self.layer_rename_patterns, - "context_data": formating_data + "context_data": formatting_data } self.log.debug(pformat( loading_context diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 86bc0f8f1e..5c5a77f0d0 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -58,11 +58,11 @@ class LoadClipBatch(opfapi.ClipLoader): self.layer_rename_template = self.layer_rename_template.replace( "output", "representation") - formating_data = deepcopy(context["representation"]["context"]) - formating_data["batch"] = self.batch.name.get_value() + formatting_data = deepcopy(context["representation"]["context"]) + formatting_data["batch"] = self.batch.name.get_value() clip_name = StringTemplate(self.clip_name_template).format( - formating_data) + formatting_data) # convert colorspace with ocio to flame mapping # in imageio flame section @@ -88,7 +88,7 @@ class LoadClipBatch(opfapi.ClipLoader): "version": "v{:0>3}".format(version_name), "layer_rename_template": self.layer_rename_template, "layer_rename_patterns": self.layer_rename_patterns, - "context_data": formating_data + "context_data": formatting_data } self.log.debug(pformat( loading_context diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 76d48dded2..23fdf5e785 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -203,7 +203,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): self._get_xml_preset_attrs( attributes, split) - # add xml overides resolution to instance data + # add xml overrides resolution to instance data xml_overrides = attributes["xml_overrides"] if xml_overrides.get("width"): attributes.update({ @@ -284,7 +284,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): self.log.debug("__ head: `{}`".format(head)) self.log.debug("__ tail: `{}`".format(tail)) - # HACK: it is here to serve for versions bellow 2021.1 + # HACK: it is here to serve for versions below 2021.1 if not any([head, tail]): retimed_attributes = get_media_range_with_retimes( otio_clip, handle_start, handle_end) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 5082217db0..a7979ab4d5 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -227,7 +227,7 @@ class ExtractSubsetResources(publish.Extractor): self.hide_others( exporting_clip, segment_name, s_track_name) - # change name patern + # change name pattern name_patern_xml = ( "__{}.").format( unique_name) @@ -358,7 +358,7 @@ class ExtractSubsetResources(publish.Extractor): representation_data["stagingDir"] = n_stage_dir files = n_files - # add files to represetation but add + # add files to representation but add # imagesequence as list if ( # first check if path in files is not mov extension diff --git a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py index 4d45f67ded..4f3945bb0f 100644 --- a/openpype/hosts/flame/plugins/publish/integrate_batch_group.py +++ b/openpype/hosts/flame/plugins/publish/integrate_batch_group.py @@ -50,7 +50,7 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin): self._load_clip_to_context(instance, bgroup) def _add_nodes_to_batch_with_links(self, instance, task_data, batch_group): - # get write file node properties > OrederDict because order does mater + # get write file node properties > OrederDict because order does matter write_pref_data = self._get_write_prefs(instance, task_data) batch_nodes = [ diff --git a/openpype/hosts/fusion/api/action.py b/openpype/hosts/fusion/api/action.py index 1750920950..ff5dd14caa 100644 --- a/openpype/hosts/fusion/api/action.py +++ b/openpype/hosts/fusion/api/action.py @@ -6,12 +6,13 @@ from openpype.pipeline.publish import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): - """Select invalid nodes in Maya when plug-in failed. + """Select invalid nodes in Fusion when plug-in failed. To retrieve the invalid nodes this assumes a static `get_invalid()` method is available on the plugin. """ + label = "Select invalid" on = "failed" # This action is only available on a failed plug-in icon = "search" # Icon from Awesome Icon @@ -31,8 +32,10 @@ class SelectInvalidAction(pyblish.api.Action): if isinstance(invalid_nodes, (list, tuple)): invalid.extend(invalid_nodes) else: - self.log.warning("Plug-in returned to be invalid, " - "but has no selectable nodes.") + self.log.warning( + "Plug-in returned to be invalid, " + "but has no selectable nodes." + ) if not invalid: # Assume relevant comp is current comp and clear selection @@ -51,4 +54,6 @@ class SelectInvalidAction(pyblish.api.Action): for tool in invalid: flow.Select(tool, True) names.add(tool.Name) - self.log.info("Selecting invalid tools: %s" % ", ".join(sorted(names))) + self.log.info( + "Selecting invalid tools: %s" % ", ".join(sorted(names)) + ) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 343f5f803a..92f38a64c2 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -6,7 +6,6 @@ from openpype.tools.utils import host_tools from openpype.style import load_stylesheet from openpype.lib import register_event_callback from openpype.hosts.fusion.scripts import ( - set_rendermode, duplicate_with_inputs, ) from openpype.hosts.fusion.api.lib import ( @@ -60,7 +59,6 @@ class OpenPypeMenu(QtWidgets.QWidget): publish_btn = QtWidgets.QPushButton("Publish...", self) manager_btn = QtWidgets.QPushButton("Manage...", self) libload_btn = QtWidgets.QPushButton("Library...", self) - rendermode_btn = QtWidgets.QPushButton("Set render mode...", self) set_framerange_btn = QtWidgets.QPushButton("Set Frame Range", self) set_resolution_btn = QtWidgets.QPushButton("Set Resolution", self) duplicate_with_inputs_btn = QtWidgets.QPushButton( @@ -91,7 +89,6 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(set_framerange_btn) layout.addWidget(set_resolution_btn) - layout.addWidget(rendermode_btn) layout.addSpacing(20) @@ -108,7 +105,6 @@ class OpenPypeMenu(QtWidgets.QWidget): load_btn.clicked.connect(self.on_load_clicked) manager_btn.clicked.connect(self.on_manager_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rendermode_btn.clicked.connect(self.on_rendermode_clicked) duplicate_with_inputs_btn.clicked.connect( self.on_duplicate_with_inputs_clicked ) @@ -162,15 +158,6 @@ class OpenPypeMenu(QtWidgets.QWidget): def on_libload_clicked(self): host_tools.show_library_loader() - def on_rendermode_clicked(self): - if self.render_mode_widget is None: - window = set_rendermode.SetRenderMode() - window.setStyleSheet(load_stylesheet()) - window.show() - self.render_mode_widget = window - else: - self.render_mode_widget.show() - def on_duplicate_with_inputs_clicked(self): duplicate_with_inputs.duplicate_with_input_connections() diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index e581bac20f..56085b0a06 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -4,29 +4,34 @@ import qtawesome from openpype.hosts.fusion.api import ( get_current_comp, - comp_lock_and_undo_chunk + comp_lock_and_undo_chunk, ) -from openpype.lib import BoolDef +from openpype.lib import ( + BoolDef, + EnumDef, +) from openpype.pipeline import ( legacy_io, Creator, - CreatedInstance + CreatedInstance, +) +from openpype.client import ( + get_asset_by_name, ) -from openpype.client import get_asset_by_name class CreateSaver(Creator): identifier = "io.openpype.creators.fusion.saver" - name = "saver" - label = "Saver" + label = "Render (saver)" + name = "render" family = "render" - default_variants = ["Main"] - + default_variants = ["Main", "Mask"] description = "Fusion Saver to generate image sequence" - def create(self, subset_name, instance_data, pre_create_data): + instance_attributes = ["reviewable"] + def create(self, subset_name, instance_data, pre_create_data): # TODO: Add pre_create attributes to choose file format? file_format = "OpenEXRFormat" @@ -58,7 +63,8 @@ class CreateSaver(Creator): family=self.family, subset_name=subset_name, data=instance_data, - creator=self) + creator=self, + ) # Insert the transient data instance.transient_data["tool"] = saver @@ -68,11 +74,9 @@ class CreateSaver(Creator): return instance def collect_instances(self): - comp = get_current_comp() tools = comp.GetToolList(False, "Saver").values() for tool in tools: - data = self.get_managed_tool_data(tool) if not data: data = self._collect_unmanaged_saver(tool) @@ -90,7 +94,6 @@ class CreateSaver(Creator): def update_instances(self, update_list): for created_inst, _changes in update_list: - new_data = created_inst.data_to_store() tool = created_inst.transient_data["tool"] self._update_tool_with_data(tool, new_data) @@ -139,7 +142,6 @@ class CreateSaver(Creator): tool.SetAttrs({"TOOLS_Name": subset}) def _collect_unmanaged_saver(self, tool): - # TODO: this should not be done this way - this should actually # get the data as stored on the tool explicitly (however) # that would disallow any 'regular saver' to be collected @@ -153,8 +155,7 @@ class CreateSaver(Creator): asset = legacy_io.Session["AVALON_ASSET"] task = legacy_io.Session["AVALON_TASK"] - asset_doc = get_asset_by_name(project_name=project, - asset_name=asset) + asset_doc = get_asset_by_name(project_name=project, asset_name=asset) path = tool["Clip"][comp.TIME_UNDEFINED] fname = os.path.basename(path) @@ -178,21 +179,20 @@ class CreateSaver(Creator): "variant": variant, "active": not passthrough, "family": self.family, - # Unique identifier for instance and this creator "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier + "creator_identifier": self.identifier, } def get_managed_tool_data(self, tool): """Return data of the tool if it matches creator identifier""" - data = tool.GetData('openpype') + data = tool.GetData("openpype") if not isinstance(data, dict): return required = { "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier + "creator_identifier": self.identifier, } for key, value in required.items(): if key not in data or data[key] != value: @@ -205,11 +205,40 @@ class CreateSaver(Creator): return data - def get_instance_attr_defs(self): - return [ - BoolDef( - "review", - default=True, - label="Review" - ) + def get_pre_create_attr_defs(self): + """Settings for create page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), ] + return attr_defs + + def get_instance_attr_defs(self): + """Settings for publish page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), + ] + return attr_defs + + # These functions below should be moved to another file + # so it can be used by other plugins. plugin.py ? + + def _get_render_target_enum(self): + rendering_targets = { + "local": "Local machine rendering", + "frames": "Use existing frames", + } + if "farm_rendering" in self.instance_attributes: + rendering_targets["farm"] = "Farm rendering" + + return EnumDef( + "render_target", items=rendering_targets, label="Render target" + ) + + def _get_reviewable_bool(self): + return BoolDef( + "review", + default=("reviewable" in self.instance_attributes), + label="Review", + ) diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index 3b14f022e5..f83ab433ee 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -72,8 +72,7 @@ class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): return # Include handles - handles = version_data.get("handles", 0) - start -= handles - end += handles + start -= version_data.get("handleStart", 0) + end += version_data.get("handleEnd", 0) lib.update_frame_range(start, end) diff --git a/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py b/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py new file mode 100644 index 0000000000..0ba777629f --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_expected_frames.py @@ -0,0 +1,50 @@ +import pyblish.api +from openpype.pipeline import publish +import os + + +class CollectFusionExpectedFrames( + pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin +): + """Collect all frames needed to publish expected frames""" + + order = pyblish.api.CollectorOrder + 0.5 + label = "Collect Expected Frames" + hosts = ["fusion"] + families = ["render"] + + def process(self, instance): + context = instance.context + + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + basename = os.path.basename(path) + head, ext = os.path.splitext(basename) + files = [ + f"{head}{str(frame).zfill(4)}{ext}" + for frame in range(frame_start, frame_end + 1) + ] + repre = { + "name": ext[1:], + "ext": ext[1:], + "frameStart": f"%0{len(str(frame_end))}d" % frame_start, + "files": files, + "stagingDir": output_dir, + } + + self.set_representation_colorspace( + representation=repre, + context=context, + ) + + # review representation + if instance.data.get("review", False): + repre["tags"] = ["review"] + + # add the repre to the instance + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(repre) diff --git a/openpype/hosts/fusion/plugins/publish/collect_render_target.py b/openpype/hosts/fusion/plugins/publish/collect_render_target.py deleted file mode 100644 index 39017f32e0..0000000000 --- a/openpype/hosts/fusion/plugins/publish/collect_render_target.py +++ /dev/null @@ -1,44 +0,0 @@ -import pyblish.api - - -class CollectFusionRenderMode(pyblish.api.InstancePlugin): - """Collect current comp's render Mode - - Options: - local - farm - - Note that this value is set for each comp separately. When you save the - comp this information will be stored in that file. If for some reason the - available tool does not visualize which render mode is set for the - current comp, please run the following line in the console (Py2) - - comp.GetData("openpype.rendermode") - - This will return the name of the current render mode as seen above under - Options. - - """ - - order = pyblish.api.CollectorOrder + 0.4 - label = "Collect Render Mode" - hosts = ["fusion"] - families = ["render"] - - def process(self, instance): - """Collect all image sequence tools""" - options = ["local", "farm"] - - comp = instance.context.data.get("currentComp") - if not comp: - raise RuntimeError("No comp previously collected, unable to " - "retrieve Fusion version.") - - rendermode = comp.GetData("openpype.rendermode") or "local" - assert rendermode in options, "Must be supported render mode" - - self.log.info("Render mode: {0}".format(rendermode)) - - # Append family - family = "render.{0}".format(rendermode) - instance.data["families"].append(family) diff --git a/openpype/hosts/fusion/plugins/publish/collect_renders.py b/openpype/hosts/fusion/plugins/publish/collect_renders.py new file mode 100644 index 0000000000..7f38e68447 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_renders.py @@ -0,0 +1,25 @@ +import pyblish.api + + +class CollectFusionRenders(pyblish.api.InstancePlugin): + """Collect current saver node's render Mode + + Options: + local (Render locally) + frames (Use existing frames) + + """ + + order = pyblish.api.CollectorOrder + 0.4 + label = "Collect Renders" + hosts = ["fusion"] + families = ["render"] + + def process(self, instance): + render_target = instance.data["render_target"] + family = instance.data["family"] + + # add targeted family to families + instance.data["families"].append( + "{}.{}".format(family, render_target) + ) diff --git a/openpype/hosts/fusion/plugins/publish/extract_render_local.py b/openpype/hosts/fusion/plugins/publish/extract_render_local.py new file mode 100644 index 0000000000..5a0140c525 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/extract_render_local.py @@ -0,0 +1,109 @@ +import logging +import contextlib +import pyblish.api +from openpype.hosts.fusion.api import comp_lock_and_undo_chunk + + +log = logging.getLogger(__name__) + + +@contextlib.contextmanager +def enabled_savers(comp, savers): + """Enable only the `savers` in Comp during the context. + + Any Saver tool in the passed composition that is not in the savers list + will be set to passthrough during the context. + + Args: + comp (object): Fusion composition object. + savers (list): List of Saver tool objects. + + """ + passthrough_key = "TOOLB_PassThrough" + original_states = {} + enabled_save_names = {saver.Name for saver in savers} + try: + all_savers = comp.GetToolList(False, "Saver").values() + for saver in all_savers: + original_state = saver.GetAttrs()[passthrough_key] + original_states[saver] = original_state + + # The passthrough state we want to set (passthrough != enabled) + state = saver.Name not in enabled_save_names + if state != original_state: + saver.SetAttrs({passthrough_key: state}) + yield + finally: + for saver, original_state in original_states.items(): + saver.SetAttrs({"TOOLB_PassThrough": original_state}) + + +class FusionRenderLocal(pyblish.api.InstancePlugin): + """Render the current Fusion composition locally.""" + + order = pyblish.api.ExtractorOrder - 0.2 + label = "Render Local" + hosts = ["fusion"] + families = ["render.local"] + + def process(self, instance): + context = instance.context + + # Start render + self.render_once(context) + + # Log render status + self.log.info( + "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( + nm=instance.data["name"], + ast=instance.data["asset"], + tsk=instance.data["task"], + ) + ) + + def render_once(self, context): + """Render context comp only once, even with more render instances""" + + # This plug-in assumes all render nodes get rendered at the same time + # to speed up the rendering. The check below makes sure that we only + # execute the rendering once and not for each instance. + key = f"__hasRun{self.__class__.__name__}" + + savers_to_render = [ + # Get the saver tool from the instance + instance[0] for instance in context if + # Only active instances + instance.data.get("publish", True) and + # Only render.local instances + "render.local" in instance.data["families"] + ] + + if key not in context.data: + # We initialize as false to indicate it wasn't successful yet + # so we can keep track of whether Fusion succeeded + context.data[key] = False + + current_comp = context.data["currentComp"] + frame_start = context.data["frameStartHandle"] + frame_end = context.data["frameEndHandle"] + + self.log.info("Starting Fusion render") + self.log.info(f"Start frame: {frame_start}") + self.log.info(f"End frame: {frame_end}") + saver_names = ", ".join(saver.Name for saver in savers_to_render) + self.log.info(f"Rendering tools: {saver_names}") + + with comp_lock_and_undo_chunk(current_comp): + with enabled_savers(current_comp, savers_to_render): + result = current_comp.Render( + { + "Start": frame_start, + "End": frame_end, + "Wait": True, + } + ) + + context.data[key] = bool(result) + + if context.data[key] is False: + raise RuntimeError("Comp render failed") diff --git a/openpype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py deleted file mode 100644 index 7d5f1a40c7..0000000000 --- a/openpype/hosts/fusion/plugins/publish/render_local.py +++ /dev/null @@ -1,100 +0,0 @@ -import os -import pyblish.api -from openpype.pipeline import publish -from openpype.hosts.fusion.api import comp_lock_and_undo_chunk - - -class Fusionlocal(pyblish.api.InstancePlugin, - publish.ColormanagedPyblishPluginMixin): - """Render the current Fusion composition locally. - - Extract the result of savers by starting a comp render - This will run the local render of Fusion. - - """ - - order = pyblish.api.ExtractorOrder - 0.1 - label = "Render Local" - hosts = ["fusion"] - families = ["render.local"] - - def process(self, instance): - context = instance.context - - # Start render - self.render_once(context) - - # Log render status - self.log.info( - "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( - nm=instance.data["name"], - ast=instance.data["asset"], - tsk=instance.data["task"], - ) - ) - - frame_start = context.data["frameStartHandle"] - frame_end = context.data["frameEndHandle"] - path = instance.data["path"] - output_dir = instance.data["outputDir"] - - basename = os.path.basename(path) - head, ext = os.path.splitext(basename) - files = [ - f"{head}{str(frame).zfill(4)}{ext}" - for frame in range(frame_start, frame_end + 1) - ] - repre = { - "name": ext[1:], - "ext": ext[1:], - "frameStart": f"%0{len(str(frame_end))}d" % frame_start, - "files": files, - "stagingDir": output_dir, - } - - self.set_representation_colorspace( - representation=repre, - context=context, - ) - - if "representations" not in instance.data: - instance.data["representations"] = [] - instance.data["representations"].append(repre) - - # review representation - if instance.data.get("review", False): - repre["tags"] = ["review", "ftrackreview"] - - def render_once(self, context): - """Render context comp only once, even with more render instances""" - - # This plug-in assumes all render nodes get rendered at the same time - # to speed up the rendering. The check below makes sure that we only - # execute the rendering once and not for each instance. - key = f"__hasRun{self.__class__.__name__}" - if key not in context.data: - # We initialize as false to indicate it wasn't successful yet - # so we can keep track of whether Fusion succeeded - context.data[key] = False - - current_comp = context.data["currentComp"] - frame_start = context.data["frameStartHandle"] - frame_end = context.data["frameEndHandle"] - - self.log.info("Starting Fusion render") - self.log.info(f"Start frame: {frame_start}") - self.log.info(f"End frame: {frame_end}") - - with comp_lock_and_undo_chunk(current_comp): - result = current_comp.Render( - { - "Start": frame_start, - "End": frame_end, - "Wait": True, - } - ) - - context.data[key] = bool(result) - - if context.data[key] is False: - raise RuntimeError("Comp render failed") diff --git a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index ba943abacb..8a91f23578 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -14,22 +14,19 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - actions = [RepairAction] label = "Validate Create Folder Checked" families = ["render"] hosts = ["fusion"] - actions = [SelectInvalidAction] + actions = [RepairAction, SelectInvalidAction] @classmethod def get_invalid(cls, instance): - active = instance.data.get("active", instance.data.get("publish")) - if not active: - return [] - tool = instance[0] create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: - cls.log.error("%s has Create Folder turned off" % instance[0].Name) + cls.log.error( + "%s has Create Folder turned off" % instance[0].Name + ) return [tool] def process(self, instance): @@ -37,7 +34,8 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): if invalid: raise PublishValidationError( "Found Saver with Create Folder During Render checked off", - title=self.label) + title=self.label, + ) @classmethod def repair(cls, instance): diff --git a/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py new file mode 100644 index 0000000000..c208b8ef15 --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/validate_expected_frames_existence.py @@ -0,0 +1,78 @@ +import os +import pyblish.api + +from openpype.pipeline.publish import RepairAction +from openpype.pipeline import PublishValidationError + +from openpype.hosts.fusion.api.action import SelectInvalidAction + + +class ValidateLocalFramesExistence(pyblish.api.InstancePlugin): + """Checks if files for savers that's set + to publish expected frames exists + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Expected Frames Exists" + families = ["render"] + hosts = ["fusion"] + actions = [RepairAction, SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance, non_existing_frames=None): + if non_existing_frames is None: + non_existing_frames = [] + + if instance.data.get("render_target") == "frames": + tool = instance[0] + + frame_start = instance.data["frameStart"] + frame_end = instance.data["frameEnd"] + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + basename = os.path.basename(path) + head, ext = os.path.splitext(basename) + files = [ + f"{head}{str(frame).zfill(4)}{ext}" + for frame in range(frame_start, frame_end + 1) + ] + + for file in files: + if not os.path.exists(os.path.join(output_dir, file)): + cls.log.error( + f"Missing file: {os.path.join(output_dir, file)}" + ) + non_existing_frames.append(file) + + if len(non_existing_frames) > 0: + cls.log.error(f"Some of {tool.Name}'s files does not exist") + return [tool] + + def process(self, instance): + non_existing_frames = [] + invalid = self.get_invalid(instance, non_existing_frames) + if invalid: + raise PublishValidationError( + "{} is set to publish existing frames but " + "some frames are missing. " + "The missing file(s) are:\n\n{}".format( + invalid[0].Name, + "\n\n".join(non_existing_frames), + ), + title=self.label, + ) + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + if invalid: + tool = invalid[0] + + # Change render target to local to render locally + tool.SetData("openpype.creator_attributes.render_target", "local") + + cls.log.info( + f"Reload the publisher and {tool.Name} " + "will be set to render locally" + ) diff --git a/openpype/hosts/fusion/scripts/set_rendermode.py b/openpype/hosts/fusion/scripts/set_rendermode.py deleted file mode 100644 index 9d2bfef310..0000000000 --- a/openpype/hosts/fusion/scripts/set_rendermode.py +++ /dev/null @@ -1,112 +0,0 @@ -from qtpy import QtWidgets -import qtawesome -from openpype.hosts.fusion.api import get_current_comp - - -_help = {"local": "Render the comp on your own machine and publish " - "it from that the destination folder", - "farm": "Submit a Fusion render job to a Render farm to use all other" - " computers and add a publish job"} - - -class SetRenderMode(QtWidgets.QWidget): - - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - - self._comp = get_current_comp() - self._comp_name = self._get_comp_name() - - self.setWindowTitle("Set Render Mode") - self.setFixedSize(300, 175) - - layout = QtWidgets.QVBoxLayout() - - # region comp info - comp_info_layout = QtWidgets.QHBoxLayout() - - update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh", - color="white"), "") - update_btn.setFixedWidth(25) - update_btn.setFixedHeight(25) - - comp_information = QtWidgets.QLineEdit() - comp_information.setEnabled(False) - - comp_info_layout.addWidget(comp_information) - comp_info_layout.addWidget(update_btn) - # endregion comp info - - # region modes - mode_options = QtWidgets.QComboBox() - mode_options.addItems(_help.keys()) - - mode_information = QtWidgets.QTextEdit() - mode_information.setReadOnly(True) - # endregion modes - - accept_btn = QtWidgets.QPushButton("Accept") - - layout.addLayout(comp_info_layout) - layout.addWidget(mode_options) - layout.addWidget(mode_information) - layout.addWidget(accept_btn) - - self.setLayout(layout) - - self.comp_information = comp_information - self.update_btn = update_btn - - self.mode_options = mode_options - self.mode_information = mode_information - - self.accept_btn = accept_btn - - self.connections() - self.update() - - # Force updated render mode help text - self._update_rendermode_info() - - def connections(self): - """Build connections between code and buttons""" - - self.update_btn.clicked.connect(self.update) - self.accept_btn.clicked.connect(self._set_comp_rendermode) - self.mode_options.currentIndexChanged.connect( - self._update_rendermode_info) - - def update(self): - """Update all information in the UI""" - - self._comp = get_current_comp() - self._comp_name = self._get_comp_name() - self.comp_information.setText(self._comp_name) - - # Update current comp settings - mode = self._get_comp_rendermode() - index = self.mode_options.findText(mode) - self.mode_options.setCurrentIndex(index) - - def _update_rendermode_info(self): - rendermode = self.mode_options.currentText() - self.mode_information.setText(_help[rendermode]) - - def _get_comp_name(self): - return self._comp.GetAttrs("COMPS_Name") - - def _get_comp_rendermode(self): - return self._comp.GetData("openpype.rendermode") or "local" - - def _set_comp_rendermode(self): - rendermode = self.mode_options.currentText() - self._comp.SetData("openpype.rendermode", rendermode) - - self._comp.Print("Updated render mode to '%s'\n" % rendermode) - self.hide() - - def _validation(self): - ui_mode = self.mode_options.currentText() - comp_mode = self._get_comp_rendermode() - - return comp_mode == ui_mode diff --git a/openpype/hosts/harmony/api/README.md b/openpype/hosts/harmony/api/README.md index b39f900886..12f21f551a 100644 --- a/openpype/hosts/harmony/api/README.md +++ b/openpype/hosts/harmony/api/README.md @@ -432,11 +432,11 @@ copy_files = """function copyFile(srcFilename, dstFilename) import_files = """function %s_import_files() { - var PNGTransparencyMode = 0; // Premultiplied wih Black - var TGATransparencyMode = 0; // Premultiplied wih Black - var SGITransparencyMode = 0; // Premultiplied wih Black + var PNGTransparencyMode = 0; // Premultiplied with Black + var TGATransparencyMode = 0; // Premultiplied with Black + var SGITransparencyMode = 0; // Premultiplied with Black var LayeredPSDTransparencyMode = 1; // Straight - var FlatPSDTransparencyMode = 2; // Premultiplied wih White + var FlatPSDTransparencyMode = 2; // Premultiplied with White function getUniqueColumnName( column_prefix ) { diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index e7cd555332..a284a6ec5c 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -142,10 +142,10 @@ function Client() { }; /** - * Process recieved request. This will eval recieved function and produce + * Process received request. This will eval received function and produce * results. * @function - * @param {object} request - recieved request JSON + * @param {object} request - received request JSON * @return {object} result of evaled function. */ self.processRequest = function(request) { @@ -245,7 +245,7 @@ function Client() { var request = JSON.parse(to_parse); var mid = request.message_id; // self.logDebug('[' + mid + '] - Request: ' + '\n' + JSON.stringify(request)); - self.logDebug('[' + mid + '] Recieved.'); + self.logDebug('[' + mid + '] Received.'); request.result = self.processRequest(request); self.logDebug('[' + mid + '] Processing done.'); @@ -286,8 +286,8 @@ function Client() { /** Harmony 21.1 doesn't have QDataStream anymore. This means we aren't able to write bytes into QByteArray so we had - modify how content lenght is sent do the server. - Content lenght is sent as string of 8 char convertible into integer + modify how content length is sent do the server. + Content length is sent as string of 8 char convertible into integer (instead of 0x00000001[4 bytes] > "000000001"[8 bytes]) */ var codec_name = new QByteArray().append("UTF-8"); @@ -476,6 +476,25 @@ function start() { action.triggered.connect(self.onSubsetManage); } + /** + * Set scene settings from DB to the scene + */ + self.onSetSceneSettings = function() { + app.avalonClient.send( + { + "module": "openpype.hosts.harmony.api", + "method": "ensure_scene_settings", + "args": [] + }, + false + ); + }; + // add Set Scene Settings + if (app.avalonMenu == null) { + action = menu.addAction('Set Scene Settings...'); + action.triggered.connect(self.onSetSceneSettings); + } + /** * Show Experimental dialog */ diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py index e1e77bfbee..b009dabb44 100644 --- a/openpype/hosts/harmony/api/lib.py +++ b/openpype/hosts/harmony/api/lib.py @@ -242,9 +242,15 @@ def launch_zip_file(filepath): print(f"Localizing {filepath}") temp_path = get_local_harmony_path(filepath) + scene_name = os.path.basename(temp_path) + if os.path.exists(os.path.join(temp_path, scene_name)): + # unzipped with duplicated scene_name + temp_path = os.path.join(temp_path, scene_name) + scene_path = os.path.join( - temp_path, os.path.basename(temp_path) + ".xstage" + temp_path, scene_name + ".xstage" ) + unzip = False if os.path.exists(scene_path): # Check remote scene is newer than local. @@ -262,6 +268,10 @@ def launch_zip_file(filepath): with _ZipFile(filepath, "r") as zip_ref: zip_ref.extractall(temp_path) + if os.path.exists(os.path.join(temp_path, scene_name)): + # unzipped with duplicated scene_name + temp_path = os.path.join(temp_path, scene_name) + # Close existing scene. if ProcessContext.pid: os.kill(ProcessContext.pid, signal.SIGTERM) @@ -309,7 +319,7 @@ def launch_zip_file(filepath): ) if not os.path.exists(scene_path): - print("error: cannot determine scene file") + print("error: cannot determine scene file {}".format(scene_path)) ProcessContext.server.stop() return @@ -394,7 +404,7 @@ def get_scene_data(): "function": "AvalonHarmony.getSceneData" })["result"] except json.decoder.JSONDecodeError: - # Means no sceen metadata has been made before. + # Means no scene metadata has been made before. return {} except KeyError: # Means no existing scene metadata has been made. @@ -465,7 +475,7 @@ def imprint(node_id, data, remove=False): Example: >>> from openpype.hosts.harmony.api import lib >>> node = "Top/Display" - >>> data = {"str": "someting", "int": 1, "float": 0.32, "bool": True} + >>> data = {"str": "something", "int": 1, "float": 0.32, "bool": True} >>> lib.imprint(layer, data) """ scene_data = get_scene_data() @@ -550,7 +560,7 @@ def save_scene(): method prevents this double request and safely saves the scene. """ - # Need to turn off the backgound watcher else the communication with + # Need to turn off the background watcher else the communication with # the server gets spammed with two requests at the same time. scene_path = send( {"function": "AvalonHarmony.saveScene"})["result"] diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 686770b64e..285ee806a1 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -142,7 +142,7 @@ def application_launch(event): harmony.send({"script": script}) inject_avalon_js() - ensure_scene_settings() + # ensure_scene_settings() check_inventory() diff --git a/openpype/hosts/harmony/api/server.py b/openpype/hosts/harmony/api/server.py index ecf339d91b..04048e5c84 100644 --- a/openpype/hosts/harmony/api/server.py +++ b/openpype/hosts/harmony/api/server.py @@ -61,7 +61,7 @@ class Server(threading.Thread): "module": (str), # Module of method. "method" (str), # Name of method in module. "args" (list), # Arguments to pass to method. - "kwargs" (dict), # Keywork arguments to pass to method. + "kwargs" (dict), # Keyword arguments to pass to method. "reply" (bool), # Optional wait for method completion. } """ diff --git a/openpype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py index c29864bb28..38b09902c1 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -25,8 +25,9 @@ class ExtractRender(pyblish.api.InstancePlugin): application_path = instance.context.data.get("applicationPath") scene_path = instance.context.data.get("scenePath") frame_rate = instance.context.data.get("frameRate") - frame_start = instance.context.data.get("frameStart") - frame_end = instance.context.data.get("frameEnd") + # real value from timeline + frame_start = instance.context.data.get("frameStartHandle") + frame_end = instance.context.data.get("frameEndHandle") audio_path = instance.context.data.get("audioPath") if audio_path and os.path.exists(audio_path): @@ -55,9 +56,13 @@ class ExtractRender(pyblish.api.InstancePlugin): # Execute rendering. Ignoring error cause Harmony returns error code # always. - self.log.info(f"running [ {application_path} -batch {scene_path}") + + args = [application_path, "-batch", + "-frames", str(frame_start), str(frame_end), + "-scene", scene_path] + self.log.info(f"running [ {application_path} {' '.join(args)}") proc = subprocess.Popen( - [application_path, "-batch", scene_path], + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE diff --git a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index 936533abd6..6e4c6955e4 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -60,7 +60,8 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): # which is available on 'context.data["assetEntity"]' # - the same approach can be used in 'ValidateSceneSettingsRepair' expected_settings = harmony.get_asset_settings() - self.log.info("scene settings from DB:".format(expected_settings)) + self.log.info("scene settings from DB:{}".format(expected_settings)) + expected_settings.pop("entityType") # not useful for the validation expected_settings = _update_frames(dict.copy(expected_settings)) expected_settings["frameEndHandle"] = expected_settings["frameEnd"] +\ @@ -68,21 +69,32 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): if (any(re.search(pattern, os.getenv('AVALON_TASK')) for pattern in self.skip_resolution_check)): + self.log.info("Skipping resolution check because of " + "task name and pattern {}".format( + self.skip_resolution_check)) expected_settings.pop("resolutionWidth") expected_settings.pop("resolutionHeight") - entity_type = expected_settings.get("entityType") - if (any(re.search(pattern, entity_type) + if (any(re.search(pattern, os.getenv('AVALON_TASK')) for pattern in self.skip_timelines_check)): + self.log.info("Skipping frames check because of " + "task name and pattern {}".format( + self.skip_timelines_check)) expected_settings.pop('frameStart', None) expected_settings.pop('frameEnd', None) - - expected_settings.pop("entityType") # not useful after the check + expected_settings.pop('frameStartHandle', None) + expected_settings.pop('frameEndHandle', None) asset_name = instance.context.data['anatomyData']['asset'] if any(re.search(pattern, asset_name) for pattern in self.frame_check_filter): - expected_settings.pop("frameEnd") + self.log.info("Skipping frames check because of " + "task name and pattern {}".format( + self.frame_check_filter)) + expected_settings.pop('frameStart', None) + expected_settings.pop('frameEnd', None) + expected_settings.pop('frameStartHandle', None) + expected_settings.pop('frameEndHandle', None) # handle case where ftrack uses only two decimal places # 23.976023976023978 vs. 23.98 @@ -99,6 +111,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): "frameEnd": instance.context.data["frameEnd"], "handleStart": instance.context.data.get("handleStart"), "handleEnd": instance.context.data.get("handleEnd"), + "frameStartHandle": instance.context.data.get("frameStartHandle"), "frameEndHandle": instance.context.data.get("frameEndHandle"), "resolutionWidth": instance.context.data.get("resolutionWidth"), "resolutionHeight": instance.context.data.get("resolutionHeight"), diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/README.md b/openpype/hosts/harmony/vendor/OpenHarmony/README.md index 7c77fbfcfa..064afca86c 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/README.md +++ b/openpype/hosts/harmony/vendor/OpenHarmony/README.md @@ -6,7 +6,7 @@ Ever tried to make a simple script for toonboom Harmony, then got stumped by the Toonboom Harmony is a very powerful software, with hundreds of functions and tools, and it unlocks a great amount of possibilities for animation studios around the globe. And... being the produce of the hard work of a small team forced to prioritise, it can also be a bit rustic at times! -We are users at heart, animators and riggers, who just want to interact with the software as simply as possible. Simplicity is at the heart of the design of openHarmony. But we also are developpers, and we made the library for people like us who can't resist tweaking the software and bend it in all possible ways, and are looking for powerful functions to help them do it. +We are users at heart, animators and riggers, who just want to interact with the software as simply as possible. Simplicity is at the heart of the design of openHarmony. But we also are developers, and we made the library for people like us who can't resist tweaking the software and bend it in all possible ways, and are looking for powerful functions to help them do it. This library's aim is to create a more direct way to interact with Toonboom through scripts, by providing a more intuitive way to access its elements, and help with the cumbersome and repetitive tasks as well as help unlock untapped potential in its many available systems. So we can go from having to do things like this: @@ -78,7 +78,7 @@ All you have to do is call : ```javascript include("openHarmony.js"); ``` -at the beggining of your script. +at the beginning of your script. You can ask your users to download their copy of the library and store it alongside, or bundle it as you wish as long as you include the license file provided on this repository. @@ -129,7 +129,7 @@ Check that the environment variable `LIB_OPENHARMONY_PATH` is set correctly to t ## How to add openHarmony to vscode intellisense for autocompletion Although not fully supported, you can get most of the autocompletion features to work by adding the following lines to a `jsconfig.json` file placed at the root of your working folder. -The paths need to be relative which means the openHarmony source code must be placed directly in your developping environnement. +The paths need to be relative which means the openHarmony source code must be placed directly in your developping environment. For example, if your working folder contains the openHarmony source in a folder called `OpenHarmony` and your working scripts in a folder called `myScripts`, place the `jsconfig.json` file at the root of the folder and add these lines to the file: diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony.js index 530c0902c5..ae65d32a2b 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -78,7 +78,7 @@ * $.log("hello"); // prints out a message to the MessageLog. * var myPoint = new $.oPoint(0,0,0); // create a new class instance from an openHarmony class. * - * // function members of the $ objects get published to the global scope, which means $ can be ommited + * // function members of the $ objects get published to the global scope, which means $ can be omitted * * log("hello"); * var myPoint = new oPoint(0,0,0); // This is all valid @@ -118,7 +118,7 @@ Object.defineProperty( $, "directory", { /** - * Wether Harmony is run with the interface or simply from command line + * Whether Harmony is run with the interface or simply from command line */ Object.defineProperty( $, "batchMode", { get: function(){ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_actions.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_actions.js index ad1efc91be..a54f74e147 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_actions.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_actions.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -67,7 +67,7 @@ * @hideconstructor * @namespace * @example - * // To check wether an action is available, call the synthax: + * // To check whether an action is available, call the synthax: * Action.validate (, ); * * // To launch an action, call the synthax: diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_application.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_application.js index 9e9acb766c..5809cee694 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_application.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_application.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -409,7 +409,7 @@ $.oApp.prototype.getToolByName = function(toolName){ /** - * returns the list of stencils useable by the specified tool + * returns the list of stencils usable by the specified tool * @param {$.oTool} tool the tool object we want valid stencils for * @return {$.oStencil[]} the list of stencils compatible with the specified tool */ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js index d4d2d791ae..fa044d5b74 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -338,7 +338,7 @@ Object.defineProperty($.oAttribute.prototype, "useSeparate", { * Returns the default value of the attribute for most keywords * @name $.oAttribute#defaultValue * @type {bool} - * @todo switch the implentation to types? + * @todo switch the implementation to types? * @example * // to reset an attribute to its default value: * // (mostly used for position/angle/skew parameters of pegs and drawing nodes) @@ -449,7 +449,7 @@ $.oAttribute.prototype.getLinkedColumns = function(){ /** * Recursively sets an attribute to the same value as another. Both must have the same keyword. - * @param {bool} [duplicateColumns=false] In the case that the attribute has a column, wether to duplicate the column before linking + * @param {bool} [duplicateColumns=false] In the case that the attribute has a column, whether to duplicate the column before linking * @private */ $.oAttribute.prototype.setToAttributeValue = function(attributeToCopy, duplicateColumns){ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js index c98e194539..1d359f93c4 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_color.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_color.js index 7726be6cd6..ff06688e66 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_color.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_color.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -158,7 +158,7 @@ $.oColorValue.prototype.fromColorString = function (hexString){ /** - * Uses a color integer (used in backdrops) and parses the INT; applies the RGBA components of the INT to thos oColorValue + * Uses a color integer (used in backdrops) and parses the INT; applies the RGBA components of the INT to the oColorValue * @param { int } colorInt 24 bit-shifted integer containing RGBA values */ $.oColorValue.prototype.parseColorFromInt = function(colorInt){ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_column.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_column.js index 1b73c7943e..f73309049e 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_column.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_column.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_database.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_database.js index 73964c5c38..5440b92875 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_database.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_database.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js index a6e16ecb78..3ab78b87d6 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js @@ -5,7 +5,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -17,7 +17,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -250,7 +250,7 @@ $.oDialog.prototype.prompt = function( labelText, title, prefilledText){ /** * Prompts with a file selector window * @param {string} [text="Select a file:"] The title of the confirmation dialog. - * @param {string} [filter="*"] The filter for the file type and/or file name that can be selected. Accepts wildcard charater "*". + * @param {string} [filter="*"] The filter for the file type and/or file name that can be selected. Accepts wildcard character "*". * @param {string} [getExisting=true] Whether to select an existing file or a save location * @param {string} [acceptMultiple=false] Whether or not selecting more than one file is ok. Is ignored if getExisting is falses. * @param {string} [startDirectory] The directory showed at the opening of the dialog. @@ -327,14 +327,14 @@ $.oDialog.prototype.browseForFolder = function(text, startDirectory){ * @constructor * @classdesc An simple progress dialog to display the progress of a task. * To react to the user clicking the cancel button, connect a function to $.oProgressDialog.canceled() signal. - * When $.batchmode is true, the progress will be outputed as a "Progress : value/range" string to the Harmony stdout. + * When $.batchmode is true, the progress will be outputted as a "Progress : value/range" string to the Harmony stdout. * @param {string} [labelText] The text displayed above the progress bar. * @param {string} [range=100] The maximum value that represents a full progress bar. * @param {string} [title] The title of the dialog * @param {bool} [show=false] Whether to immediately show the dialog. * * @property {bool} wasCanceled Whether the progress bar was cancelled. - * @property {$.oSignal} canceled A Signal emited when the dialog is canceled. Can be connected to a callback. + * @property {$.oSignal} canceled A Signal emitted when the dialog is canceled. Can be connected to a callback. */ $.oProgressDialog = function( labelText, range, title, show ){ if (typeof title === 'undefined') var title = "Progress"; @@ -608,7 +608,7 @@ $.oPieMenu = function( name, widgets, show, minAngle, maxAngle, radius, position this.maxAngle = maxAngle; this.globalCenter = position; - // how wide outisde the icons is the slice drawn + // how wide outside the icons is the slice drawn this._circleMargin = 30; // set these values before calling show() to customize the menu appearance @@ -974,7 +974,7 @@ $.oPieMenu.prototype.getMenuRadius = function(){ var _minRadius = UiLoader.dpiScale(30); var _speed = 10; // the higher the value, the slower the progression - // hyperbolic tangent function to determin the radius + // hyperbolic tangent function to determine the radius var exp = Math.exp(2*itemsNumber/_speed); var _radius = ((exp-1)/(exp+1))*_maxRadius+_minRadius; @@ -1383,7 +1383,7 @@ $.oActionButton.prototype.activate = function(){ * This class is a subclass of QPushButton and all the methods from that class are available to modify this button. * @param {string} paletteName The name of the palette that contains the color * @param {string} colorName The name of the color (if more than one is present, will pick the first match) - * @param {bool} showName Wether to display the name of the color on the button + * @param {bool} showName Whether to display the name of the color on the button * @param {QWidget} parent The parent QWidget for the button. Automatically set during initialisation of the menu. * */ @@ -1437,7 +1437,7 @@ $.oColorButton.prototype.activate = function(){ * @name $.oScriptButton * @constructor * @classdescription This subclass of QPushButton provides an easy way to create a button for a widget that will launch a function from another script file.
- * The buttons created this way automatically load the icon named after the script if it finds one named like the funtion in a script-icons folder next to the script file.
+ * The buttons created this way automatically load the icon named after the script if it finds one named like the function in a script-icons folder next to the script file.
* It will also automatically set the callback to lanch the function from the script.
* This class is a subclass of QPushButton and all the methods from that class are available to modify this button. * @param {string} scriptFile The path to the script file that will be launched diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js index bad735f237..6f2bc19c0c 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -426,7 +426,7 @@ Object.defineProperty($.oDrawing.prototype, 'drawingData', { /** * Import a given file into an existing drawing. * @param {$.oFile} file The path to the file - * @param {bool} [convertToTvg=false] Wether to convert the bitmap to the tvg format (this doesn't vectorise the drawing) + * @param {bool} [convertToTvg=false] Whether to convert the bitmap to the tvg format (this doesn't vectorise the drawing) * * @return { $.oFile } the oFile object pointing to the drawing file after being it has been imported into the element folder. */ @@ -878,8 +878,8 @@ $.oArtLayer.prototype.drawCircle = function(center, radius, lineStyle, fillStyle * @param {$.oVertex[]} path an array of $.oVertex objects that describe a path. * @param {$.oLineStyle} [lineStyle] the line style to draw with. (By default, will use the current stencil selection) * @param {$.oFillStyle} [fillStyle] the fill information for the path. (By default, will use the current palette selection) - * @param {bool} [polygon] Wether bezier handles should be created for the points in the path (ignores "onCurve" properties of oVertex from path) - * @param {bool} [createUnderneath] Wether the new shape will appear on top or underneath the contents of the layer. (not working yet) + * @param {bool} [polygon] Whether bezier handles should be created for the points in the path (ignores "onCurve" properties of oVertex from path) + * @param {bool} [createUnderneath] Whether the new shape will appear on top or underneath the contents of the layer. (not working yet) */ $.oArtLayer.prototype.drawShape = function(path, lineStyle, fillStyle, polygon, createUnderneath){ if (typeof fillStyle === 'undefined') var fillStyle = new this.$.oFillStyle(); @@ -959,7 +959,7 @@ $.oArtLayer.prototype.drawContour = function(path, fillStyle){ * @param {float} width the width of the rectangle. * @param {float} height the height of the rectangle. * @param {$.oLineStyle} lineStyle a line style to use for the rectangle stroke. - * @param {$.oFillStyle} fillStyle a fill style to use for the rectange fill. + * @param {$.oFillStyle} fillStyle a fill style to use for the rectangle fill. * @returns {$.oShape} the shape containing the added stroke. */ $.oArtLayer.prototype.drawRectangle = function(x, y, width, height, lineStyle, fillStyle){ @@ -1514,7 +1514,7 @@ Object.defineProperty($.oStroke.prototype, "path", { /** - * The oVertex that are on the stroke (Bezier handles exluded.) + * The oVertex that are on the stroke (Bezier handles excluded.) * The first is repeated at the last position when the stroke is closed. * @name $.oStroke#points * @type {$.oVertex[]} @@ -1583,7 +1583,7 @@ Object.defineProperty($.oStroke.prototype, "style", { /** - * wether the stroke is a closed shape. + * whether the stroke is a closed shape. * @name $.oStroke#closed * @type {bool} */ @@ -1919,7 +1919,7 @@ $.oContour.prototype.toString = function(){ * @constructor * @classdesc * The $.oVertex class represents a single control point on a stroke. This class is used to get the index of the point in the stroke path sequence, as well as its position as a float along the stroke's length. - * The onCurve property describes wether this control point is a bezier handle or a point on the curve. + * The onCurve property describes whether this control point is a bezier handle or a point on the curve. * * @param {$.oStroke} stroke the stroke that this vertex belongs to * @param {float} x the x coordinate of the vertex, in drawing space diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_element.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_element.js index ed50d6e50b..b64c8169ec 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_element.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_element.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_file.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_file.js index 14dafa3b63..50e4b0d475 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_file.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_file.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -509,7 +509,7 @@ Object.defineProperty($.oFile.prototype, 'fullName', { /** - * The name of the file without extenstion. + * The name of the file without extension. * @name $.oFile#name * @type {string} */ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_frame.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_frame.js index 37bdede02a..e1d1dd7fad 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_frame.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_frame.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -263,7 +263,7 @@ Object.defineProperty($.oFrame.prototype, 'duration', { return _sceneLength; } - // walk up the frames of the scene to the next keyFrame to determin duration + // walk up the frames of the scene to the next keyFrame to determine duration var _frames = this.column.frames for (var i=this.frameNumber+1; i<_sceneLength; i++){ if (_frames[i].isKeyframe) return _frames[i].frameNumber - _startFrame; @@ -426,7 +426,7 @@ Object.defineProperty($.oFrame.prototype, 'velocity', { * easeIn : a $.oPoint object representing the left handle for bezier columns, or a {point, ease} object for ease columns. * easeOut : a $.oPoint object representing the left handle for bezier columns, or a {point, ease} object for ease columns. * continuity : the type of bezier used by the point. - * constant : wether the frame is interpolated or a held value. + * constant : whether the frame is interpolated or a held value. * @name $.oFrame#ease * @type {oPoint/object} */ @@ -520,7 +520,7 @@ Object.defineProperty($.oFrame.prototype, 'easeOut', { /** - * Determines the frame's continuity setting. Can take the values "CORNER", (two independant bezier handles on each side), "SMOOTH"(handles are aligned) or "STRAIGHT" (no handles and in straight lines). + * Determines the frame's continuity setting. Can take the values "CORNER", (two independent bezier handles on each side), "SMOOTH"(handles are aligned) or "STRAIGHT" (no handles and in straight lines). * @name $.oFrame#continuity * @type {string} */ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_list.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_list.js index 9d02b1c2aa..63a5c0eeb8 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_list.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_list.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -516,5 +516,5 @@ Object.defineProperty($.oList.prototype, 'toString', { -//Needs all filtering, limiting. mapping, pop, concat, join, ect +//Needs all filtering, limiting. mapping, pop, concat, join, etc //Speed up by finessing the way it extends and tracks the enumerable properties. \ No newline at end of file diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_math.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_math.js index c0d4ca99a7..06bfb51f30 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_math.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_math.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -193,7 +193,7 @@ $.oPoint.prototype.pointSubtract = function( sub_pt ){ /** * Subtracts the point to the coordinates of the current oPoint and returns a new oPoint with the result. * @param {$.oPoint} point The point to subtract to this point. - * @returns {$.oPoint} a new independant oPoint. + * @returns {$.oPoint} a new independent oPoint. */ $.oPoint.prototype.subtractPoint = function( point ){ var x = this.x - point.x; @@ -298,9 +298,9 @@ $.oPoint.prototype.convertToWorldspace = function(){ /** - * Linearily Interpolate between this (0.0) and the provided point (1.0) + * Linearly Interpolate between this (0.0) and the provided point (1.0) * @param {$.oPoint} point The target point at 100% - * @param {double} perc 0-1.0 value to linearily interp + * @param {double} perc 0-1.0 value to linearly interp * * @return: { $.oPoint } The interpolated value. */ @@ -410,9 +410,9 @@ $.oBox.prototype.include = function(box){ /** - * Checks wether the box contains another $.oBox. + * Checks whether the box contains another $.oBox. * @param {$.oBox} box The $.oBox to check for. - * @param {bool} [partial=false] wether to accept partially contained boxes. + * @param {bool} [partial=false] whether to accept partially contained boxes. */ $.oBox.prototype.contains = function(box, partial){ if (typeof partial === 'undefined') var partial = false; @@ -537,7 +537,7 @@ $.oMatrix.prototype.toString = function(){ * @classdesc The $.oVector is a replacement for the Vector3d objects of Harmony. * @param {float} x a x coordinate for this vector. * @param {float} y a y coordinate for this vector. - * @param {float} [z=0] a z coordinate for this vector. If ommited, will be set to 0 and vector will be 2D. + * @param {float} [z=0] a z coordinate for this vector. If omitted, will be set to 0 and vector will be 2D. */ $.oVector = function(x, y, z){ if (typeof z === "undefined" || isNaN(z)) var z = 0; diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js index c19e6d12f4..29afeb522c 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_misc.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_misc.js index fec5d32816..6ef75f5560 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_misc.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_misc.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -54,7 +54,7 @@ /** - * The $.oUtils helper class -- providing generic utilities. Doesn't need instanciation. + * The $.oUtils helper class -- providing generic utilities. Doesn't need instantiation. * @classdesc $.oUtils utility Class */ $.oUtils = function(){ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_network.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_network.js index a4476d7591..2a6aa3519a 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_network.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_network.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -87,7 +87,7 @@ $.oNetwork = function( ){ * @param {function} callback_func Providing a callback function prevents blocking, and will respond on this function. The callback function is in form func( results ){} * @param {bool} use_json In the event of a JSON api, this will return an object converted from the returned JSON. * - * @return: {string/object} The resulting object/string from the query -- otherwise a bool as false when an error occured.. + * @return: {string/object} The resulting object/string from the query -- otherwise a bool as false when an error occurred.. */ $.oNetwork.prototype.webQuery = function ( address, callback_func, use_json ){ if (typeof callback_func === 'undefined') var callback_func = false; @@ -272,7 +272,7 @@ $.oNetwork.prototype.webQuery = function ( address, callback_func, use_json ){ * @param {function} path The local file path to save the download. * @param {bool} replace Replace the file if it exists. * - * @return: {string/object} The resulting object/string from the query -- otherwise a bool as false when an error occured.. + * @return: {string/object} The resulting object/string from the query -- otherwise a bool as false when an error occurred.. */ $.oNetwork.prototype.downloadSingle = function ( address, path, replace ){ if (typeof replace === 'undefined') var replace = false; diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_node.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_node.js index 5590d7b7e9..deb1854357 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_node.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_node.js @@ -4,7 +4,7 @@ // openHarmony Library // // -// Developped by Mathieu Chaptel, Chris Fourney +// Developed by Mathieu Chaptel, Chris Fourney // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -562,7 +562,7 @@ Object.defineProperty($.oNode.prototype, 'height', { /** - * The list of oNodeLinks objects descibing the connections to the inport of this node, in order of inport. + * The list of oNodeLinks objects describing the connections to the inport of this node, in order of inport. * @name $.oNode#inLinks * @readonly * @deprecated returns $.oNodeLink instances but $.oLink is preferred. Use oNode.getInLinks() instead. @@ -658,7 +658,7 @@ Object.defineProperty($.oNode.prototype, 'outPorts', { /** - * The list of oNodeLinks objects descibing the connections to the outports of this node, in order of outport. + * The list of oNodeLinks objects describing the connections to the outports of this node, in order of outport. * @name $.oNode#outLinks * @readonly * @type {$.oNodeLink[]} @@ -1666,7 +1666,7 @@ $.oNode.prototype.refreshAttributes = function( ){ * It represents peg nodes in the scene. * @constructor * @augments $.oNode - * @classdesc Peg Moudle Class + * @classdesc Peg Module Class * @param {string} path Path to the node in the network. * @param {oScene} oSceneObject Access to the oScene object of the DOM. */ @@ -1886,7 +1886,7 @@ $.oDrawingNode.prototype.getDrawingAtFrame = function(frameNumber){ /** - * Gets the list of palettes containing colors used by a drawing node. This only gets palettes with the first occurence of the colors. + * Gets the list of palettes containing colors used by a drawing node. This only gets palettes with the first occurrence of the colors. * @return {$.oPalette[]} The palettes that contain the color IDs used by the drawings of the node. */ $.oDrawingNode.prototype.getUsedPalettes = function(){ @@ -1968,7 +1968,7 @@ $.oDrawingNode.prototype.unlinkPalette = function(oPaletteObject){ * Duplicates a node by creating an independent copy. * @param {string} [newName] The new name for the duplicated node. * @param {oPoint} [newPosition] The new position for the duplicated node. - * @param {bool} [duplicateElement] Wether to also duplicate the element. + * @param {bool} [duplicateElement] Whether to also duplicate the element. */ $.oDrawingNode.prototype.duplicate = function(newName, newPosition, duplicateElement){ if (typeof newPosition === 'undefined') var newPosition = this.nodePosition; @@ -2464,7 +2464,7 @@ $.oGroupNode.prototype.getNodeByName = function(name){ * Returns all the nodes of a certain type in the group. * Pass a value to recurse to look into the groups as well. * @param {string} typeName The type of the nodes. - * @param {bool} recurse Wether to look inside the groups. + * @param {bool} recurse Whether to look inside the groups. * * @return {$.oNode[]} The nodes found. */ @@ -2626,7 +2626,7 @@ $.oGroupNode.prototype.orderNodeView = function(recurse){ * * peg.linkOutNode(drawingNode); * - * //through all this we didn't specify nodePosition parameters so we'll sort evertything at once + * //through all this we didn't specify nodePosition parameters so we'll sort everything at once * * sceneRoot.orderNodeView(); * @@ -3333,7 +3333,7 @@ $.oGroupNode.prototype.importImageAsTVG = function(path, alignment, nodePosition * imports an image sequence as a node into the current group. * @param {$.oFile[]} imagePaths a list of paths to the images to import (can pass a list of strings or $.oFile) * @param {number} [exposureLength=1] the number of frames each drawing should be exposed at. If set to 0/false, each drawing will use the numbering suffix of the file to set its frame. - * @param {boolean} [convertToTvg=false] wether to convert the files to tvg during import + * @param {boolean} [convertToTvg=false] whether to convert the files to tvg during import * @param {string} [alignment="ASIS"] the alignment to apply to the node * @param {$.oPoint} [nodePosition] the position of the node in the nodeview * @@ -3346,7 +3346,7 @@ $.oGroupNode.prototype.importImageSequence = function(imagePaths, exposureLength if (typeof extendScene === 'undefined') var extendScene = false; - // match anything but capture trailing numbers and separates punctuation preceeding it + // match anything but capture trailing numbers and separates punctuation preceding it var numberingRe = /(.*?)([\W_]+)?(\d*)$/i; // sanitize imagePaths diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js index 279a871691..07a4d147da 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, Chris Fourney... +// Developed by Mathieu Chaptel, Chris Fourney... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -174,7 +174,7 @@ Object.defineProperty($.oNodeLink.prototype, 'outNode', { return; } - this.apply(); // do we really want to apply everytime we set? + this.apply(); // do we really want to apply every time we set? } }); @@ -198,7 +198,7 @@ Object.defineProperty($.oNodeLink.prototype, 'inNode', { return; } - this.apply(); // do we really want to apply everytime we set? + this.apply(); // do we really want to apply every time we set? } }); @@ -222,7 +222,7 @@ Object.defineProperty($.oNodeLink.prototype, 'outPort', { return; } - this.apply(); // do we really want to apply everytime we set? + this.apply(); // do we really want to apply every time we set? } }); @@ -256,7 +256,7 @@ Object.defineProperty($.oNodeLink.prototype, 'inPort', { return; } - this.apply(); // do we really want to apply everytime we set? + this.apply(); // do we really want to apply every time we set? } }); @@ -983,7 +983,7 @@ $.oNodeLink.prototype.validate = function ( ) { * @return {bool} Whether the connection is a valid connection that exists currently in the node system. */ $.oNodeLink.prototype.validateUpwards = function( inport, outportProvided ) { - //IN THE EVENT OUTNODE WASNT PROVIDED. + //IN THE EVENT OUTNODE WASN'T PROVIDED. this.path = this.findInputPath( this._inNode, inport, [] ); if( !this.path || this.path.length == 0 ){ return false; @@ -1173,7 +1173,7 @@ Object.defineProperty($.oLink.prototype, 'outPort', { /** - * The index of the link comming out of the out-port. + * The index of the link coming out of the out-port. *
In the event this value wasn't known by the link object but the link is actually connected, the correct value will be found. * @name $.oLink#outLink * @readonly @@ -1323,7 +1323,7 @@ $.oLink.prototype.getValidLink = function(createOutPorts, createInPorts){ /** - * Attemps to connect a link. Will guess the ports if not provided. + * Attempts to connect a link. Will guess the ports if not provided. * @return {bool} */ $.oLink.prototype.connect = function(){ @@ -1623,11 +1623,11 @@ $.oLinkPath.prototype.findExistingPath = function(){ /** - * Gets a link object from two nodes that can be succesfully connected. Provide port numbers if there are specific requirements to match. If a link already exists, it will be returned. + * Gets a link object from two nodes that can be successfully connected. Provide port numbers if there are specific requirements to match. If a link already exists, it will be returned. * @param {$.oNode} start The node from which the link originates. * @param {$.oNode} end The node at which the link ends. - * @param {int} [outPort] A prefered out-port for the link to use. - * @param {int} [inPort] A prefered in-port for the link to use. + * @param {int} [outPort] A preferred out-port for the link to use. + * @param {int} [inPort] A preferred in-port for the link to use. * * @return {$.oLink} the valid $.oLink object. Returns null if no such link could be created (for example if the node's in-port is already linked) */ diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony_tools.js b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony_tools.js index 57d4a63e96..9014929fc4 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony_tools.js +++ b/openpype/hosts/harmony/vendor/OpenHarmony/openHarmony_tools.js @@ -4,7 +4,7 @@ // openHarmony Library v0.01 // // -// Developped by Mathieu Chaptel, ... +// Developed by Mathieu Chaptel, ... // // // This library is an open source implementation of a Document Object Model @@ -16,7 +16,7 @@ // and by hiding the heavy lifting required by the official API. // // This library is provided as is and is a work in progress. As such, not every -// function has been implemented or is garanteed to work. Feel free to contribute +// function has been implemented or is guaranteed to work. Feel free to contribute // improvements to its official github. If you do make sure you follow the provided // template and naming conventions and document your new methods properly. // @@ -212,7 +212,7 @@ function openHarmony_toolInstaller(){ //---------------------------------------------- - //-- GET THE FILE CONTENTS IN A DIRCTORY ON GIT + //-- GET THE FILE CONTENTS IN A DIRECTORY ON GIT this.recurse_files = function( contents, arr_files ){ with( context.$.global ){ try{ @@ -501,7 +501,7 @@ function openHarmony_toolInstaller(){ var download_item = item["download_url"]; var query = $.network.webQuery( download_item, false, false ); if( query ){ - //INSTALL TYPES ARE script, package, ect. + //INSTALL TYPES ARE script, package, etc. if( install_types[ m.install_cache[ item["url"] ] ] ){ m.installLabel.text = install_types[ m.install_cache[ item["url"] ] ]; diff --git a/openpype/hosts/harmony/vendor/OpenHarmony/package.json b/openpype/hosts/harmony/vendor/OpenHarmony/package.json index c62ecbc9d8..7a535cdcf6 100644 --- a/openpype/hosts/harmony/vendor/OpenHarmony/package.json +++ b/openpype/hosts/harmony/vendor/OpenHarmony/package.json @@ -1,7 +1,7 @@ { "name": "openharmony", "version": "0.0.1", - "description": "An Open Source Imlementation of a Document Object Model for the Toonboom Harmony scripting interface", + "description": "An Open Source Implementation of a Document Object Model for the Toonboom Harmony scripting interface", "main": "openHarmony.js", "scripts": { "test": "$", diff --git a/openpype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py index 1fa40c9f74..b95c0fe1d7 100644 --- a/openpype/hosts/hiero/api/__init__.py +++ b/openpype/hosts/hiero/api/__init__.py @@ -108,7 +108,7 @@ __all__ = [ "apply_colorspace_project", "apply_colorspace_clips", "get_sequence_pattern_and_padding", - # depricated + # deprecated "get_track_item_pype_tag", "set_track_item_pype_tag", "get_track_item_pype_data", diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index bbd1edc14a..0d4368529f 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -1221,7 +1221,7 @@ def set_track_color(track_item, color): def check_inventory_versions(track_items=None): """ - Actual version color idetifier of Loaded containers + Actual version color identifier of Loaded containers Check all track items and filter only Loader nodes for its version. It will get all versions from database @@ -1249,10 +1249,10 @@ def check_inventory_versions(track_items=None): project_name = legacy_io.active_project() filter_result = filter_containers(containers, project_name) for container in filter_result.latest: - set_track_color(container["_item"], clip_color) + set_track_color(container["_item"], clip_color_last) for container in filter_result.outdated: - set_track_color(container["_item"], clip_color_last) + set_track_color(container["_item"], clip_color) def selection_changed_timeline(event): diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 4ab73e7d19..d88aeac810 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -193,8 +193,8 @@ def parse_container(item, validate=True): return # convert the data to list and validate them for _, obj_data in _data.items(): - cotnainer = data_to_container(item, obj_data) - return_list.append(cotnainer) + container = data_to_container(item, obj_data) + return_list.append(container) return return_list else: _data = lib.get_trackitem_openpype_data(item) diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 07457db1a4..a3f8a6c524 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -146,6 +146,8 @@ class CreatorWidget(QtWidgets.QDialog): return " ".join([str(m.group(0)).capitalize() for m in matches]) def create_row(self, layout, type, text, **kwargs): + value_keys = ["setText", "setCheckState", "setValue", "setChecked"] + # get type attribute from qwidgets attr = getattr(QtWidgets, type) @@ -167,14 +169,27 @@ class CreatorWidget(QtWidgets.QDialog): # assign the created attribute to variable item = getattr(self, attr_name) + + # set attributes to item which are not values for func, val in kwargs.items(): + if func in value_keys: + continue + if getattr(item, func): + log.debug("Setting {} to {}".format(func, val)) func_attr = getattr(item, func) if isinstance(val, tuple): func_attr(*val) else: func_attr(val) + # set values to item + for value_item in value_keys: + if value_item not in kwargs: + continue + if getattr(item, value_item): + getattr(item, value_item)(kwargs[value_item]) + # add to layout layout.addRow(label, item) @@ -276,8 +291,11 @@ class CreatorWidget(QtWidgets.QDialog): elif v["type"] == "QSpinBox": data[k]["value"] = self.create_row( content_layout, "QSpinBox", v["label"], - setValue=v["value"], setMinimum=0, + setValue=v["value"], + setDisplayIntegerBase=10000, + setRange=(0, 99999), setMinimum=0, setMaximum=100000, setToolTip=tool_tip) + return data @@ -393,7 +411,7 @@ class ClipLoader: self.with_handles = options.get("handles") or bool( options.get("handles") is True) # try to get value from options or evaluate key value for `load_how` - self.sequencial_load = options.get("sequencially") or bool( + self.sequencial_load = options.get("sequentially") or bool( "Sequentially in order" in options.get("load_how", "")) # try to get value from options or evaluate key value for `load_to` self.new_sequence = options.get("newSequence") or bool( @@ -818,7 +836,7 @@ class PublishClip: # increasing steps by index of rename iteration self.count_steps *= self.rename_index - hierarchy_formating_data = {} + hierarchy_formatting_data = {} hierarchy_data = deepcopy(self.hierarchy_data) _data = self.track_item_default_data.copy() if self.ui_inputs: @@ -853,13 +871,13 @@ class PublishClip: # fill up pythonic expresisons in hierarchy data for k, _v in hierarchy_data.items(): - hierarchy_formating_data[k] = _v["value"].format(**_data) + hierarchy_formatting_data[k] = _v["value"].format(**_data) else: # if no gui mode then just pass default data - hierarchy_formating_data = hierarchy_data + hierarchy_formatting_data = hierarchy_data tag_hierarchy_data = self._solve_tag_hierarchy_data( - hierarchy_formating_data + hierarchy_formatting_data ) tag_hierarchy_data.update({"heroTrack": True}) @@ -887,20 +905,20 @@ class PublishClip: # add data to return data dict self.tag_data.update(tag_hierarchy_data) - def _solve_tag_hierarchy_data(self, hierarchy_formating_data): + def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve tag data from hierarchy data and templates. """ # fill up clip name and hierarchy keys - hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) - clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + hierarchy_filled = self.hierarchy.format(**hierarchy_formatting_data) + clip_name_filled = self.clip_name.format(**hierarchy_formatting_data) # remove shot from hierarchy data: is not needed anymore - hierarchy_formating_data.pop("shot") + hierarchy_formatting_data.pop("shot") return { "newClipName": clip_name_filled, "hierarchy": hierarchy_filled, "parents": self.parents, - "hierarchyData": hierarchy_formating_data, + "hierarchyData": hierarchy_formatting_data, "subset": self.subset, "family": self.subset_family, "families": [self.data["family"]] @@ -916,16 +934,16 @@ class PublishClip: ) # first collect formatting data to use for formatting template - formating_data = {} + formatting_data = {} for _k, _v in self.hierarchy_data.items(): value = _v["value"].format( **self.track_item_default_data) - formating_data[_k] = value + formatting_data[_k] = value return { "entity_type": entity_type, "entity_name": template.format( - **formating_data + **formatting_data ) } diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 13f5a62ec3..f19dc64992 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -479,23 +479,13 @@ def reset_framerange(): frame_start = asset_data.get("frameStart") frame_end = asset_data.get("frameEnd") - # Backwards compatibility - if frame_start is None or frame_end is None: - frame_start = asset_data.get("edit_in") - frame_end = asset_data.get("edit_out") if frame_start is None or frame_end is None: log.warning("No edit information found for %s" % asset_name) return - handles = asset_data.get("handles") or 0 - handle_start = asset_data.get("handleStart") - if handle_start is None: - handle_start = handles - - handle_end = asset_data.get("handleEnd") - if handle_end is None: - handle_end = handles + handle_start = asset_data.get("handleStart", 0) + handle_end = asset_data.get("handleEnd", 0) frame_start -= int(handle_start) frame_end += int(handle_end) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 9793679b45..45e2f8f87f 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -144,13 +144,10 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): """ obj_network = hou.node("/obj") - op_ctx = obj_network.createNode("null", node_name="OpenPypeContext") - - # A null in houdini by default comes with content inside to visualize - # the null. However since we explicitly want to hide the node lets - # remove the content and disable the display flag of the node - for node in op_ctx.children(): - node.destroy() + op_ctx = obj_network.createNode("subnet", + node_name="OpenPypeContext", + run_init_scripts=False, + load_contents=False) op_ctx.moveToGoodPosition() op_ctx.setBuiltExplicitly(False) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index f0985973a6..340a7f0770 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -60,7 +60,7 @@ class Creator(LegacyCreator): def process(self): instance = super(CreateEpicNode, self, process() - # Set paramaters for Alembic node + # Set parameters for Alembic node instance.setParms( {"sop_path": "$HIP/%s.abc" % self.nodes[0]} ) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index ebd668e9e4..6e0f367f62 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -69,7 +69,7 @@ def generate_shelves(): mandatory_attributes = {'label', 'script'} for tool_definition in shelf_definition.get('tools_list'): - # We verify that the name and script attibutes of the tool + # We verify that the name and script attributes of the tool # are set if not all( tool_definition[key] for key in mandatory_attributes diff --git a/openpype/hosts/houdini/plugins/create/convert_legacy.py b/openpype/hosts/houdini/plugins/create/convert_legacy.py index 4b8041b4f5..e549c9dc26 100644 --- a/openpype/hosts/houdini/plugins/create/convert_legacy.py +++ b/openpype/hosts/houdini/plugins/create/convert_legacy.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Convertor for legacy Houdini subsets.""" +"""Converter for legacy Houdini subsets.""" from openpype.pipeline.create.creator_plugins import SubsetConvertorPlugin from openpype.hosts.houdini.api.lib import imprint @@ -7,7 +7,7 @@ from openpype.hosts.houdini.api.lib import imprint class HoudiniLegacyConvertor(SubsetConvertorPlugin): """Find and convert any legacy subsets in the scene. - This Convertor will find all legacy subsets in the scene and will + This Converter will find all legacy subsets in the scene and will transform them to the current system. Since the old subsets doesn't retain any information about their original creators, the only mapping we can do is based on their families. diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index 9cca07fdc7..caf679f98b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -1,7 +1,6 @@ import os import hou -from openpype.pipeline import legacy_io import pyblish.api @@ -11,7 +10,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder - 0.01 label = "Houdini Current File" hosts = ["houdini"] - family = ["workfile"] + families = ["workfile"] def process(self, instance): """Inject the current working file""" @@ -21,7 +20,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): # By default, Houdini will even point a new scene to a path. # However if the file is not saved at all and does not exist, # we assume the user never set it. - filepath = "" + current_file = "" elif os.path.basename(current_file) == "untitled.hip": # Due to even a new file being called 'untitled.hip' we are unable diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 4fb750d91b..ad9a450cad 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -6,6 +6,11 @@ from pymxs import runtime as rt from typing import Union import contextlib +from openpype.pipeline.context_tools import ( + get_current_project_asset, + get_current_project +) + JSON_PREFIX = "JSON::" @@ -157,6 +162,105 @@ def get_multipass_setting(project_setting=None): ["multipass"]) +def set_scene_resolution(width: int, height: int): + """Set the render resolution + + Args: + width(int): value of the width + height(int): value of the height + + Returns: + None + + """ + rt.renderWidth = width + rt.renderHeight = height + + +def reset_scene_resolution(): + """Apply the scene resolution from the project definition + + scene resolution can be overwritten by an asset if the asset.data contains + any information regarding scene resolution . + Returns: + None + """ + data = ["data.resolutionWidth", "data.resolutionHeight"] + project_resolution = get_current_project(fields=data) + project_resolution_data = project_resolution["data"] + asset_resolution = get_current_project_asset(fields=data) + asset_resolution_data = asset_resolution["data"] + # Set project resolution + project_width = int(project_resolution_data.get("resolutionWidth", 1920)) + project_height = int(project_resolution_data.get("resolutionHeight", 1080)) + width = int(asset_resolution_data.get("resolutionWidth", project_width)) + height = int(asset_resolution_data.get("resolutionHeight", project_height)) + + set_scene_resolution(width, height) + + +def get_frame_range() -> dict: + """Get the current assets frame range and handles. + + Returns: + dict: with frame start, frame end, handle start, handle end. + """ + # Set frame start/end + asset = get_current_project_asset() + frame_start = asset["data"].get("frameStart") + frame_end = asset["data"].get("frameEnd") + + if frame_start is None or frame_end is None: + return + + handle_start = asset["data"].get("handleStart", 0) + handle_end = asset["data"].get("handleEnd", 0) + return { + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end + } + + +def reset_frame_range(fps: bool = True): + """Set frame range to current asset. + This is part of 3dsmax documentation: + + animationRange: A System Global variable which lets you get and + set an Interval value that defines the start and end frames + of the Active Time Segment. + frameRate: A System Global variable which lets you get + and set an Integer value that defines the current + scene frame rate in frames-per-second. + """ + if fps: + data_fps = get_current_project(fields=["data.fps"]) + fps_number = float(data_fps["data"]["fps"]) + rt.frameRate = fps_number + frame_range = get_frame_range() + frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) + frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) + frange_cmd = f"animationRange = interval {frame_start} {frame_end}" + rt.execute(frange_cmd) + + +def set_context_setting(): + """Apply the project settings from the project definition + + Settings can be overwritten by an asset if the asset.data contains + any information regarding those settings. + + Examples of settings: + frame range + resolution + + Returns: + None + """ + reset_scene_resolution() + + def get_max_version(): """ Args: diff --git a/openpype/hosts/max/api/menu.py b/openpype/hosts/max/api/menu.py index 5c273b49b4..066cc90039 100644 --- a/openpype/hosts/max/api/menu.py +++ b/openpype/hosts/max/api/menu.py @@ -4,6 +4,7 @@ from qtpy import QtWidgets, QtCore from pymxs import runtime as rt from openpype.tools.utils import host_tools +from openpype.hosts.max.api import lib class OpenPypeMenu(object): @@ -107,6 +108,17 @@ class OpenPypeMenu(object): workfiles_action = QtWidgets.QAction("Work Files...", openpype_menu) workfiles_action.triggered.connect(self.workfiles_callback) openpype_menu.addAction(workfiles_action) + + openpype_menu.addSeparator() + + res_action = QtWidgets.QAction("Set Resolution", openpype_menu) + res_action.triggered.connect(self.resolution_callback) + openpype_menu.addAction(res_action) + + frame_action = QtWidgets.QAction("Set Frame Range", openpype_menu) + frame_action.triggered.connect(self.frame_range_callback) + openpype_menu.addAction(frame_action) + return openpype_menu def load_callback(self): @@ -128,3 +140,11 @@ class OpenPypeMenu(object): def workfiles_callback(self): """Callback to show Workfiles tool.""" host_tools.show_workfiles(parent=self.main_widget) + + def resolution_callback(self): + """Callback to reset scene resolution""" + return lib.reset_scene_resolution() + + def frame_range_callback(self): + """Callback to reset frame range""" + return lib.reset_frame_range() diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index f8a7b8ea5c..dacc402318 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -50,6 +50,11 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): self._has_been_setup = True + def context_setting(): + return lib.set_context_setting() + rt.callbacks.addScript(rt.Name('systemPostNew'), + context_setting) + def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? return True diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 7c9e311c2f..63e4108c84 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -61,7 +61,7 @@ class CollectRender(pyblish.api.InstancePlugin): "plugin": "3dsmax", "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], - "version": version_int + "version": version_int, } self.log.info("data: {0}".format(data)) instance.data.update(data) diff --git a/openpype/hosts/max/plugins/publish/increment_workfile_version.py b/openpype/hosts/max/plugins/publish/increment_workfile_version.py new file mode 100644 index 0000000000..3dec214f77 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/increment_workfile_version.py @@ -0,0 +1,19 @@ +import pyblish.api +from openpype.lib import version_up +from pymxs import runtime as rt + + +class IncrementWorkfileVersion(pyblish.api.ContextPlugin): + """Increment current workfile version.""" + + order = pyblish.api.IntegratorOrder + 0.9 + label = "Increment Workfile Version" + hosts = ["max"] + families = ["workfile"] + + def process(self, context): + path = context.data["currentFile"] + filepath = version_up(path) + + rt.saveMaxFile(filepath) + self.log.info("Incrementing file version") diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index 018340d86c..3e31875fd8 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -69,7 +69,7 @@ def _resolution_from_document(doc): resolution_width = doc["data"].get("resolution_width") resolution_height = doc["data"].get("resolution_height") - # Make sure both width and heigh are set + # Make sure both width and height are set if resolution_width is None or resolution_height is None: cmds.warning( "No resolution information found for \"{}\"".format(doc["name"]) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index aa1e501578..22803a2e3a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1367,6 +1367,71 @@ def set_id(node, unique_id, overwrite=False): cmds.setAttr(attr, unique_id, type="string") +def get_attribute(plug, + asString=False, + expandEnvironmentVariables=False, + **kwargs): + """Maya getAttr with some fixes based on `pymel.core.general.getAttr()`. + + Like Pymel getAttr this applies some changes to `maya.cmds.getAttr` + - maya pointlessly returned vector results as a tuple wrapped in a list + (ex. '[(1,2,3)]'). This command unpacks the vector for you. + - when getting a multi-attr, maya would raise an error, but this will + return a list of values for the multi-attr + - added support for getting message attributes by returning the + connections instead + + Note that the asString + expandEnvironmentVariables argument naming + convention matches the `maya.cmds.getAttr` arguments so that it can + act as a direct replacement for it. + + Args: + plug (str): Node's attribute plug as `node.attribute` + asString (bool): Return string value for enum attributes instead + of the index. Note that the return value can be dependent on the + UI language Maya is running in. + expandEnvironmentVariables (bool): Expand any environment variable and + (tilde characters on UNIX) found in string attributes which are + returned. + + Kwargs: + Supports the keyword arguments of `maya.cmds.getAttr` + + Returns: + object: The value of the maya attribute. + + """ + attr_type = cmds.getAttr(plug, type=True) + if asString: + kwargs["asString"] = True + if expandEnvironmentVariables: + kwargs["expandEnvironmentVariables"] = True + try: + res = cmds.getAttr(plug, **kwargs) + except RuntimeError: + if attr_type == "message": + return cmds.listConnections(plug) + + node, attr = plug.split(".", 1) + children = cmds.attributeQuery(attr, node=node, listChildren=True) + if children: + return [ + get_attribute("{}.{}".format(node, child)) + for child in children + ] + + raise + + # Convert vector result wrapped in tuple + if isinstance(res, list) and len(res): + if isinstance(res[0], tuple) and len(res): + if attr_type in {'pointArray', 'vectorArray'}: + return res + return res[0] + + return res + + def set_attribute(attribute, value, node): """Adjust attributes based on the value from the attribute data @@ -1881,6 +1946,12 @@ def remove_other_uv_sets(mesh): cmds.removeMultiInstance(attr, b=True) +def get_node_parent(node): + """Return full path name for parent of node""" + parents = cmds.listRelatives(node, parent=True, fullPath=True) + return parents[0] if parents else None + + def get_id_from_sibling(node, history_only=True): """Return first node id in the history chain that matches this node. @@ -1904,10 +1975,6 @@ def get_id_from_sibling(node, history_only=True): """ - def _get_parent(node): - """Return full path name for parent of node""" - return cmds.listRelatives(node, parent=True, fullPath=True) - node = cmds.ls(node, long=True)[0] # Find all similar nodes in history @@ -1919,8 +1986,8 @@ def get_id_from_sibling(node, history_only=True): similar_nodes = [x for x in similar_nodes if x != node] # The node *must be* under the same parent - parent = _get_parent(node) - similar_nodes = [i for i in similar_nodes if _get_parent(i) == parent] + parent = get_node_parent(node) + similar_nodes = [i for i in similar_nodes if get_node_parent(i) == parent] # Check all of the remaining similar nodes and take the first one # with an id and assume it's the original. @@ -2073,23 +2140,13 @@ def get_frame_range(): frame_start = asset["data"].get("frameStart") frame_end = asset["data"].get("frameEnd") - # Backwards compatibility - if frame_start is None or frame_end is None: - frame_start = asset["data"].get("edit_in") - frame_end = asset["data"].get("edit_out") if frame_start is None or frame_end is None: cmds.warning("No edit information found for %s" % asset_name) return - handles = asset["data"].get("handles") or 0 - handle_start = asset["data"].get("handleStart") - if handle_start is None: - handle_start = handles - - handle_end = asset["data"].get("handleEnd") - if handle_end is None: - handle_end = handles + handle_start = asset["data"].get("handleStart") or 0 + handle_end = asset["data"].get("handleEnd") or 0 return { "frameStart": frame_start, @@ -2478,8 +2535,8 @@ def load_capture_preset(data=None): float(value[2]) / 255 ] disp_options[key] = value - else: - disp_options['displayGradient'] = True + elif key == "displayGradient": + disp_options[key] = value options['display_options'] = disp_options @@ -3176,38 +3233,78 @@ def set_colorspace(): def parent_nodes(nodes, parent=None): # type: (list, str) -> list """Context manager to un-parent provided nodes and return them back.""" - import pymel.core as pm # noqa - parent_node = None + def _as_mdagpath(node): + """Return MDagPath for node path.""" + if not node: + return + sel = OpenMaya.MSelectionList() + sel.add(node) + return sel.getDagPath(0) + + # We can only parent dag nodes so we ensure input contains only dag nodes + nodes = cmds.ls(nodes, type="dagNode", long=True) + if not nodes: + # opt-out early + yield + return + + parent_node_path = None delete_parent = False - if parent: if not cmds.objExists(parent): - parent_node = pm.createNode("transform", n=parent, ss=False) + parent_node = cmds.createNode("transform", + name=parent, + skipSelect=False) delete_parent = True else: - parent_node = pm.PyNode(parent) + parent_node = parent + parent_node_path = cmds.ls(parent_node, long=True)[0] + + # Store original parents node_parents = [] for node in nodes: - n = pm.PyNode(node) - try: - root = pm.listRelatives(n, parent=1)[0] - except IndexError: - root = None - node_parents.append((n, root)) + node_parent = get_node_parent(node) + node_parents.append((_as_mdagpath(node), _as_mdagpath(node_parent))) + try: - for node in node_parents: - if not parent: - node[0].setParent(world=True) + for node, node_parent in node_parents: + node_parent_path = node_parent.fullPathName() if node_parent else None # noqa + if node_parent_path == parent_node_path: + # Already a child + continue + + if parent_node_path: + cmds.parent(node.fullPathName(), parent_node_path) else: - node[0].setParent(parent_node) + cmds.parent(node.fullPathName(), world=True) + yield finally: - for node in node_parents: - if node[1]: - node[0].setParent(node[1]) + # Reparent to original parents + for node, original_parent in node_parents: + node_path = node.fullPathName() + if not node_path: + # Node must have been deleted + continue + + node_parent_path = get_node_parent(node_path) + + original_parent_path = None + if original_parent: + original_parent_path = original_parent.fullPathName() + if not original_parent_path: + # Original parent node must have been deleted + continue + + if node_parent_path != original_parent_path: + if not original_parent_path: + cmds.parent(node_path, world=True) + else: + cmds.parent(node_path, original_parent_path) + if delete_parent: - pm.delete(parent_node) + cmds.delete(parent_node_path) @contextlib.contextmanager @@ -3675,3 +3772,43 @@ def len_flattened(components): else: n += 1 return n + + +def get_all_children(nodes): + """Return all children of `nodes` including each instanced child. + Using maya.cmds.listRelatives(allDescendents=True) includes only the first + instance. As such, this function acts as an optimal replacement with a + focus on a fast query. + + """ + + sel = OpenMaya.MSelectionList() + traversed = set() + iterator = OpenMaya.MItDag(OpenMaya.MItDag.kDepthFirst) + for node in nodes: + + if node in traversed: + # Ignore if already processed as a child + # before + continue + + sel.clear() + sel.add(node) + dag = sel.getDagPath(0) + + iterator.reset(dag) + # ignore self + iterator.next() # noqa: B305 + while not iterator.isDone(): + + path = iterator.fullPathName() + + if path in traversed: + iterator.prune() + iterator.next() # noqa: B305 + continue + + traversed.add(path) + iterator.next() # noqa: B305 + + return list(traversed) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index a54256c59a..a6bcd003a5 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -339,7 +339,7 @@ class ARenderProducts: aov_tokens = ["", ""] def match_last(tokens, text): - """regex match the last occurence from a list of tokens""" + """regex match the last occurrence from a list of tokens""" pattern = "(?:.*)({})".format("|".join(tokens)) return re.search(pattern, text, re.IGNORECASE) @@ -857,6 +857,7 @@ class RenderProductsVray(ARenderProducts): if default_ext in {"exr (multichannel)", "exr (deep)"}: default_ext = "exr" + colorspace = lib.get_color_management_output_transform() products = [] # add beauty as default when not disabled @@ -868,7 +869,7 @@ class RenderProductsVray(ARenderProducts): productName="", ext=default_ext, camera=camera, - colorspace=lib.get_color_management_output_transform(), + colorspace=colorspace, multipart=self.multipart ) ) @@ -882,6 +883,7 @@ class RenderProductsVray(ARenderProducts): productName="Alpha", ext=default_ext, camera=camera, + colorspace=colorspace, multipart=self.multipart ) ) @@ -917,7 +919,8 @@ class RenderProductsVray(ARenderProducts): product = RenderProduct(productName=name, ext=default_ext, aov=aov, - camera=camera) + camera=camera, + colorspace=colorspace) products.append(product) # Continue as we've processed this special case AOV continue @@ -929,7 +932,7 @@ class RenderProductsVray(ARenderProducts): ext=default_ext, aov=aov, camera=camera, - colorspace=lib.get_color_management_output_transform() + colorspace=colorspace ) products.append(product) @@ -1051,7 +1054,7 @@ class RenderProductsRedshift(ARenderProducts): def get_files(self, product): # When outputting AOVs we need to replace Redshift specific AOV tokens # with Maya render tokens for generating file sequences. We validate to - # a specific AOV fileprefix so we only need to accout for one + # a specific AOV fileprefix so we only need to account for one # replacement. if not product.multipart and product.driver: file_prefix = self._get_attr(product.driver + ".filePrefix") @@ -1130,6 +1133,7 @@ class RenderProductsRedshift(ARenderProducts): products = [] light_groups_enabled = False has_beauty_aov = False + colorspace = lib.get_color_management_output_transform() for aov in aovs: enabled = self._get_attr(aov, "enabled") if not enabled: @@ -1173,7 +1177,8 @@ class RenderProductsRedshift(ARenderProducts): ext=ext, multipart=False, camera=camera, - driver=aov) + driver=aov, + colorspace=colorspace) products.append(product) if light_groups: @@ -1188,7 +1193,8 @@ class RenderProductsRedshift(ARenderProducts): ext=ext, multipart=False, camera=camera, - driver=aov) + driver=aov, + colorspace=colorspace) products.append(product) # When a Beauty AOV is added manually, it will be rendered as @@ -1204,7 +1210,8 @@ class RenderProductsRedshift(ARenderProducts): RenderProduct(productName=beauty_name, ext=ext, multipart=self.multipart, - camera=camera)) + camera=camera, + colorspace=colorspace)) return products @@ -1236,6 +1243,8 @@ class RenderProductsRenderman(ARenderProducts): """ from rfm2.api.displays import get_displays # noqa + colorspace = lib.get_color_management_output_transform() + cameras = [ self.sanitize_camera_name(c) for c in self.get_renderable_cameras() @@ -1302,7 +1311,8 @@ class RenderProductsRenderman(ARenderProducts): productName=aov_name, ext=extensions, camera=camera, - multipart=True + multipart=True, + colorspace=colorspace ) if has_cryptomatte and matte_enabled: @@ -1311,7 +1321,8 @@ class RenderProductsRenderman(ARenderProducts): aov=cryptomatte_aov, ext=extensions, camera=camera, - multipart=True + multipart=True, + colorspace=colorspace ) else: # this code should handle the case where no multipart diff --git a/openpype/hosts/maya/api/lib_rendersetup.py b/openpype/hosts/maya/api/lib_rendersetup.py index e616f26e1b..440ee21a52 100644 --- a/openpype/hosts/maya/api/lib_rendersetup.py +++ b/openpype/hosts/maya/api/lib_rendersetup.py @@ -19,6 +19,8 @@ from maya.app.renderSetup.model.override import ( UniqueOverride ) +from openpype.hosts.maya.api.lib import get_attribute + EXACT_MATCH = 0 PARENT_MATCH = 1 CLIENT_MATCH = 2 @@ -96,9 +98,6 @@ def get_attr_in_layer(node_attr, layer): """ - # Delay pymel import to here because it's slow to load - import pymel.core as pm - def _layer_needs_update(layer): """Return whether layer needs updating.""" # Use `getattr` as e.g. DEFAULT_RENDER_LAYER does not have @@ -125,7 +124,7 @@ def get_attr_in_layer(node_attr, layer): node = history_overrides[-1] if history_overrides else override node_attr_ = node + ".original" - return pm.getAttr(node_attr_, asString=True) + return get_attribute(node_attr_, asString=True) layer = get_rendersetup_layer(layer) rs = renderSetup.instance() @@ -145,7 +144,7 @@ def get_attr_in_layer(node_attr, layer): # we will let it error out. rs.switchToLayer(current_layer) - return pm.getAttr(node_attr, asString=True) + return get_attribute(node_attr, asString=True) overrides = get_attr_overrides(node_attr, layer) default_layer_value = get_default_layer_value(node_attr) @@ -156,7 +155,7 @@ def get_attr_in_layer(node_attr, layer): for match, layer_override, index in overrides: if isinstance(layer_override, AbsOverride): # Absolute override - value = pm.getAttr(layer_override.name() + ".attrValue") + value = get_attribute(layer_override.name() + ".attrValue") if match == EXACT_MATCH: # value = value pass @@ -168,8 +167,8 @@ def get_attr_in_layer(node_attr, layer): elif isinstance(layer_override, RelOverride): # Relative override # Value = Original * Multiply + Offset - multiply = pm.getAttr(layer_override.name() + ".multiply") - offset = pm.getAttr(layer_override.name() + ".offset") + multiply = get_attribute(layer_override.name() + ".multiply") + offset = get_attribute(layer_override.name() + ".offset") if match == EXACT_MATCH: value = value * multiply + offset diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 90ab6e21e0..4bee0664ef 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -33,7 +33,7 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): get_template_preset implementation) Returns: - bool: Wether the template was succesfully imported or not + bool: Whether the template was successfully imported or not """ if cmds.objExists(PLACEHOLDER_SET): @@ -116,7 +116,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): placeholder_name_parts = placeholder_data["builder_type"].split("_") pos = 1 - # add famlily in any + # add family in any placeholder_family = placeholder_data["family"] if placeholder_family: placeholder_name_parts.insert(pos, placeholder_family) diff --git a/openpype/hosts/maya/plugins/create/create_look.py b/openpype/hosts/maya/plugins/create/create_look.py index 44e439fe1f..51b0b8819a 100644 --- a/openpype/hosts/maya/plugins/create/create_look.py +++ b/openpype/hosts/maya/plugins/create/create_look.py @@ -12,6 +12,7 @@ class CreateLook(plugin.Creator): family = "look" icon = "paint-brush" make_tx = True + rs_tex = False def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) @@ -20,7 +21,8 @@ class CreateLook(plugin.Creator): # Whether to automatically convert the textures to .tx upon publish. self.data["maketx"] = self.make_tx - + # Whether to automatically convert the textures to .rstex upon publish. + self.data["rstex"] = self.rs_tex # Enable users to force a copy. # - on Windows is "forceCopy" always changed to `True` because of # windows implementation of hardlinks diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index f1b626c06b..e709239ae7 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -26,6 +26,7 @@ class CreateReview(plugin.Creator): "alpha cut" ] useMayaTimeline = True + panZoom = False def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -45,5 +46,6 @@ class CreateReview(plugin.Creator): data["keepImages"] = self.keepImages data["imagePlane"] = self.imagePlane data["transparency"] = self.transparency + data["panZoom"] = self.panZoom self.data = data diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 2574624dbb..ba69debc40 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -118,7 +118,7 @@ class ImportMayaLoader(load.LoaderPlugin): "clean_import", label="Clean import", default=False, - help="Should all occurences of cbId be purged?" + help="Should all occurrences of cbId be purged?" ) ] diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 11a2bd1966..7c3a732389 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -84,7 +84,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - nodes = [root, standin] + nodes = [root, standin, standin_shape] if operator is not None: nodes.append(operator) self[:] = nodes @@ -180,10 +180,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_basename, proxy_path = self._get_proxy_path(path) # Whether there is proxy or so, we still update the string operator. - # If no proxy exists, the string operator wont replace anything. + # If no proxy exists, the string operator won't replace anything. cmds.setAttr( string_replace_operator + ".match", - "resources/" + proxy_basename, + proxy_basename, type="string" ) cmds.setAttr( diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 6f60cb5726..9e7fd96bdb 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -11,7 +11,7 @@ from openpype.pipeline import ( get_representation_path, ) from openpype.hosts.maya.api.pipeline import containerise -from openpype.hosts.maya.api.lib import unique_namespace +from openpype.hosts.maya.api.lib import unique_namespace, get_container_members class AudioLoader(load.LoaderPlugin): @@ -52,17 +52,15 @@ class AudioLoader(load.LoaderPlugin): ) def update(self, container, representation): - import pymel.core as pm - audio_node = None - for node in pm.PyNode(container["objectName"]).members(): - if node.nodeType() == "audio": - audio_node = node + members = get_container_members(container) + audio_nodes = cmds.ls(members, type="audio") - assert audio_node is not None, "Audio node not found." + assert audio_nodes is not None, "Audio node not found." + audio_node = audio_nodes[0] path = get_representation_path(representation) - audio_node.filename.set(path) + cmds.setAttr("{}.filename".format(audio_node), path, type="string") cmds.setAttr( container["objectName"] + ".representation", str(representation["_id"]), @@ -80,8 +78,12 @@ class AudioLoader(load.LoaderPlugin): asset = get_asset_by_id( project_name, subset["parent"], fields=["parent"] ) - audio_node.sourceStart.set(1 - asset["data"]["frameStart"]) - audio_node.sourceEnd.set(asset["data"]["frameEnd"]) + + source_start = 1 - asset["data"]["frameStart"] + source_end = asset["data"]["frameEnd"] + + cmds.setAttr("{}.sourceStart".format(audio_node), source_start) + cmds.setAttr("{}.sourceEnd".format(audio_node), source_end) def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 07e5734f43..794b21eb5d 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -1,5 +1,9 @@ import os +import maya.cmds as cmds + +from openpype.hosts.maya.api.pipeline import containerise +from openpype.hosts.maya.api.lib import unique_namespace from openpype.pipeline import ( load, get_representation_path @@ -11,19 +15,15 @@ class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" families = ["model", "animation", "proxyAbc", "pointcache"] - representations = ["abc"] + representations = ["abc", "gpu_cache"] - label = "Import Gpu Cache" + label = "Load Gpu Cache" order = -5 icon = "code-fork" color = "orange" def load(self, context, name, namespace, data): - import maya.cmds as cmds - from openpype.hosts.maya.api.pipeline import containerise - from openpype.hosts.maya.api.lib import unique_namespace - asset = context['asset']['name'] namespace = namespace or unique_namespace( asset + "_", @@ -42,10 +42,9 @@ class GpuCacheLoader(load.LoaderPlugin): c = colors.get('model') if c is not None: cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + cmds.setAttr( + root + ".outlinerColor", + (float(c[0]) / 255), (float(c[1]) / 255), (float(c[2]) / 255) ) # Create transform with shape @@ -74,9 +73,6 @@ class GpuCacheLoader(load.LoaderPlugin): loader=self.__class__.__name__) def update(self, container, representation): - - import maya.cmds as cmds - path = get_representation_path(representation) # Update the cache @@ -96,7 +92,6 @@ class GpuCacheLoader(load.LoaderPlugin): self.update(container, representation) def remove(self, container): - import maya.cmds as cmds members = cmds.sets(container['objectName'], query=True) cmds.lockNode(members, lock=False) cmds.delete([container['objectName']] + members) diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py index 6421f3ffe2..bf13708e9b 100644 --- a/openpype/hosts/maya/plugins/load/load_image_plane.py +++ b/openpype/hosts/maya/plugins/load/load_image_plane.py @@ -11,11 +11,26 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.maya.api.pipeline import containerise -from openpype.hosts.maya.api.lib import unique_namespace +from openpype.hosts.maya.api.lib import ( + unique_namespace, + namespaced, + pairwise, + get_container_members +) from maya import cmds +def disconnect_inputs(plug): + overrides = cmds.listConnections(plug, + source=True, + destination=False, + plugs=True, + connections=True) or [] + for dest, src in pairwise(overrides): + cmds.disconnectAttr(src, dest) + + class CameraWindow(QtWidgets.QDialog): def __init__(self, cameras): @@ -74,6 +89,7 @@ class CameraWindow(QtWidgets.QDialog): self.camera = None self.close() + class ImagePlaneLoader(load.LoaderPlugin): """Specific loader of plate for image planes on selected camera.""" @@ -84,9 +100,7 @@ class ImagePlaneLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, data, options=None): - import pymel.core as pm - new_nodes = [] image_plane_depth = 1000 asset = context['asset']['name'] namespace = namespace or unique_namespace( @@ -96,16 +110,20 @@ class ImagePlaneLoader(load.LoaderPlugin): ) # Get camera from user selection. - camera = None # is_static_image_plane = None # is_in_all_views = None - if data: - camera = pm.PyNode(data.get("camera")) + camera = data.get("camera") if data else None if not camera: - cameras = pm.ls(type="camera") - camera_names = {x.getParent().name(): x for x in cameras} - camera_names["Create new camera."] = "create_camera" + cameras = cmds.ls(type="camera") + + # Cameras by names + camera_names = {} + for camera in cameras: + parent = cmds.listRelatives(camera, parent=True, path=True)[0] + camera_names[parent] = camera + + camera_names["Create new camera."] = "create-camera" window = CameraWindow(camera_names.keys()) window.exec_() # Skip if no camera was selected (Dialog was closed) @@ -113,43 +131,48 @@ class ImagePlaneLoader(load.LoaderPlugin): return camera = camera_names[window.camera] - if camera == "create_camera": - camera = pm.createNode("camera") + if camera == "create-camera": + camera = cmds.createNode("camera") if camera is None: return try: - camera.displayResolution.set(1) - camera.farClipPlane.set(image_plane_depth * 10) + cmds.setAttr("{}.displayResolution".format(camera), True) + cmds.setAttr("{}.farClipPlane".format(camera), + image_plane_depth * 10) except RuntimeError: pass # Create image plane - image_plane_transform, image_plane_shape = pm.imagePlane( - fileName=context["representation"]["data"]["path"], - camera=camera) - image_plane_shape.depth.set(image_plane_depth) + with namespaced(namespace): + # Create inside the namespace + image_plane_transform, image_plane_shape = cmds.imagePlane( + fileName=context["representation"]["data"]["path"], + camera=camera + ) + start_frame = cmds.playbackOptions(query=True, min=True) + end_frame = cmds.playbackOptions(query=True, max=True) - - start_frame = pm.playbackOptions(q=True, min=True) - end_frame = pm.playbackOptions(q=True, max=True) - - image_plane_shape.frameOffset.set(0) - image_plane_shape.frameIn.set(start_frame) - image_plane_shape.frameOut.set(end_frame) - image_plane_shape.frameCache.set(end_frame) - image_plane_shape.useFrameExtension.set(1) + for attr, value in { + "depth": image_plane_depth, + "frameOffset": 0, + "frameIn": start_frame, + "frameOut": end_frame, + "frameCache": end_frame, + "useFrameExtension": True + }.items(): + plug = "{}.{}".format(image_plane_shape, attr) + cmds.setAttr(plug, value) movie_representations = ["mov", "preview"] if context["representation"]["name"] in movie_representations: - # Need to get "type" by string, because its a method as well. - pm.Attribute(image_plane_shape + ".type").set(2) + cmds.setAttr(image_plane_shape + ".type", 2) # Ask user whether to use sequence or still image. if context["representation"]["name"] == "exr": # Ensure OpenEXRLoader plugin is loaded. - pm.loadPlugin("OpenEXRLoader.mll", quiet=True) + cmds.loadPlugin("OpenEXRLoader", quiet=True) message = ( "Hold image sequence on first frame?" @@ -161,32 +184,18 @@ class ImagePlaneLoader(load.LoaderPlugin): None, "Frame Hold.", message, - QtWidgets.QMessageBox.Ok, - QtWidgets.QMessageBox.Cancel + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No ) - if reply == QtWidgets.QMessageBox.Ok: - # find the input and output of frame extension - expressions = image_plane_shape.frameExtension.inputs() - frame_ext_output = image_plane_shape.frameExtension.outputs() - if expressions: - # the "time1" node is non-deletable attr - # in Maya, use disconnectAttr instead - pm.disconnectAttr(expressions, frame_ext_output) + if reply == QtWidgets.QMessageBox.Yes: + frame_extension_plug = "{}.frameExtension".format(image_plane_shape) # noqa - if not image_plane_shape.frameExtension.isFreeToChange(): - raise RuntimeError("Can't set frame extension for {}".format(image_plane_shape)) # noqa - # get the node of time instead and set the time for it. - image_plane_shape.frameExtension.set(start_frame) + # Remove current frame expression + disconnect_inputs(frame_extension_plug) - new_nodes.extend( - [ - image_plane_transform.longName().split("|")[-1], - image_plane_shape.longName().split("|")[-1] - ] - ) + cmds.setAttr(frame_extension_plug, start_frame) - for node in new_nodes: - pm.rename(node, "{}:{}".format(namespace, node)) + new_nodes = [image_plane_transform, image_plane_shape] return containerise( name=name, @@ -197,21 +206,19 @@ class ImagePlaneLoader(load.LoaderPlugin): ) def update(self, container, representation): - import pymel.core as pm - image_plane_shape = None - for node in pm.PyNode(container["objectName"]).members(): - if node.nodeType() == "imagePlane": - image_plane_shape = node - assert image_plane_shape is not None, "Image plane not found." + members = get_container_members(container) + image_planes = cmds.ls(members, type="imagePlane") + assert image_planes, "Image plane not found." + image_plane_shape = image_planes[0] path = get_representation_path(representation) - image_plane_shape.imageName.set(path) - cmds.setAttr( - container["objectName"] + ".representation", - str(representation["_id"]), - type="string" - ) + cmds.setAttr("{}.imageName".format(image_plane_shape), + path, + type="string") + cmds.setAttr("{}.representation".format(container["objectName"]), + str(representation["_id"]), + type="string") # Set frame range. project_name = legacy_io.active_project() @@ -227,10 +234,14 @@ class ImagePlaneLoader(load.LoaderPlugin): start_frame = asset["data"]["frameStart"] end_frame = asset["data"]["frameEnd"] - image_plane_shape.frameOffset.set(0) - image_plane_shape.frameIn.set(start_frame) - image_plane_shape.frameOut.set(end_frame) - image_plane_shape.frameCache.set(end_frame) + for attr, value in { + "frameOffset": 0, + "frameIn": start_frame, + "frameOut": end_frame, + "frameCache": end_frame + }: + plug = "{}.{}".format(image_plane_shape, attr) + cmds.setAttr(plug, value) def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index d93702a16d..461f4258aa 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -1,4 +1,6 @@ import os +import difflib +import contextlib from maya import cmds from openpype.settings import get_project_settings @@ -8,7 +10,83 @@ from openpype.pipeline.create import ( get_legacy_creator_by_name, ) import openpype.hosts.maya.api.plugin -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api.lib import ( + maintained_selection, + get_container_members, + parent_nodes +) + + +@contextlib.contextmanager +def preserve_modelpanel_cameras(container, log=None): + """Preserve camera members of container in the modelPanels. + + This is used to ensure a camera remains in the modelPanels after updating + to a new version. + + """ + + # Get the modelPanels that used the old camera + members = get_container_members(container) + old_cameras = set(cmds.ls(members, type="camera", long=True)) + if not old_cameras: + # No need to manage anything + yield + return + + panel_cameras = {} + for panel in cmds.getPanel(type="modelPanel"): + cam = cmds.ls(cmds.modelPanel(panel, query=True, camera=True), + long=True) + + # Often but not always maya returns the transform from the + # modelPanel as opposed to the camera shape, so we convert it + # to explicitly be the camera shape + if cmds.nodeType(cam) != "camera": + cam = cmds.listRelatives(cam, + children=True, + fullPath=True, + type="camera")[0] + if cam in old_cameras: + panel_cameras[panel] = cam + + if not panel_cameras: + # No need to manage anything + yield + return + + try: + yield + finally: + new_members = get_container_members(container) + new_cameras = set(cmds.ls(new_members, type="camera", long=True)) + if not new_cameras: + return + + for panel, cam_name in panel_cameras.items(): + new_camera = None + if cam_name in new_cameras: + new_camera = cam_name + elif len(new_cameras) == 1: + new_camera = next(iter(new_cameras)) + else: + # Multiple cameras in the updated container but not an exact + # match detected by name. Find the closest match + matches = difflib.get_close_matches(word=cam_name, + possibilities=new_cameras, + n=1) + if matches: + new_camera = matches[0] # best match + if log: + log.info("Camera in '{}' restored with " + "closest match camera: {} (before: {})" + .format(panel, new_camera, cam_name)) + + if not new_camera: + # Unable to find the camera to re-apply in the modelpanel + continue + + cmds.modelPanel(panel, edit=True, camera=new_camera) class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -41,7 +119,6 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, options): import maya.cmds as cmds - import pymel.core as pm try: family = context["representation"]["context"]["family"] @@ -68,7 +145,10 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): new_nodes = (list(set(nodes) - set(shapes))) - current_namespace = pm.namespaceInfo(currentNamespace=True) + # if there are cameras, try to lock their transforms + self._lock_camera_transforms(new_nodes) + + current_namespace = cmds.namespaceInfo(currentNamespace=True) if current_namespace != ":": group_name = current_namespace + ":" + group_name @@ -78,37 +158,29 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self[:] = new_nodes if attach_to_root: - group_node = pm.PyNode(group_name) - roots = set() + roots = cmds.listRelatives(group_name, + children=True, + fullPath=True) or [] - for node in new_nodes: - try: - roots.add(pm.PyNode(node).getAllParents()[-2]) - except: # noqa: E722 - pass + if family not in {"layout", "setdress", + "mayaAscii", "mayaScene"}: + # QUESTION Why do we need to exclude these families? + with parent_nodes(roots, parent=None): + cmds.xform(group_name, zeroTransformPivots=True) - if family not in ["layout", "setdress", - "mayaAscii", "mayaScene"]: - for root in roots: - root.setParent(world=True) - - group_node.zeroTransformPivots() - for root in roots: - root.setParent(group_node) - - cmds.setAttr(group_name + ".displayHandle", 1) + cmds.setAttr("{}.displayHandle".format(group_name), 1) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings['maya']['load']['colors'] c = colors.get(family) if c is not None: - group_node.useOutlinerColor.set(1) - group_node.outlinerColor.set( - (float(c[0]) / 255), - (float(c[1]) / 255), - (float(c[2]) / 255)) + cmds.setAttr("{}.useOutlinerColor".format(group_name), 1) + cmds.setAttr("{}.outlinerColor".format(group_name), + (float(c[0]) / 255), + (float(c[1]) / 255), + (float(c[2]) / 255)) - cmds.setAttr(group_name + ".displayHandle", 1) + cmds.setAttr("{}.displayHandle".format(group_name), 1) # get bounding box bbox = cmds.exactWorldBoundingBox(group_name) # get pivot position on world space @@ -122,20 +194,30 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): cy = cy + pivot[1] cz = cz + pivot[2] # set selection handle offset to center of bounding box - cmds.setAttr(group_name + ".selectHandleX", cx) - cmds.setAttr(group_name + ".selectHandleY", cy) - cmds.setAttr(group_name + ".selectHandleZ", cz) + cmds.setAttr("{}.selectHandleX".format(group_name), cx) + cmds.setAttr("{}.selectHandleY".format(group_name), cy) + cmds.setAttr("{}.selectHandleZ".format(group_name), cz) if family == "rig": self._post_process_rig(name, namespace, context, options) else: if "translate" in options: - cmds.setAttr(group_name + ".t", *options["translate"]) + cmds.setAttr("{}.translate".format(group_name), + *options["translate"]) return new_nodes def switch(self, container, representation): self.update(container, representation) + def update(self, container, representation): + with preserve_modelpanel_cameras(container, log=self.log): + super(ReferenceLoader, self).update(container, representation) + + # We also want to lock camera transforms on any new cameras in the + # reference or for a camera which might have changed names. + members = get_container_members(container) + self._lock_camera_transforms(members) + def _post_process_rig(self, name, namespace, context, options): output = next((node for node in self if @@ -168,3 +250,18 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): options={"useSelection": True}, data={"dependencies": dependency} ) + + def _lock_camera_transforms(self, nodes): + cameras = cmds.ls(nodes, type="camera") + if not cameras: + return + + # Check the Maya version, lockTransform has been introduced since + # Maya 2016.5 Ext 2 + version = int(cmds.about(version=True)) + if version >= 2016: + for camera in cameras: + cmds.camera(camera, edit=True, lockTransform=True) + else: + self.log.warning("This version of Maya does not support locking of" + " transforms of cameras.") diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 0415808b7a..0845f653b1 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -1,6 +1,7 @@ from maya import cmds import pyblish.api +from openpype.hosts.maya.api.lib import get_all_children class CollectArnoldSceneSource(pyblish.api.InstancePlugin): @@ -21,18 +22,21 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if objset.endswith("content_SET"): - instance.data["setMembers"] = cmds.ls(members, long=True) - self.log.debug("content members: {}".format(members)) + members = cmds.ls(members, long=True) + children = get_all_children(members) + instance.data["contentMembers"] = children + self.log.debug("content members: {}".format(children)) elif objset.endswith("proxy_SET"): - instance.data["proxy"] = cmds.ls(members, long=True) - self.log.debug("proxy members: {}".format(members)) + set_members = get_all_children(cmds.ls(members, long=True)) + instance.data["proxy"] = set_members + self.log.debug("proxy members: {}".format(set_members)) # Use camera in object set if present else default to render globals # camera. cameras = cmds.ls(type="camera", long=True) renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] camera = renderable[0] - for node in instance.data["setMembers"]: + for node in instance.data["contentMembers"]: camera_shapes = cmds.listRelatives( node, shapes=True, type="camera" ) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index c594626569..87a4de162d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -1,48 +1,8 @@ from maya import cmds -import maya.api.OpenMaya as om import pyblish.api import json - - -def get_all_children(nodes): - """Return all children of `nodes` including each instanced child. - Using maya.cmds.listRelatives(allDescendents=True) includes only the first - instance. As such, this function acts as an optimal replacement with a - focus on a fast query. - - """ - - sel = om.MSelectionList() - traversed = set() - iterator = om.MItDag(om.MItDag.kDepthFirst) - for node in nodes: - - if node in traversed: - # Ignore if already processed as a child - # before - continue - - sel.clear() - sel.add(node) - dag = sel.getDagPath(0) - - iterator.reset(dag) - # ignore self - iterator.next() # noqa: B305 - while not iterator.isDone(): - - path = iterator.fullPathName() - - if path in traversed: - iterator.prune() - iterator.next() # noqa: B305 - continue - - traversed.add(path) - iterator.next() # noqa: B305 - - return list(traversed) +from openpype.hosts.maya.api.lib import get_all_children class CollectInstances(pyblish.api.ContextPlugin): @@ -149,13 +109,6 @@ class CollectInstances(pyblish.api.ContextPlugin): # Append start frame and end frame to label if present if "frameStart" and "frameEnd" in data: - - # Backwards compatibility for 'handles' data - if "handles" in data: - data["handleStart"] = data["handles"] - data["handleEnd"] = data["handles"] - data.pop('handles') - # Take handles from context if not set locally on the instance for key in ["handleStart", "handleEnd"]: if key not in data: diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index b01160a1c0..287ddc228b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -556,7 +556,7 @@ class CollectLook(pyblish.api.InstancePlugin): continue if cmds.getAttr(attribute, type=True) == "message": continue - node_attributes[attr] = cmds.getAttr(attribute) + node_attributes[attr] = cmds.getAttr(attribute, asString=True) # Only include if there are any properties we care about if not node_attributes: continue diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index a7cb14855b..33fc7a025f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -255,7 +255,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): Searches through the overrides finding all material overrides. From there it extracts the shading group and then finds all texture files in the shading group network. It also checks for mipmap versions of texture files - and adds them to the resouces to get published. + and adds them to the resources to get published. """ diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index 548b1c996a..0b03988002 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -1,10 +1,10 @@ from maya import cmds, mel -import pymel.core as pm import pyblish.api from openpype.client import get_subset_by_name -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, KnownPublishError +from openpype.hosts.maya.api.lib import get_attribute_input class CollectReview(pyblish.api.InstancePlugin): @@ -15,7 +15,6 @@ class CollectReview(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.3 label = 'Collect Review Data' families = ["review"] - legacy = True def process(self, instance): @@ -35,55 +34,67 @@ class CollectReview(pyblish.api.InstancePlugin): self.log.debug('members: {}'.format(members)) # validate required settings - assert len(cameras) == 1, "Not a single camera found in extraction" + if len(cameras) == 0: + raise KnownPublishError("No camera found in review " + "instance: {}".format(instance)) + elif len(cameras) > 2: + raise KnownPublishError( + "Only a single camera is allowed for a review instance but " + "more than one camera found in review instance: {}. " + "Cameras found: {}".format(instance, ", ".join(cameras))) + camera = cameras[0] self.log.debug('camera: {}'.format(camera)) - objectset = instance.context.data['objectsets'] + context = instance.context + objectset = context.data['objectsets'] - reviewable_subset = None - reviewable_subset = list(set(members) & set(objectset)) - if reviewable_subset: - assert len(reviewable_subset) <= 1, "Multiple subsets for review" - self.log.debug('subset for review: {}'.format(reviewable_subset)) + reviewable_subsets = list(set(members) & set(objectset)) + if reviewable_subsets: + if len(reviewable_subsets) > 1: + raise KnownPublishError( + "Multiple attached subsets for review are not supported. " + "Attached: {}".format(", ".join(reviewable_subsets)) + ) - i = 0 - for inst in instance.context: + reviewable_subset = reviewable_subsets[0] + self.log.debug( + "Subset attached to review: {}".format(reviewable_subset) + ) - self.log.debug('filtering {}'.format(inst)) - data = instance.context[i].data + # Find the relevant publishing instance in the current context + reviewable_inst = next(inst for inst in context + if inst.name == reviewable_subset) + data = reviewable_inst.data - if inst.name != reviewable_subset[0]: - self.log.debug('subset name does not match {}'.format( - reviewable_subset[0])) - i += 1 - continue + self.log.debug( + 'Adding review family to {}'.format(reviewable_subset) + ) + if data.get('families'): + data['families'].append('review') + else: + data['families'] = ['review'] + + data['review_camera'] = camera + data['frameStartFtrack'] = instance.data["frameStartHandle"] + data['frameEndFtrack'] = instance.data["frameEndHandle"] + data['frameStartHandle'] = instance.data["frameStartHandle"] + data['frameEndHandle'] = instance.data["frameEndHandle"] + data["frameStart"] = instance.data["frameStart"] + data["frameEnd"] = instance.data["frameEnd"] + data['step'] = instance.data['step'] + data['fps'] = instance.data['fps'] + data['review_width'] = instance.data['review_width'] + data['review_height'] = instance.data['review_height'] + data["isolate"] = instance.data["isolate"] + data["panZoom"] = instance.data.get("panZoom", False) + data["panel"] = instance.data["panel"] + + # The review instance must be active + cmds.setAttr(str(instance) + '.active', 1) + + instance.data['remove'] = True - if data.get('families'): - data['families'].append('review') - else: - data['families'] = ['review'] - self.log.debug('adding review family to {}'.format( - reviewable_subset)) - data['review_camera'] = camera - # data["publish"] = False - data['frameStartFtrack'] = instance.data["frameStartHandle"] - data['frameEndFtrack'] = instance.data["frameEndHandle"] - data['frameStartHandle'] = instance.data["frameStartHandle"] - data['frameEndHandle'] = instance.data["frameEndHandle"] - data["frameStart"] = instance.data["frameStart"] - data["frameEnd"] = instance.data["frameEnd"] - data['handles'] = instance.data.get('handles', None) - data['step'] = instance.data['step'] - data['fps'] = instance.data['fps'] - data['review_width'] = instance.data['review_width'] - data['review_height'] = instance.data['review_height'] - data["isolate"] = instance.data["isolate"] - cmds.setAttr(str(instance) + '.active', 1) - self.log.debug('data {}'.format(instance.context[i].data)) - instance.context[i].data.update(data) - instance.data['remove'] = True - self.log.debug('isntance data {}'.format(instance.data)) else: legacy_subset_name = task + 'Review' asset_doc = instance.context.data['assetEntity'] @@ -105,42 +116,59 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data["frameEndHandle"] # make ftrack publishable - instance.data["families"] = ['ftrack'] + instance.data.setdefault("families", []).append('ftrack') cmds.setAttr(str(instance) + '.active', 1) # Collect audio playback_slider = mel.eval('$tmpVar=$gPlayBackSlider') - audio_name = cmds.timeControl(playback_slider, q=True, s=True) + audio_name = cmds.timeControl(playback_slider, + query=True, + sound=True) display_sounds = cmds.timeControl( - playback_slider, q=True, displaySound=True + playback_slider, query=True, displaySound=True ) - audio_nodes = [] + def get_audio_node_data(node): + return { + "offset": cmds.getAttr("{}.offset".format(node)), + "filename": cmds.getAttr("{}.filename".format(node)) + } + + audio_data = [] if audio_name: - audio_nodes.append(pm.PyNode(audio_name)) + audio_data.append(get_audio_node_data(audio_name)) - if not audio_name and display_sounds: - start_frame = int(pm.playbackOptions(q=True, min=True)) - end_frame = float(pm.playbackOptions(q=True, max=True)) - frame_range = range(int(start_frame), int(end_frame)) + elif display_sounds: + start_frame = int(cmds.playbackOptions(query=True, min=True)) + end_frame = int(cmds.playbackOptions(query=True, max=True)) - for node in pm.ls(type="audio"): + for node in cmds.ls(type="audio"): # Check if frame range and audio range intersections, # for whether to include this audio node or not. - start_audio = node.offset.get() - end_audio = node.offset.get() + node.duration.get() - audio_range = range(int(start_audio), int(end_audio)) + duration = cmds.getAttr("{}.duration".format(node)) + start_audio = cmds.getAttr("{}.offset".format(node)) + end_audio = start_audio + duration - if bool(set(frame_range).intersection(audio_range)): - audio_nodes.append(node) + if start_audio <= end_frame and end_audio > start_frame: + audio_data.append(get_audio_node_data(node)) - instance.data["audio"] = [] - for node in audio_nodes: - instance.data["audio"].append( - { - "offset": node.offset.get(), - "filename": node.filename.get() - } - ) + instance.data["audio"] = audio_data + + # Collect focal length. + attr = camera + ".focalLength" + if get_attribute_input(attr): + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + 1 + focal_length = [ + cmds.getAttr(attr, time=t) for t in range(int(start), int(end)) + ] + else: + focal_length = cmds.getAttr(attr) + + key = "focalLength" + try: + instance.data["burninDataMembers"][key] = focal_length + except KeyError: + instance.data["burninDataMembers"] = {key: focal_length} diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 924ac58c40..14bcc71da6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -1,12 +1,12 @@ import os +from collections import defaultdict +import json from maya import cmds import arnold from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import ( - maintained_selection, attribute_values, delete_after -) +from openpype.hosts.maya.api import lib class ExtractArnoldSceneSource(publish.Extractor): @@ -19,8 +19,7 @@ class ExtractArnoldSceneSource(publish.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) - filename = "{}.ass".format(instance.name) - file_path = os.path.join(staging_dir, filename) + file_path = os.path.join(staging_dir, "{}.ass".format(instance.name)) # Mask mask = arnold.AI_NODE_ALL @@ -71,8 +70,8 @@ class ExtractArnoldSceneSource(publish.Extractor): "mask": mask } - filenames = self._extract( - instance.data["setMembers"], attribute_data, kwargs + filenames, nodes_by_id = self._extract( + instance.data["contentMembers"], attribute_data, kwargs ) if "representations" not in instance.data: @@ -88,6 +87,19 @@ class ExtractArnoldSceneSource(publish.Extractor): instance.data["representations"].append(representation) + json_path = os.path.join(staging_dir, "{}.json".format(instance.name)) + with open(json_path, "w") as f: + json.dump(nodes_by_id, f) + + representation = { + "name": "json", + "ext": "json", + "files": os.path.basename(json_path), + "stagingDir": staging_dir + } + + instance.data["representations"].append(representation) + self.log.info( "Extracted instance {} to: {}".format(instance.name, staging_dir) ) @@ -97,7 +109,7 @@ class ExtractArnoldSceneSource(publish.Extractor): return kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") - filenames = self._extract( + filenames, _ = self._extract( instance.data["proxy"], attribute_data, kwargs ) @@ -113,34 +125,60 @@ class ExtractArnoldSceneSource(publish.Extractor): instance.data["representations"].append(representation) def _extract(self, nodes, attribute_data, kwargs): - self.log.info("Writing: " + kwargs["filename"]) + self.log.info( + "Writing {} with:\n{}".format(kwargs["filename"], kwargs) + ) filenames = [] + nodes_by_id = defaultdict(list) # Duplicating nodes so they are direct children of the world. This # makes the hierarchy of any exported ass file the same. - with delete_after() as delete_bin: + with lib.delete_after() as delete_bin: duplicate_nodes = [] for node in nodes: + # Only interested in transforms: + if cmds.nodeType(node) != "transform": + continue + + # Only interested in transforms with shapes. + shapes = cmds.listRelatives( + node, shapes=True, noIntermediate=True + ) + if not shapes: + continue + duplicate_transform = cmds.duplicate(node)[0] - # Discard the children. - shapes = cmds.listRelatives(duplicate_transform, shapes=True) + if cmds.listRelatives(duplicate_transform, parent=True): + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] + + basename = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + duplicate_transform = cmds.rename( + duplicate_transform, basename + ) + + # Discard children nodes that are not shapes + shapes = cmds.listRelatives( + duplicate_transform, shapes=True, fullPath=True + ) children = cmds.listRelatives( - duplicate_transform, children=True + duplicate_transform, children=True, fullPath=True ) cmds.delete(set(children) - set(shapes)) - duplicate_transform = cmds.parent( - duplicate_transform, world=True - )[0] - - cmds.rename(duplicate_transform, node.split("|")[-1]) - duplicate_transform = "|" + node.split("|")[-1] - duplicate_nodes.append(duplicate_transform) + duplicate_nodes.extend(shapes) delete_bin.append(duplicate_transform) - with attribute_values(attribute_data): - with maintained_selection(): + # Copy cbId to mtoa_constant. + for node in duplicate_nodes: + # Converting Maya hierarchy separator "|" to Arnold + # separator "/". + nodes_by_id[lib.get_id(node)].append(node.replace("|", "/")) + + with lib.attribute_values(attribute_data): + with lib.maintained_selection(): self.log.info( "Writing: {}".format(duplicate_nodes) ) @@ -157,4 +195,4 @@ class ExtractArnoldSceneSource(publish.Extractor): self.log.info("Exported: {}".format(filenames)) - return filenames + return filenames, nodes_by_id diff --git a/openpype/hosts/maya/plugins/publish/extract_gpu_cache.py b/openpype/hosts/maya/plugins/publish/extract_gpu_cache.py new file mode 100644 index 0000000000..422f5ad019 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_gpu_cache.py @@ -0,0 +1,65 @@ +import json + +from maya import cmds + +from openpype.pipeline import publish + + +class ExtractGPUCache(publish.Extractor): + """Extract the content of the instance to a GPU cache file.""" + + label = "GPU Cache" + hosts = ["maya"] + families = ["model", "animation", "pointcache"] + step = 1.0 + stepSave = 1 + optimize = True + optimizationThreshold = 40000 + optimizeAnimationsForMotionBlur = True + writeMaterials = True + useBaseTessellation = True + + def process(self, instance): + cmds.loadPlugin("gpuCache", quiet=True) + + staging_dir = self.staging_dir(instance) + filename = "{}_gpu_cache".format(instance.name) + + # Write out GPU cache file. + kwargs = { + "directory": staging_dir, + "fileName": filename, + "saveMultipleFiles": False, + "simulationRate": self.step, + "sampleMultiplier": self.stepSave, + "optimize": self.optimize, + "optimizationThreshold": self.optimizationThreshold, + "optimizeAnimationsForMotionBlur": ( + self.optimizeAnimationsForMotionBlur + ), + "writeMaterials": self.writeMaterials, + "useBaseTessellation": self.useBaseTessellation + } + self.log.debug( + "Extract {} with:\n{}".format( + instance[:], json.dumps(kwargs, indent=4, sort_keys=True) + ) + ) + cmds.gpuCache(instance[:], **kwargs) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "gpu_cache", + "ext": "abc", + "files": filename + ".abc", + "stagingDir": staging_dir, + "outputName": "gpu_cache" + } + + instance.data["representations"].append(representation) + + self.log.info( + "Extracted instance {} to: {}".format(instance.name, staging_dir) + ) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index bc506b7feb..93054e5fbb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -1,63 +1,42 @@ # -*- coding: utf-8 -*- """Maya look extractor.""" -import os -import json -import tempfile -import platform -import contextlib +from abc import ABCMeta, abstractmethod from collections import OrderedDict - -from maya import cmds # noqa +import contextlib +import json +import logging +import os +import platform +import tempfile +import six +import attr import pyblish.api -from openpype.lib import source_hash, run_subprocess -from openpype.pipeline import legacy_io, publish +from maya import cmds # noqa + +from openpype.lib.vendor_bin_utils import find_executable +from openpype.lib import source_hash, run_subprocess, get_oiio_tools_path +from openpype.pipeline import legacy_io, publish, KnownPublishError from openpype.hosts.maya.api import lib -from openpype.hosts.maya.api.lib import image_info, guess_colorspace # Modes for transfer COPY = 1 HARDLINK = 2 -def _has_arnold(): - """Return whether the arnold package is available and can be imported.""" - try: - import arnold # noqa: F401 - return True - except (ImportError, ModuleNotFoundError): - return False - - -def escape_space(path): - """Ensure path is enclosed by quotes to allow paths with spaces""" - return '"{}"'.format(path) if " " in path else path - - -def get_ocio_config_path(profile_folder): - """Path to OpenPype vendorized OCIO. - - Vendorized OCIO config file path is grabbed from the specific path - hierarchy specified below. - - "{OPENPYPE_ROOT}/vendor/OpenColorIO-Configs/{profile_folder}/config.ocio" - Args: - profile_folder (str): Name of folder to grab config file from. - - Returns: - str: Path to vendorized config file. - """ - - return os.path.join( - os.environ["OPENPYPE_ROOT"], - "vendor", - "bin", - "ocioconfig", - "OpenColorIOConfigs", - profile_folder, - "config.ocio" - ) +@attr.s +class TextureResult: + """The resulting texture of a processed file for a resource""" + # Path to the file + path = attr.ib() + # Colorspace of the resulting texture. This might not be the input + # colorspace of the texture if a TextureProcessor has processed the file. + colorspace = attr.ib() + # Hash generated for the texture using openpype.lib.source_hash + file_hash = attr.ib() + # The transfer mode, e.g. COPY or HARDLINK + transfer_mode = attr.ib() def find_paths_by_hash(texture_hash): @@ -76,61 +55,6 @@ def find_paths_by_hash(texture_hash): return legacy_io.distinct(key, {"type": "version"}) -def maketx(source, destination, args, logger): - """Make `.tx` using `maketx` with some default settings. - - The settings are based on default as used in Arnold's - txManager in the scene. - This function requires the `maketx` executable to be - on the `PATH`. - - Args: - source (str): Path to source file. - destination (str): Writing destination path. - args (list): Additional arguments for `maketx`. - logger (logging.Logger): Logger to log messages to. - - Returns: - str: Output of `maketx` command. - - """ - from openpype.lib import get_oiio_tools_path - - maketx_path = get_oiio_tools_path("maketx") - - if not maketx_path: - print( - "OIIO tool not found in {}".format(maketx_path)) - raise AssertionError("OIIO tool not found") - - subprocess_args = [ - maketx_path, - "-v", # verbose - "-u", # update mode - # unpremultiply before conversion (recommended when alpha present) - "--unpremult", - "--checknan", - # use oiio-optimized settings for tile-size, planarconfig, metadata - "--oiio", - "--filter", "lanczos3", - source - ] - - subprocess_args.extend(args) - subprocess_args.extend(["-o", destination]) - - cmd = " ".join(subprocess_args) - logger.debug(cmd) - - try: - out = run_subprocess(subprocess_args) - except Exception: - logger.error("Maketx converion failed", exc_info=True) - raise - - return out - - @contextlib.contextmanager def no_workspace_dir(): """Force maya to a fake temporary workspace directory. @@ -163,6 +87,303 @@ def no_workspace_dir(): os.rmdir(fake_workspace_dir) +@six.add_metaclass(ABCMeta) +class TextureProcessor: + + extension = None + + def __init__(self, log=None): + if log is None: + log = logging.getLogger(self.__class__.__name__) + self.log = log + + def apply_settings(self, system_settings, project_settings): + """Apply OpenPype system/project settings to the TextureProcessor + + Args: + system_settings (dict): OpenPype system settings + project_settings (dict): OpenPype project settings + + Returns: + None + + """ + pass + + @abstractmethod + def process(self, + source, + colorspace, + color_management, + staging_dir): + """Process the `source` texture. + + Must be implemented on inherited class. + + This must always return a TextureResult even when it does not generate + a texture. If it doesn't generate a texture then it should return a + TextureResult using the input path and colorspace. + + Args: + source (str): Path to source file. + colorspace (str): Colorspace of the source file. + color_management (dict): Maya Color management data from + `lib.get_color_management_preferences` + staging_dir (str): Output directory to write to. + + Returns: + TextureResult: The resulting texture information. + + """ + pass + + def __repr__(self): + # Log instance as class name + return self.__class__.__name__ + + +class MakeRSTexBin(TextureProcessor): + """Make `.rstexbin` using `redshiftTextureProcessor`""" + + extension = ".rstexbin" + + def process(self, + source, + colorspace, + color_management, + staging_dir): + + texture_processor_path = self.get_redshift_tool( + "redshiftTextureProcessor" + ) + if not texture_processor_path: + raise KnownPublishError("Must have Redshift available.") + + subprocess_args = [ + texture_processor_path, + source + ] + + hash_args = ["rstex"] + texture_hash = source_hash(source, *hash_args) + + # Redshift stores the output texture next to the input but with + # the extension replaced to `.rstexbin` + basename, ext = os.path.splitext(source) + destination = "{}{}".format(basename, self.extension) + + self.log.debug(" ".join(subprocess_args)) + try: + run_subprocess(subprocess_args) + except Exception: + self.log.error("Texture .rstexbin conversion failed", + exc_info=True) + raise + + return TextureResult( + path=destination, + file_hash=texture_hash, + colorspace=colorspace, + transfer_mode=COPY + ) + + @staticmethod + def get_redshift_tool(tool_name): + """Path to redshift texture processor. + + On Windows it adds .exe extension if missing from tool argument. + + Args: + tool_name (string): Tool name. + + Returns: + str: Full path to redshift texture processor executable. + """ + if "REDSHIFT_COREDATAPATH" not in os.environ: + raise RuntimeError("Must have Redshift available.") + + redshift_tool_path = os.path.join( + os.environ["REDSHIFT_COREDATAPATH"], + "bin", + tool_name + ) + + return find_executable(redshift_tool_path) + + +class MakeTX(TextureProcessor): + """Make `.tx` using `maketx` with some default settings. + + Some hardcoded arguments passed to `maketx` are based on the defaults used + in Arnold's txManager tool. + + """ + + extension = ".tx" + + def __init__(self, log=None): + super(MakeTX, self).__init__(log=log) + self.extra_args = [] + + def apply_settings(self, system_settings, project_settings): + # Allow extra maketx arguments from project settings + args_settings = ( + project_settings["maya"]["publish"] + .get("ExtractLook", {}).get("maketx_arguments", []) + ) + extra_args = [] + for arg_data in args_settings: + argument = arg_data["argument"] + parameters = arg_data["parameters"] + if not argument: + self.log.debug("Ignoring empty parameter from " + "`maketx_arguments` setting..") + continue + + extra_args.append(argument) + extra_args.extend(parameters) + + self.extra_args = extra_args + + def process(self, + source, + colorspace, + color_management, + staging_dir): + """Process the texture. + + This function requires the `maketx` executable to be available in an + OpenImageIO toolset detectable by OpenPype. + + Args: + source (str): Path to source file. + colorspace (str): Colorspace of the source file. + color_management (dict): Maya Color management data from + `lib.get_color_management_preferences` + staging_dir (str): Output directory to write to. + + Returns: + TextureResult: The resulting texture information. + + """ + + maketx_path = get_oiio_tools_path("maketx") + + if not maketx_path: + raise AssertionError( + "OIIO 'maketx' tool not found. Result: {}".format(maketx_path) + ) + + # Define .tx filepath in staging if source file is not .tx + fname, ext = os.path.splitext(os.path.basename(source)) + if ext == ".tx": + # Do nothing if the source file is already a .tx file. + return TextureResult( + path=source, + file_hash=None, # todo: unknown texture hash? + colorspace=colorspace, + transfer_mode=COPY + ) + + # Hardcoded default arguments for maketx conversion based on Arnold's + # txManager in Maya + args = [ + # unpremultiply before conversion (recommended when alpha present) + "--unpremult", + # use oiio-optimized settings for tile-size, planarconfig, metadata + "--oiio", + "--filter", "lanczos3", + ] + if color_management["enabled"]: + config_path = color_management["config"] + if not os.path.exists(config_path): + raise RuntimeError("OCIO config not found at: " + "{}".format(config_path)) + + render_colorspace = color_management["rendering_space"] + + self.log.info("tx: converting colorspace {0} " + "-> {1}".format(colorspace, + render_colorspace)) + args.extend(["--colorconvert", colorspace, render_colorspace]) + args.extend(["--colorconfig", config_path]) + + else: + # Maya Color management is disabled. We cannot rely on an OCIO + self.log.debug("tx: Maya color management is disabled. No color " + "conversion will be applied to .tx conversion for: " + "{}".format(source)) + # Assume linear + render_colorspace = "linear" + + # Note: The texture hash is only reliable if we include any potential + # conversion arguments provide to e.g. `maketx` + hash_args = ["maketx"] + args + self.extra_args + texture_hash = source_hash(source, *hash_args) + + # Ensure folder exists + resources_dir = os.path.join(staging_dir, "resources") + if not os.path.exists(resources_dir): + os.makedirs(resources_dir) + + self.log.info("Generating .tx file for %s .." % source) + + subprocess_args = [ + maketx_path, + "-v", # verbose + "-u", # update mode + # --checknan doesn't influence the output file but aborts the + # conversion if it finds any. So we can avoid it for the file hash + "--checknan", + source + ] + + subprocess_args.extend(args) + if self.extra_args: + subprocess_args.extend(self.extra_args) + + # Add source hash attribute after other arguments for log readability + # Note: argument is excluded from the hash since it is the hash itself + subprocess_args.extend([ + "--sattrib", + "sourceHash", + texture_hash + ]) + + destination = os.path.join(resources_dir, fname + ".tx") + subprocess_args.extend(["-o", destination]) + + # We want to make sure we are explicit about what OCIO config gets + # used. So when we supply no --colorconfig flag that no fallback to + # an OCIO env var occurs. + env = os.environ.copy() + env.pop("OCIO", None) + + self.log.debug(" ".join(subprocess_args)) + try: + run_subprocess(subprocess_args, env=env) + except Exception: + self.log.error("Texture maketx conversion failed", + exc_info=True) + raise + + return TextureResult( + path=destination, + file_hash=texture_hash, + colorspace=render_colorspace, + transfer_mode=COPY + ) + + @staticmethod + def _has_arnold(): + """Return whether the arnold package is available and importable.""" + try: + import arnold # noqa: F401 + return True + except (ImportError, ModuleNotFoundError): + return False + + class ExtractLook(publish.Extractor): """Extract Look (Maya Scene + JSON) @@ -179,22 +400,6 @@ class ExtractLook(publish.Extractor): scene_type = "ma" look_data_type = "json" - @staticmethod - def get_renderer_name(): - """Get renderer name from Maya. - - Returns: - str: Renderer name. - - """ - renderer = cmds.getAttr( - "defaultRenderGlobals.currentRenderer" - ).lower() - # handle various renderman names - if renderer.startswith("renderman"): - renderer = "renderman" - return renderer - def get_maya_scene_type(self, instance): """Get Maya scene type from settings. @@ -234,16 +439,12 @@ class ExtractLook(publish.Extractor): dir_path = self.staging_dir(instance) maya_fname = "{0}.{1}".format(instance.name, self.scene_type) json_fname = "{0}.{1}".format(instance.name, self.look_data_type) - - # Make texture dump folder maya_path = os.path.join(dir_path, maya_fname) json_path = os.path.join(dir_path, json_fname) - self.log.info("Performing extraction..") - # Remove all members of the sets so they are not included in the # exported file by accident - self.log.info("Extract sets (%s) ..." % _scene_type) + self.log.info("Processing sets..") lookdata = instance.data["lookData"] relationships = lookdata["relationships"] sets = list(relationships.keys()) @@ -251,13 +452,36 @@ class ExtractLook(publish.Extractor): self.log.info("No sets found") return - results = self.process_resources(instance, staging_dir=dir_path) + # Specify texture processing executables to activate + # TODO: Load these more dynamically once we support more processors + processors = [] + context = instance.context + for key, Processor in { + # Instance data key to texture processor mapping + "maketx": MakeTX, + "rstex": MakeRSTexBin + }.items(): + if instance.data.get(key, False): + processor = Processor() + processor.apply_settings(context.data["system_settings"], + context.data["project_settings"]) + processors.append(processor) + + if processors: + self.log.debug("Collected texture processors: " + "{}".format(processors)) + + self.log.debug("Processing resources..") + results = self.process_resources(instance, + staging_dir=dir_path, + processors=processors) transfers = results["fileTransfers"] hardlinks = results["fileHardlinks"] hashes = results["fileHashes"] remap = results["attrRemap"] # Extract in correct render layer + self.log.info("Extracting look maya scene file: {}".format(maya_path)) layer = instance.data.get("renderlayer", "defaultRenderLayer") with lib.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides @@ -265,7 +489,7 @@ class ExtractLook(publish.Extractor): # To avoid Maya trying to automatically remap the file # textures relative to the `workspace -directory` we force # it to a fake temporary workspace. This fixes textures - # getting incorrectly remapped. (LKD-17, PLN-101) + # getting incorrectly remapped. with no_workspace_dir(): with lib.attribute_values(remap): with lib.maintained_selection(): @@ -329,40 +553,38 @@ class ExtractLook(publish.Extractor): # Source hash for the textures instance.data["sourceHashes"] = hashes - """ - self.log.info("Returning colorspaces to their original values ...") - for attr, value in remap.items(): - self.log.info(" - {}: {}".format(attr, value)) - cmds.setAttr(attr, value, type="string") - """ self.log.info("Extracted instance '%s' to: %s" % (instance.name, maya_path)) - def process_resources(self, instance, staging_dir): + def _set_resource_result_colorspace(self, resource, colorspace): + """Update resource resulting colorspace after texture processing""" + if "result_color_space" in resource: + if resource["result_color_space"] == colorspace: + return + + self.log.warning( + "Resource already has a resulting colorspace but is now " + "being overridden to a new one: {} -> {}".format( + resource["result_color_space"], colorspace + ) + ) + resource["result_color_space"] = colorspace + + def process_resources(self, instance, staging_dir, processors): + """Process all resources in the instance. + + It is assumed that all resources are nodes using file textures. + + Extract the textures to transfer, possibly convert with maketx and + remap the node paths to the destination path. Note that a source + might be included more than once amongst the resources as they could + be the input file to multiple nodes. + + """ - # Extract the textures to transfer, possibly convert with maketx and - # remap the node paths to the destination path. Note that a source - # might be included more than once amongst the resources as they could - # be the input file to multiple nodes. resources = instance.data["resources"] - do_maketx = instance.data.get("maketx", False) + color_management = lib.get_color_management_preferences() - # Collect all unique files used in the resources - files_metadata = {} - for resource in resources: - # Preserve color space values (force value after filepath change) - # This will also trigger in the same order at end of context to - # ensure after context it's still the original value. - color_space = resource.get("color_space") - - for f in resource["files"]: - files_metadata[os.path.normpath(f)] = { - "color_space": color_space} - - # Process the resource files - transfers = [] - hardlinks = [] - hashes = {} # Temporary fix to NOT create hardlinks on windows machines if platform.system().lower() == "windows": self.log.info( @@ -372,95 +594,114 @@ class ExtractLook(publish.Extractor): else: force_copy = instance.data.get("forceCopy", False) - for filepath in files_metadata: + destinations_cache = {} - linearize = False - # if OCIO color management enabled - # it won't take the condition of the files_metadata + def get_resource_destination_cached(path): + """Get resource destination with cached result per filepath""" + if path not in destinations_cache: + destination = self.get_resource_destination( + path, instance.data["resourcesDir"], processors) + destinations_cache[path] = destination + return destinations_cache[path] - ocio_maya = cmds.colorManagementPrefs(q=True, - cmConfigFileEnabled=True, - cmEnabled=True) - - if do_maketx and not ocio_maya: - if files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501 - linearize = True - # set its file node to 'raw' as tx will be linearized - files_metadata[filepath]["color_space"] = "Raw" - - # if do_maketx: - # color_space = "Raw" - - source, mode, texture_hash = self._process_texture( - filepath, - resource, - do_maketx, - staging=staging_dir, - linearize=linearize, - force=force_copy - ) - destination = self.resource_destination(instance, - source, - do_maketx) - - # Force copy is specified. - if force_copy: - mode = COPY - - if mode == COPY: - transfers.append((source, destination)) - self.log.info('file will be copied {} -> {}'.format( - source, destination)) - elif mode == HARDLINK: - hardlinks.append((source, destination)) - self.log.info('file will be hardlinked {} -> {}'.format( - source, destination)) - - # Store the hashes from hash to destination to include in the - # database - hashes[texture_hash] = destination - - # Remap the resources to the destination path (change node attributes) - destinations = {} - remap = OrderedDict() # needs to be ordered, see color space values + # Process all resource's individual files + processed_files = {} + transfers = [] + hardlinks = [] + hashes = {} + remap = OrderedDict() for resource in resources: - source = os.path.normpath(resource["source"]) - if source not in destinations: - # Cache destination as source resource might be included - # multiple times - destinations[source] = self.resource_destination( - instance, source, do_maketx + colorspace = resource["color_space"] + + for filepath in resource["files"]: + filepath = os.path.normpath(filepath) + + if filepath in processed_files: + # The file was already processed, likely due to usage by + # another resource in the scene. We confirm here it + # didn't do color spaces different than the current + # resource. + processed_file = processed_files[filepath] + self.log.debug( + "File was already processed. Likely used by another " + "resource too: {}".format(filepath) + ) + + if colorspace != processed_file["color_space"]: + self.log.warning( + "File '{}' was already processed using colorspace " + "'{}' instead of the current resource's " + "colorspace '{}'. The already processed texture " + "result's colorspace '{}' will be used." + "".format(filepath, + colorspace, + processed_file["color_space"], + processed_file["result_color_space"])) + + self._set_resource_result_colorspace( + resource, + colorspace=processed_file["result_color_space"] + ) + continue + + texture_result = self._process_texture( + filepath, + processors=processors, + staging_dir=staging_dir, + force_copy=force_copy, + color_management=color_management, + colorspace=colorspace ) + # Set the resulting color space on the resource + self._set_resource_result_colorspace( + resource, colorspace=texture_result.colorspace + ) + + processed_files[filepath] = { + "color_space": colorspace, + "result_color_space": texture_result.colorspace, + } + + source = texture_result.path + destination = get_resource_destination_cached(source) + if force_copy or texture_result.transfer_mode == COPY: + transfers.append((source, destination)) + self.log.info('file will be copied {} -> {}'.format( + source, destination)) + elif texture_result.transfer_mode == HARDLINK: + hardlinks.append((source, destination)) + self.log.info('file will be hardlinked {} -> {}'.format( + source, destination)) + + # Store the hashes from hash to destination to include in the + # database + hashes[texture_result.file_hash] = destination + + # Set up remapping attributes for the node during the publish + # The order of these can be important if one attribute directly + # affects another, e.g. we set colorspace after filepath because + # maya sometimes tries to guess the colorspace when changing + # filepaths (which is avoidable, but we don't want to have those + # attributes changed in the resulting publish) + # Remap filepath to publish destination + # TODO It would be much better if we could use the destination path + # from the actual processed texture results, but since the + # attribute will need to preserve tokens like , etc for + # now we will define the output path from the attribute value + # including the tokens to persist them. + filepath_attr = resource["attribute"] + remap[filepath_attr] = get_resource_destination_cached( + resource["source"] + ) + # Preserve color space values (force value after filepath change) # This will also trigger in the same order at end of context to # ensure after context it's still the original value. - color_space_attr = resource["node"] + ".colorSpace" - try: - color_space = cmds.getAttr(color_space_attr) - except ValueError: - # node doesn't have color space attribute - color_space = "Raw" - else: - # get the resolved files - metadata = files_metadata.get(source) - # if the files are unresolved from `source` - # assume color space from the first file of - # the resource - if not metadata: - first_file = next(iter(resource.get( - "files", [])), None) - if not first_file: - continue - first_filepath = os.path.normpath(first_file) - metadata = files_metadata[first_filepath] - if metadata["color_space"] == "Raw": - # set color space to raw if we linearized it - color_space = "Raw" - # Remap file node filename to destination - remap[color_space_attr] = color_space - attr = resource["attribute"] - remap[attr] = destinations[source] + node = resource["node"] + if cmds.attributeQuery("colorSpace", node=node, exists=True): + color_space_attr = "{}.colorSpace".format(node) + remap[color_space_attr] = resource["result_color_space"] self.log.info("Finished remapping destinations ...") @@ -471,134 +712,131 @@ class ExtractLook(publish.Extractor): "attrRemap": remap, } - def resource_destination(self, instance, filepath, do_maketx): + def get_resource_destination(self, filepath, resources_dir, processors): """Get resource destination path. This is utility function to change path if resource file name is changed by some external tool like `maketx`. Args: - instance: Current Instance. - filepath (str): Resource path - do_maketx (bool): Flag if resource is processed by `maketx`. + filepath (str): Resource source path + resources_dir (str): Destination dir for resources in publish. + processors (list): Texture processors converting resource. Returns: str: Path to resource file """ - resources_dir = instance.data["resourcesDir"] - # Compute destination location basename, ext = os.path.splitext(os.path.basename(filepath)) - # If `maketx` then the texture will always end with .tx - if do_maketx: - ext = ".tx" + # Get extension from the last processor + for processor in reversed(processors): + processor_ext = processor.extension + if processor_ext and ext != processor_ext: + self.log.debug("Processor {} overrides extension to '{}' " + "for path: {}".format(processor, + processor_ext, + filepath)) + ext = processor_ext + break return os.path.join( resources_dir, basename + ext ) - def _process_texture(self, filepath, resource, - do_maketx, staging, linearize, force): - """Process a single texture file on disk for publishing. - This will: - 1. Check whether it's already published, if so it will do hardlink - 2. If not published and maketx is enabled, generate a new .tx file. - 3. Compute the destination path for the source file. - Args: - filepath (str): The source file path to process. - do_maketx (bool): Whether to produce a .tx file - Returns: - """ - - fname, ext = os.path.splitext(os.path.basename(filepath)) - - args = [] - if do_maketx: - args.append("maketx") - texture_hash = source_hash(filepath, *args) + def _get_existing_hashed_texture(self, texture_hash): + """Return the first found filepath from a texture hash""" # If source has been published before with the same settings, # then don't reprocess but hardlink from the original existing = find_paths_by_hash(texture_hash) - if existing and not force: - self.log.info("Found hash in database, preparing hardlink..") + if existing: source = next((p for p in existing if os.path.exists(p)), None) if source: - return source, HARDLINK, texture_hash + return source else: self.log.warning( - ("Paths not found on disk, " - "skipping hardlink: %s") % (existing,) + "Paths not found on disk, " + "skipping hardlink: {}".format(existing) ) - if do_maketx and ext != ".tx": - # Produce .tx file in staging if source file is not .tx - converted = os.path.join(staging, "resources", fname + ".tx") - additional_args = [ - "--sattrib", - "sourceHash", - texture_hash - ] - if linearize: - if cmds.colorManagementPrefs(query=True, cmEnabled=True): - render_colorspace = cmds.colorManagementPrefs(query=True, - renderingSpaceName=True) # noqa - config_path = cmds.colorManagementPrefs(query=True, - configFilePath=True) # noqa - if not os.path.exists(config_path): - raise RuntimeError("No OCIO config path found!") + def _process_texture(self, + filepath, + processors, + staging_dir, + force_copy, + color_management, + colorspace): + """Process a single texture file on disk for publishing. - color_space_attr = resource["node"] + ".colorSpace" - try: - color_space = cmds.getAttr(color_space_attr) - except ValueError: - # node doesn't have color space attribute - if _has_arnold(): - img_info = image_info(filepath) - color_space = guess_colorspace(img_info) - else: - color_space = "Raw" - self.log.info("tx: converting {0} -> {1}".format(color_space, render_colorspace)) # noqa + This will: + 1. Check whether it's already published, if so it will do hardlink + (if the texture hash is found and force copy is not enabled) + 2. It will process the texture using the supplied texture + processors like MakeTX and MakeRSTexBin if enabled. + 3. Compute the destination path for the source file. - additional_args.extend(["--colorconvert", - color_space, - render_colorspace]) - else: + Args: + filepath (str): The source file path to process. + processors (list): List of TextureProcessor processing the texture + staging_dir (str): The staging directory to write to. + force_copy (bool): Whether to force a copy even if a file hash + might have existed already in the project, otherwise + hardlinking the existing file is allowed. + color_management (dict): Maya's Color Management settings from + `lib.get_color_management_preferences` + colorspace (str): The source colorspace of the resources this + texture belongs to. - if _has_arnold(): - img_info = image_info(filepath) - color_space = guess_colorspace(img_info) - if color_space == "sRGB": - self.log.info("tx: converting sRGB -> linear") - additional_args.extend(["--colorconvert", - "sRGB", - "Raw"]) - else: - self.log.info("tx: texture's colorspace " - "is already linear") - else: - self.log.warning("cannot guess the colorspace" - "color conversion won't be available!") # noqa + Returns: + TextureResult: The texture result information. + """ - - additional_args.extend(["--colorconfig", config_path]) - # Ensure folder exists - if not os.path.exists(os.path.dirname(converted)): - os.makedirs(os.path.dirname(converted)) - - self.log.info("Generating .tx file for %s .." % filepath) - maketx( - filepath, - converted, - additional_args, - self.log + if len(processors) > 1: + raise KnownPublishError( + "More than one texture processor not supported. " + "Current processors enabled: {}".format(processors) ) - return converted, COPY, texture_hash + for processor in processors: + self.log.debug("Processing texture {} with processor {}".format( + filepath, processor + )) - return filepath, COPY, texture_hash + processed_result = processor.process(filepath, + colorspace, + color_management, + staging_dir) + if not processed_result: + raise RuntimeError("Texture Processor {} returned " + "no result.".format(processor)) + self.log.info("Generated processed " + "texture: {}".format(processed_result.path)) + + # TODO: Currently all processors force copy instead of allowing + # hardlinks using source hashes. This should be refactored + return processed_result + + # No texture processing for this file + texture_hash = source_hash(filepath) + if not force_copy: + existing = self._get_existing_hashed_texture(filepath) + if existing: + self.log.info("Found hash in database, preparing hardlink..") + return TextureResult( + path=filepath, + file_hash=texture_hash, + colorspace=colorspace, + transfer_mode=HARDLINK + ) + + return TextureResult( + path=filepath, + file_hash=texture_hash, + colorspace=colorspace, + transfer_mode=COPY + ) class ExtractModelRenderSets(ExtractLook): diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index 0628623e88..cf610ac6b4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -102,7 +102,7 @@ class ExtractMultiverseUsdOverride(publish.Extractor): long=True) self.log.info("Collected object {}".format(members)) - # TODO: Deal with asset, composition, overide with options. + # TODO: Deal with asset, composition, override with options. import multiverse time_opts = None diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 94571ff731..0f3425a1de 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,5 +1,6 @@ import os import json +import contextlib import clique import capture @@ -8,7 +9,16 @@ from openpype.pipeline import publish from openpype.hosts.maya.api import lib from maya import cmds -import pymel.core as pm + + +@contextlib.contextmanager +def panel_camera(panel, camera): + original_camera = cmds.modelPanel(panel, query=True, camera=True) + try: + cmds.modelPanel(panel, edit=True, camera=camera) + yield + finally: + cmds.modelPanel(panel, edit=True, camera=original_camera) class ExtractPlayblast(publish.Extractor): @@ -25,6 +35,16 @@ class ExtractPlayblast(publish.Extractor): optional = True capture_preset = {} + def _capture(self, preset): + self.log.info( + "Using preset:\n{}".format( + json.dumps(preset, sort_keys=True, indent=4) + ) + ) + + path = capture.capture(log=self.log, **preset) + self.log.debug("playblast path {}".format(path)) + def process(self, instance): self.log.info("Extracting capture..") @@ -43,7 +63,7 @@ class ExtractPlayblast(publish.Extractor): self.log.info("start: {}, end: {}".format(start, end)) # get cameras - camera = instance.data['review_camera'] + camera = instance.data["review_camera"] preset = lib.load_capture_preset(data=self.capture_preset) # Grab capture presets from the project settings @@ -57,23 +77,23 @@ class ExtractPlayblast(publish.Extractor): asset_height = asset_data.get("resolutionHeight") review_instance_width = instance.data.get("review_width") review_instance_height = instance.data.get("review_height") - preset['camera'] = camera + preset["camera"] = camera # Tests if project resolution is set, # if it is a value other than zero, that value is # used, if not then the asset resolution is # used if review_instance_width and review_instance_height: - preset['width'] = review_instance_width - preset['height'] = review_instance_height + preset["width"] = review_instance_width + preset["height"] = review_instance_height elif width_preset and height_preset: - preset['width'] = width_preset - preset['height'] = height_preset + preset["width"] = width_preset + preset["height"] = height_preset elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height - preset['start_frame'] = start - preset['end_frame'] = end + preset["width"] = asset_width + preset["height"] = asset_height + preset["start_frame"] = start + preset["end_frame"] = end # Enforce persisting camera depth of field camera_options = preset.setdefault("camera_options", {}) @@ -86,14 +106,14 @@ class ExtractPlayblast(publish.Extractor): self.log.info("Outputting images to %s" % path) - preset['filename'] = path - preset['overwrite'] = True + preset["filename"] = path + preset["overwrite"] = True - pm.refresh(f=True) + cmds.refresh(force=True) - refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True)) - pm.currentTime(refreshFrameInt - 1, edit=True) - pm.currentTime(refreshFrameInt, edit=True) + refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True)) + cmds.currentTime(refreshFrameInt - 1, edit=True) + cmds.currentTime(refreshFrameInt, edit=True) # Override transparency if requested. transparency = instance.data.get("transparency", 0) @@ -114,7 +134,8 @@ class ExtractPlayblast(publish.Extractor): # Disable Pan/Zoom. pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + preset.pop("pan_zoom", None) + preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"] # Need to explicitly enable some viewport changes so the viewport is # refreshed ahead of playblasting. @@ -136,30 +157,39 @@ class ExtractPlayblast(publish.Extractor): ) override_viewport_options = ( - capture_presets['Viewport Options']['override_viewport_options'] + capture_presets["Viewport Options"]["override_viewport_options"] ) - with lib.maintained_time(): - filename = preset.get("filename", "%TEMP%") - # Force viewer to False in call to capture because we have our own - # viewer opening call to allow a signal to trigger between - # playblast and viewer - preset['viewer'] = False + # Force viewer to False in call to capture because we have our own + # viewer opening call to allow a signal to trigger between + # playblast and viewer + preset["viewer"] = False - # Update preset with current panel setting - # if override_viewport_options is turned off - if not override_viewport_options: - panel_preset = capture.parse_view(instance.data["panel"]) - panel_preset.pop("camera") - preset.update(panel_preset) + # Update preset with current panel setting + # if override_viewport_options is turned off + if not override_viewport_options: + panel_preset = capture.parse_view(instance.data["panel"]) + panel_preset.pop("camera") + preset.update(panel_preset) - self.log.info( - "Using preset:\n{}".format( - json.dumps(preset, sort_keys=True, indent=4) + # Need to ensure Python 2 compatibility. + # TODO: Remove once dropping Python 2. + if getattr(contextlib, "nested", None): + # Python 3 compatibility. + with contextlib.nested( + lib.maintained_time(), + panel_camera(instance.data["panel"], preset["camera"]) + ): + self._capture(preset) + else: + # Python 2 compatibility. + with contextlib.ExitStack() as stack: + stack.enter_context(lib.maintained_time()) + stack.enter_context( + panel_camera(instance.data["panel"], preset["camera"]) ) - ) - path = capture.capture(log=self.log, **preset) + self._capture(preset) # Restoring viewport options. if viewport_defaults: @@ -169,18 +199,17 @@ class ExtractPlayblast(publish.Extractor): cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) - self.log.debug("playblast path {}".format(path)) - collected_files = os.listdir(stagingdir) patterns = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble(collected_files, minimum_items=1, patterns=patterns) + filename = preset.get("filename", "%TEMP%") self.log.debug("filename {}".format(filename)) frame_collection = None for collection in collections: - filebase = collection.format('{head}').rstrip(".") + filebase = collection.format("{head}").rstrip(".") self.log.debug("collection head {}".format(filebase)) if filebase in filename: frame_collection = collection @@ -196,7 +225,7 @@ class ExtractPlayblast(publish.Extractor): tags.append("delete") # Add camera node name to representation data - camera_node_name = pm.ls(camera)[0].getTransform().name() + camera_node_name = cmds.listRelatives(camera, parent=True)[0] collected_files = list(frame_collection) # single frame file shouldn't be in list, only as a string @@ -204,15 +233,14 @@ class ExtractPlayblast(publish.Extractor): collected_files = collected_files[0] representation = { - 'name': 'png', - 'ext': 'png', - 'files': collected_files, + "name": self.capture_preset["Codec"]["compression"], + "ext": self.capture_preset["Codec"]["compression"], + "files": collected_files, "stagingDir": stagingdir, "frameStart": start, "frameEnd": end, - 'fps': fps, - 'preview': True, - 'tags': tags, - 'camera_name': camera_node_name + "fps": fps, + "tags": tags, + "camera_name": camera_node_name } instance.data["representations"].append(representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 1d94bd58c5..b4ed8dce4c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -8,7 +8,6 @@ from openpype.pipeline import publish from openpype.hosts.maya.api import lib from maya import cmds -import pymel.core as pm class ExtractThumbnail(publish.Extractor): @@ -26,28 +25,28 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): self.log.info("Extracting capture..") - camera = instance.data['review_camera'] + camera = instance.data["review_camera"] - capture_preset = ( - instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']['capture_preset'] - ) + maya_setting = instance.context.data["project_settings"]["maya"] + plugin_setting = maya_setting["publish"]["ExtractPlayblast"] + capture_preset = plugin_setting["capture_preset"] override_viewport_options = ( - capture_preset['Viewport Options']['override_viewport_options'] + capture_preset["Viewport Options"]["override_viewport_options"] ) try: preset = lib.load_capture_preset(data=capture_preset) except KeyError as ke: - self.log.error('Error loading capture presets: {}'.format(str(ke))) + self.log.error("Error loading capture presets: {}".format(str(ke))) preset = {} - self.log.info('Using viewport preset: {}'.format(preset)) + self.log.info("Using viewport preset: {}".format(preset)) # preset["off_screen"] = False - preset['camera'] = camera - preset['start_frame'] = instance.data["frameStart"] - preset['end_frame'] = instance.data["frameStart"] - preset['camera_options'] = { + preset["camera"] = camera + preset["start_frame"] = instance.data["frameStart"] + preset["end_frame"] = instance.data["frameStart"] + preset["camera_options"] = { "displayGateMask": False, "displayResolution": False, "displayFilmGate": False, @@ -74,14 +73,14 @@ class ExtractThumbnail(publish.Extractor): # used, if not then the asset resolution is # used if review_instance_width and review_instance_height: - preset['width'] = review_instance_width - preset['height'] = review_instance_height + preset["width"] = review_instance_width + preset["height"] = review_instance_height elif width_preset and height_preset: - preset['width'] = width_preset - preset['height'] = height_preset + preset["width"] = width_preset + preset["height"] = height_preset elif asset_width and asset_height: - preset['width'] = asset_width - preset['height'] = asset_height + preset["width"] = asset_width + preset["height"] = asset_height # Create temp directory for thumbnail # - this is to avoid "override" of source file @@ -96,14 +95,14 @@ class ExtractThumbnail(publish.Extractor): self.log.info("Outputting images to %s" % path) - preset['filename'] = path - preset['overwrite'] = True + preset["filename"] = path + preset["overwrite"] = True - pm.refresh(f=True) + cmds.refresh(force=True) - refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True)) - pm.currentTime(refreshFrameInt - 1, edit=True) - pm.currentTime(refreshFrameInt, edit=True) + refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True)) + cmds.currentTime(refreshFrameInt - 1, edit=True) + cmds.currentTime(refreshFrameInt, edit=True) # Override transparency if requested. transparency = instance.data.get("transparency", 0) @@ -123,14 +122,14 @@ class ExtractThumbnail(publish.Extractor): preset["viewport_options"] = {"imagePlane": image_plane} # Disable Pan/Zoom. - pan_zoom = cmds.getAttr("{}.panZoomEnabled".format(preset["camera"])) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), False) + preset.pop("pan_zoom", None) + preset["camera_options"]["panZoomEnabled"] = instance.data["panZoom"] with lib.maintained_time(): # Force viewer to False in call to capture because we have our own # viewer opening call to allow a signal to trigger between # playblast and viewer - preset['viewer'] = False + preset["viewer"] = False # Update preset with current panel setting # if override_viewport_options is turned off @@ -145,17 +144,15 @@ class ExtractThumbnail(publish.Extractor): _, thumbnail = os.path.split(playblast) - cmds.setAttr("{}.panZoomEnabled".format(preset["camera"]), pan_zoom) - self.log.info("file list {}".format(thumbnail)) if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'thumbnail', - 'ext': 'jpg', - 'files': thumbnail, + "name": "thumbnail", + "ext": "jpg", + "files": thumbnail, "stagingDir": dst_staging, "thumbnail": True } diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index 9b10d2737d..df16c6c357 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -30,9 +30,7 @@ class ExtractVRayProxy(publish.Extractor): # non-animated subsets keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", - "frameStartHandle", "frameEndHandle", - # Backwards compatibility - "handles"] + "frameStartHandle", "frameEndHandle"] for key in keys: instance.data.pop(key, None) diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py index b90885663c..d8e8554b68 100644 --- a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -30,7 +30,7 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): cmds.setAttr(palette + ".xgExportAsDelta", True) # Need to save the scene, cause the attribute changes above does not - # mark the scene as modified so user can exit without commiting the + # mark the scene as modified so user can exit without committing the # changes. self.log.info("Saving changes.") cmds.file(save=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py index 3b0ffd52d7..7055dc145e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -1,5 +1,3 @@ -import maya.cmds as cmds - import pyblish.api from openpype.pipeline.publish import ( ValidateContentsOrder, PublishValidationError @@ -22,10 +20,11 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): families = ["ass"] label = "Validate Arnold Scene Source" - def _get_nodes_data(self, nodes): + def _get_nodes_by_name(self, nodes): ungrouped_nodes = [] nodes_by_name = {} parents = [] + same_named_nodes = {} for node in nodes: node_split = node.split("|") if len(node_split) == 2: @@ -35,21 +34,38 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): if parent: parents.append(parent) - nodes_by_name[node_split[-1]] = node - for shape in cmds.listRelatives(node, shapes=True): - nodes_by_name[shape.split("|")[-1]] = shape + node_name = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + + # Check for same same nodes, which can happen in different + # hierarchies. + if node_name in nodes_by_name: + try: + same_named_nodes[node_name].append(node) + except KeyError: + same_named_nodes[node_name] = [ + nodes_by_name[node_name], node + ] + + nodes_by_name[node_name] = node + + if same_named_nodes: + message = "Found nodes with the same name:" + for name, nodes in same_named_nodes.items(): + message += "\n\n\"{}\":\n{}".format(name, "\n".join(nodes)) + + raise PublishValidationError(message) return ungrouped_nodes, nodes_by_name, parents def process(self, instance): ungrouped_nodes = [] - nodes, content_nodes_by_name, content_parents = self._get_nodes_data( - instance.data["setMembers"] + nodes, content_nodes_by_name, content_parents = ( + self._get_nodes_by_name(instance.data["contentMembers"]) ) ungrouped_nodes.extend(nodes) - nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_data( + nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_by_name( instance.data.get("proxy", []) ) ungrouped_nodes.extend(nodes) @@ -66,11 +82,11 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): return # Validate for content and proxy nodes amount being the same. - if len(instance.data["setMembers"]) != len(instance.data["proxy"]): + if len(instance.data["contentMembers"]) != len(instance.data["proxy"]): raise PublishValidationError( "Amount of content nodes ({}) and proxy nodes ({}) needs to " "be the same.".format( - len(instance.data["setMembers"]), + len(instance.data["contentMembers"]), len(instance.data["proxy"]) ) ) diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py new file mode 100644 index 0000000000..e27723e104 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py @@ -0,0 +1,74 @@ +import pyblish.api +from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishValidationError, RepairAction +) + + +class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin): + """Validate Arnold Scene Source Cbid. + + It is required for the proxy and content nodes to share the same cbid. + """ + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["ass"] + label = "Validate Arnold Scene Source CBID" + actions = [RepairAction] + + @staticmethod + def _get_nodes_by_name(nodes): + nodes_by_name = {} + for node in nodes: + node_name = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + nodes_by_name[node_name] = node + + return nodes_by_name + + @classmethod + def get_invalid_couples(cls, instance): + content_nodes_by_name = cls._get_nodes_by_name( + instance.data["contentMembers"] + ) + proxy_nodes_by_name = cls._get_nodes_by_name( + instance.data.get("proxy", []) + ) + + invalid_couples = [] + for content_name, content_node in content_nodes_by_name.items(): + proxy_node = proxy_nodes_by_name.get(content_name, None) + + if not proxy_node: + cls.log.debug( + "Content node '{}' has no matching proxy node.".format( + content_node + ) + ) + continue + + content_id = lib.get_id(content_node) + proxy_id = lib.get_id(proxy_node) + if content_id != proxy_id: + invalid_couples.append((content_node, proxy_node)) + + return invalid_couples + + def process(self, instance): + # Proxy validation. + if not instance.data.get("proxy", []): + return + + # Validate for proxy nodes sharing the same cbId as content nodes. + invalid_couples = self.get_invalid_couples(instance) + if invalid_couples: + raise PublishValidationError( + "Found proxy nodes with mismatching cbid:\n{}".format( + invalid_couples + ) + ) + + @classmethod + def repair(cls, instance): + for content_node, proxy_node in cls.get_invalid_couples(cls, instance): + lib.set_id(proxy_node, lib.get_id(content_node), overwrite=False) diff --git a/openpype/hosts/maya/plugins/publish/validate_attributes.py b/openpype/hosts/maya/plugins/publish/validate_attributes.py index 7a1f0cf086..6ca9afb9a4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_attributes.py @@ -1,13 +1,17 @@ -import pymel.core as pm +from collections import defaultdict + +from maya import cmds import pyblish.api + +from openpype.hosts.maya.api.lib import set_attribute from openpype.pipeline.publish import ( RepairContextAction, ValidateContentsOrder, ) -class ValidateAttributes(pyblish.api.ContextPlugin): +class ValidateAttributes(pyblish.api.InstancePlugin): """Ensure attributes are consistent. Attributes to validate and their values comes from the @@ -27,86 +31,80 @@ class ValidateAttributes(pyblish.api.ContextPlugin): attributes = None - def process(self, context): + def process(self, instance): # Check for preset existence. - if not self.attributes: return - invalid = self.get_invalid(context, compute=True) + invalid = self.get_invalid(instance, compute=True) if invalid: raise RuntimeError( "Found attributes with invalid values: {}".format(invalid) ) @classmethod - def get_invalid(cls, context, compute=False): - invalid = context.data.get("invalid_attributes", []) + def get_invalid(cls, instance, compute=False): if compute: - invalid = cls.get_invalid_attributes(context) - - return invalid + return cls.get_invalid_attributes(instance) + else: + return instance.data.get("invalid_attributes", []) @classmethod - def get_invalid_attributes(cls, context): + def get_invalid_attributes(cls, instance): invalid_attributes = [] - for instance in context: - # Filter publisable instances. - if not instance.data["publish"]: + + # Filter families. + families = [instance.data["family"]] + families += instance.data.get("families", []) + families = set(families) & set(cls.attributes.keys()) + if not families: + return [] + + # Get all attributes to validate. + attributes = defaultdict(dict) + for family in families: + if family not in cls.attributes: + # No attributes to validate for family continue - # Filter families. - families = [instance.data["family"]] - families += instance.data.get("families", []) - families = list(set(families) & set(cls.attributes.keys())) - if not families: + for preset_attr, preset_value in cls.attributes[family].items(): + node_name, attribute_name = preset_attr.split(".", 1) + attributes[node_name][attribute_name] = preset_value + + if not attributes: + return [] + + # Get invalid attributes. + nodes = cmds.ls(long=True) + for node in nodes: + node_name = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + if node_name not in attributes: continue - # Get all attributes to validate. - attributes = {} - for family in families: - for preset in cls.attributes[family]: - [node_name, attribute_name] = preset.split(".") - try: - attributes[node_name].update( - {attribute_name: cls.attributes[family][preset]} - ) - except KeyError: - attributes.update({ - node_name: { - attribute_name: cls.attributes[family][preset] - } - }) + for attr_name, expected in attributes.items(): - # Get invalid attributes. - nodes = pm.ls() - for node in nodes: - name = node.name(stripNamespace=True) - if name not in attributes.keys(): + # Skip if attribute does not exist + if not cmds.attributeQuery(attr_name, node=node, exists=True): continue - presets_to_validate = attributes[name] - for attribute in node.listAttr(): - names = [attribute.shortName(), attribute.longName()] - attribute_name = list( - set(names) & set(presets_to_validate.keys()) + plug = "{}.{}".format(node, attr_name) + value = cmds.getAttr(plug) + if value != expected: + invalid_attributes.append( + { + "attribute": plug, + "expected": expected, + "current": value + } ) - if attribute_name: - expected = presets_to_validate[attribute_name[0]] - if attribute.get() != expected: - invalid_attributes.append( - { - "attribute": attribute, - "expected": expected, - "current": attribute.get() - } - ) - context.data["invalid_attributes"] = invalid_attributes + instance.data["invalid_attributes"] = invalid_attributes return invalid_attributes @classmethod def repair(cls, instance): invalid = cls.get_invalid(instance) for data in invalid: - data["attribute"].set(data["expected"]) + node, attr = data["attribute"].split(".", 1) + value = data["expected"] + set_attribute(node=node, attribute=attr, value=value) diff --git a/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py index bd1529e252..13ea53a357 100644 --- a/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py @@ -8,7 +8,7 @@ from openpype.pipeline.publish import ValidateContentsOrder class ValidateCameraAttributes(pyblish.api.InstancePlugin): """Validates Camera has no invalid attribute keys or values. - The Alembic file format does not a specifc subset of attributes as such + The Alembic file format does not a specific subset of attributes as such we validate that no values are set there as the output will not match the current scene. For example the preScale, film offsets and film roll. diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index 59b06874b3..ccb351c880 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -4,6 +4,7 @@ from maya import cmds from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) from openpype.hosts.maya.api.lib_rendersetup import ( get_attr_overrides, @@ -49,7 +50,6 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): frame_start_handle = int(context.data.get("frameStartHandle")) frame_end_handle = int(context.data.get("frameEndHandle")) - handles = int(context.data.get("handles")) handle_start = int(context.data.get("handleStart")) handle_end = int(context.data.get("handleEnd")) frame_start = int(context.data.get("frameStart")) @@ -66,8 +66,6 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): assert frame_start_handle <= frame_end_handle, ( "start frame is lower then end frame") - assert handles >= 0, ("handles cannot have negative values") - # compare with data on instance errors = [] if [ef for ef in self.exclude_families diff --git a/openpype/hosts/maya/plugins/publish/validate_look_color_space.py b/openpype/hosts/maya/plugins/publish/validate_look_color_space.py deleted file mode 100644 index b1bdeb7541..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_look_color_space.py +++ /dev/null @@ -1,26 +0,0 @@ -from maya import cmds - -import pyblish.api -from openpype.pipeline.publish import ValidateContentsOrder -from openpype.pipeline import PublishValidationError - - -class ValidateMayaColorSpace(pyblish.api.InstancePlugin): - """ - Check if the OCIO Color Management and maketx options - enabled at the same time - """ - - order = ValidateContentsOrder - families = ['look'] - hosts = ['maya'] - label = 'Color Management with maketx' - - def process(self, instance): - ocio_maya = cmds.colorManagementPrefs(q=True, - cmConfigFileEnabled=True, - cmEnabled=True) - maketx = instance.data["maketx"] - - if ocio_maya and maketx: - raise PublishValidationError("Maya is color managed and maketx option is on. OpenPype doesn't support this combination yet.") # noqa diff --git a/openpype/hosts/maya/plugins/publish/validate_look_contents.py b/openpype/hosts/maya/plugins/publish/validate_look_contents.py index 53501d11e5..2d38099f0f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_look_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_contents.py @@ -1,6 +1,7 @@ import pyblish.api import openpype.hosts.maya.api.action from openpype.pipeline.publish import ValidateContentsOrder +from maya import cmds # noqa class ValidateLookContents(pyblish.api.InstancePlugin): @@ -85,6 +86,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid.add(instance.name) return list(invalid) + @classmethod def validate_looks(cls, instance): @@ -112,3 +114,23 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid.append(node) return invalid + + @classmethod + def validate_renderer(cls, instance): + # TODO: Rewrite this to be more specific and configurable + renderer = cmds.getAttr( + 'defaultRenderGlobals.currentRenderer').lower() + do_maketx = instance.data.get("maketx", False) + do_rstex = instance.data.get("rstex", False) + processors = [] + + if do_maketx: + processors.append('arnold') + if do_rstex: + processors.append('redshift') + + for processor in processors: + if processor == renderer: + continue + else: + cls.log.error("Converted texture does not match current renderer.") # noqa diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index 357dde692c..011df0846c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -34,7 +34,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): fps = context.data.get('fps') - # TODO repace query with using 'context.data["assetEntity"]' + # TODO replace query with using 'context.data["assetEntity"]' asset_doc = get_current_project_asset() asset_fps = mayalib.convert_to_maya_fps(asset_doc["data"]["fps"]) @@ -86,7 +86,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): cls.log.debug(current_linear) cls.log.info("Setting time unit to match project") - # TODO repace query with using 'context.data["assetEntity"]' + # TODO replace query with using 'context.data["assetEntity"]' asset_doc = get_current_project_asset() asset_fps = asset_doc["data"]["fps"] mayalib.set_scene_fps(asset_fps) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py index fa4c66952c..a580a1c787 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -1,8 +1,14 @@ -import pymel.core as pc from maya import cmds import pyblish.api + import openpype.hosts.maya.api.action -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api.lib import ( + maintained_selection, + delete_after, + undo_chunk, + get_attribute, + set_attribute +) from openpype.pipeline.publish import ( RepairAction, ValidateMeshOrder, @@ -31,60 +37,68 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): else: active = False + @classmethod + def get_default_attributes(cls): + # Get default arnold attribute values for mesh type. + defaults = {} + with delete_after() as tmp: + transform = cmds.createNode("transform") + tmp.append(transform) + + mesh = cmds.createNode("mesh", parent=transform) + for attr in cmds.listAttr(mesh, string="ai*"): + plug = "{}.{}".format(mesh, attr) + try: + defaults[attr] = get_attribute(plug) + except RuntimeError: + cls.log.debug("Ignoring arnold attribute: {}".format(attr)) + + return defaults + @classmethod def get_invalid_attributes(cls, instance, compute=False): invalid = [] if compute: - # Get default arnold attributes. - temp_transform = pc.polyCube()[0] - for shape in pc.ls(instance, type="mesh"): - for attr in temp_transform.getShape().listAttr(): - if not attr.attrName().startswith("ai"): - continue + meshes = cmds.ls(instance, type="mesh", long=True) + if not meshes: + return [] - target_attr = pc.PyNode( - "{}.{}".format(shape.name(), attr.attrName()) - ) - if attr.get() != target_attr.get(): - invalid.append(target_attr) - - pc.delete(temp_transform) + # Compare the values against the defaults + defaults = cls.get_default_attributes() + for mesh in meshes: + for attr_name, default_value in defaults.items(): + plug = "{}.{}".format(mesh, attr_name) + if get_attribute(plug) != default_value: + invalid.append(plug) instance.data["nondefault_arnold_attributes"] = invalid - else: - invalid.extend(instance.data["nondefault_arnold_attributes"]) - return invalid + return instance.data.get("nondefault_arnold_attributes", []) @classmethod def get_invalid(cls, instance): - invalid = [] - - for attr in cls.get_invalid_attributes(instance, compute=False): - invalid.append(attr.node().name()) - - return invalid + invalid_attrs = cls.get_invalid_attributes(instance, compute=False) + invalid_nodes = set(attr.split(".", 1)[0] for attr in invalid_attrs) + return sorted(invalid_nodes) @classmethod def repair(cls, instance): with maintained_selection(): - with pc.UndoChunk(): - temp_transform = pc.polyCube()[0] - + with undo_chunk(): + defaults = cls.get_default_attributes() attributes = cls.get_invalid_attributes( instance, compute=False ) for attr in attributes: - source = pc.PyNode( - "{}.{}".format( - temp_transform.getShape(), attr.attrName() - ) + node, attr_name = attr.split(".", 1) + value = defaults[attr_name] + set_attribute( + node=node, + attribute=attr_name, + value=value ) - attr.set(source.get()) - - pc.delete(temp_transform) def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py index be23f61ec5..74269cc506 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py @@ -1,10 +1,11 @@ -import pyblish.api -import openpype.hosts.maya.api.action import math -import maya.api.OpenMaya as om -import pymel.core as pm - from six.moves import xrange + +from maya import cmds +import maya.api.OpenMaya as om +import pyblish.api + +import openpype.hosts.maya.api.action from openpype.pipeline.publish import ValidateMeshOrder @@ -185,8 +186,7 @@ class GetOverlappingUVs(object): center, radius = self._createBoundingCircle(meshfn) for i in xrange(meshfn.numPolygons): # noqa: F821 - rayb1, face1Orig, face1Vec = self._createRayGivenFace( - meshfn, i) + rayb1, face1Orig, face1Vec = self._createRayGivenFace(meshfn, i) if not rayb1: continue cui = center[2*i] @@ -206,8 +206,8 @@ class GetOverlappingUVs(object): if (dsqr >= (ri + rj) * (ri + rj)): continue - rayb2, face2Orig, face2Vec = self._createRayGivenFace( - meshfn, j) + rayb2, face2Orig, face2Vec = self._createRayGivenFace(meshfn, + j) if not rayb2: continue # Exclude the degenerate face @@ -240,37 +240,45 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin): optional = True @classmethod - def _get_overlapping_uvs(cls, node): - """ Check if mesh has overlapping UVs. + def _get_overlapping_uvs(cls, mesh): + """Return overlapping UVs of mesh. + + Args: + mesh (str): Mesh node name + + Returns: + list: Overlapping uvs for the input mesh in all uv sets. - :param node: node to check - :type node: str - :returns: True is has overlapping UVs, False otherwise - :rtype: bool """ ovl = GetOverlappingUVs() + # Store original uv set + original_current_uv_set = cmds.polyUVSet(mesh, + query=True, + currentUVSet=True) + overlapping_faces = [] - for i, uv in enumerate(pm.polyUVSet(node, q=1, auv=1)): - pm.polyUVSet(node, cuv=1, uvSet=uv) - overlapping_faces.extend(ovl._getOverlapUVFaces(str(node))) + for uv_set in cmds.polyUVSet(mesh, query=True, allUVSets=True): + cmds.polyUVSet(mesh, currentUVSet=True, uvSet=uv_set) + overlapping_faces.extend(ovl._getOverlapUVFaces(mesh)) + + # Restore original uv set + cmds.polyUVSet(mesh, currentUVSet=True, uvSet=original_current_uv_set) return overlapping_faces @classmethod def get_invalid(cls, instance, compute=False): - invalid = [] + if compute: - instance.data["overlapping_faces"] = [] - for node in pm.ls(instance, type="mesh"): + invalid = [] + for node in cmds.ls(instance, type="mesh"): faces = cls._get_overlapping_uvs(node) invalid.extend(faces) - # Store values for later. - instance.data["overlapping_faces"].extend(faces) - else: - invalid.extend(instance.data["overlapping_faces"]) - return invalid + instance.data["overlapping_faces"] = invalid + + return instance.data.get("overlapping_faces", []) def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py index e583c1edba..04db5a061b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -42,7 +42,8 @@ class ValidateMvLookContents(pyblish.api.InstancePlugin): resources = instance.data.get("resources", []) for resource in resources: files = resource["files"] - self.log.debug("Resouce '{}', files: [{}]".format(resource, files)) + self.log.debug( + "Resource '{}', files: [{}]".format(resource, files)) node = resource["node"] if len(files) == 0: self.log.error("File node '{}' uses no or non-existing " diff --git a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py index e91b99359d..0ff03f9165 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -1,4 +1,3 @@ -import pymel.core as pm import maya.cmds as cmds import pyblish.api @@ -12,7 +11,7 @@ import openpype.hosts.maya.api.action def get_namespace(node_name): # ensure only node's name (not parent path) - node_name = node_name.rsplit("|")[-1] + node_name = node_name.rsplit("|", 1)[-1] # ensure only namespace return node_name.rpartition(":")[0] @@ -45,13 +44,11 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): invalid = cls.get_invalid(instance) - # Get nodes with pymel since we'll be renaming them - # Since we don't want to keep checking the hierarchy - # or full paths - nodes = pm.ls(invalid) + # Iterate over the nodes by long to short names to iterate the lowest + # in hierarchy nodes first. This way we avoid having renamed parents + # before renaming children nodes + for node in sorted(invalid, key=len, reverse=True): - for node in nodes: - namespace = node.namespace() - if namespace: - name = node.nodeName() - node.rename(name[len(namespace):]) + node_name = node.rsplit("|", 1)[-1] + node_name_without_namespace = node_name.rsplit(":")[-1] + cmds.rename(node, node_name_without_namespace) diff --git a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index 6b6fb03eec..7919a6eaa1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -37,8 +37,8 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): project_name = legacy_io.active_project() asset_doc = instance.data["assetEntity"] - render_passses = instance.data.get("renderPasses", []) - for render_pass in render_passses: + render_passes = instance.data.get("renderPasses", []) + for render_pass in render_passes: is_valid = self.validate_subset_registered( project_name, asset_doc, render_pass ) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 53f340cd2c..ebf7b3138d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -275,15 +275,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # go through definitions and test if such node.attribute exists. # if so, compare its value from the one required. for attribute, data in cls.get_nodes(instance, renderer).items(): - # Validate the settings has values. - if not data["values"]: - cls.log.error( - "Settings for {}.{} is missing values.".format( - node, attribute - ) - ) - continue - for node in data["nodes"]: try: render_value = cmds.getAttr( @@ -316,6 +307,15 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): ) result = {} for attr, values in OrderedDict(validation_settings).items(): + values = [convert_to_int_or_float(v) for v in values if v] + + # Validate the settings has values. + if not values: + cls.log.error( + "Settings for {} is missing values.".format(attr) + ) + continue + cls.log.debug("{}: {}".format(attr, values)) if "." not in attr: cls.log.warning( @@ -324,8 +324,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): ) continue - values = [convert_to_int_or_float(v) for v in values] - node_type, attribute_name = attr.split(".", 1) # first get node of that type diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index f3ed1a36ef..499bfd4e37 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -1,14 +1,22 @@ -import pymel.core as pc +from collections import defaultdict + +from maya import cmds import pyblish.api import openpype.hosts.maya.api.action +from openpype.hosts.maya.api.lib import get_id, set_id from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, ) +def get_basename(node): + """Return node short name without namespace""" + return node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + + class ValidateRigOutputIds(pyblish.api.InstancePlugin): """Validate rig output ids. @@ -30,43 +38,48 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance, compute=False): - invalid = cls.get_invalid_matches(instance, compute=compute) - return [x["node"].longName() for x in invalid] + invalid_matches = cls.get_invalid_matches(instance, compute=compute) + return list(invalid_matches.keys()) @classmethod def get_invalid_matches(cls, instance, compute=False): - invalid = [] + invalid = {} if compute: out_set = next(x for x in instance if x.endswith("out_SET")) - instance_nodes = pc.sets(out_set, query=True) - instance_nodes.extend( - [x.getShape() for x in instance_nodes if x.getShape()]) - scene_nodes = pc.ls(type="transform") + pc.ls(type="mesh") + instance_nodes = cmds.sets(out_set, query=True, nodesOnly=True) + instance_nodes = cmds.ls(instance_nodes, long=True) + for node in instance_nodes: + shapes = cmds.listRelatives(node, shapes=True, fullPath=True) + if shapes: + instance_nodes.extend(shapes) + + scene_nodes = cmds.ls(type="transform") + cmds.ls(type="mesh") scene_nodes = set(scene_nodes) - set(instance_nodes) + scene_nodes_by_basename = defaultdict(list) + for node in scene_nodes: + basename = get_basename(node) + scene_nodes_by_basename[basename].append(node) + for instance_node in instance_nodes: - matches = [] - basename = instance_node.name(stripNamespace=True) - for scene_node in scene_nodes: - if scene_node.name(stripNamespace=True) == basename: - matches.append(scene_node) + basename = get_basename(instance_node) + if basename not in scene_nodes_by_basename: + continue - if matches: - ids = [instance_node.cbId.get()] - ids.extend([x.cbId.get() for x in matches]) - ids = set(ids) + matches = scene_nodes_by_basename[basename] - if len(ids) > 1: - cls.log.error( - "\"{}\" id mismatch to: {}".format( - instance_node.longName(), matches - ) - ) - invalid.append( - {"node": instance_node, "matches": matches} + ids = set(get_id(node) for node in matches) + ids.add(get_id(instance_node)) + + if len(ids) > 1: + cls.log.error( + "\"{}\" id mismatch to: {}".format( + instance_node.longName(), matches ) + ) + invalid[instance_node] = matches instance.data["mismatched_output_ids"] = invalid else: @@ -76,19 +89,21 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - invalid = cls.get_invalid_matches(instance) + invalid_matches = cls.get_invalid_matches(instance) multiple_ids_match = [] - for data in invalid: - ids = [x.cbId.get() for x in data["matches"]] + for instance_node, matches in invalid_matches.items(): + ids = set(get_id(node) for node in matches) # If there are multiple scene ids matched, and error needs to be # raised for manual correction. if len(ids) > 1: - multiple_ids_match.append(data) + multiple_ids_match.append({"node": instance_node, + "matches": matches}) continue - data["node"].cbId.set(ids[0]) + id_to_set = next(iter(ids)) + set_id(instance_node, id_to_set, overwrite=True) if multiple_ids_match: raise RuntimeError( diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index 0147aa8a52..b2a83a80fb 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -21,7 +21,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): - nurbsSurface: _NRB - locator: _LOC - null/group: _GRP - Suffices can also be overriden by project settings. + Suffices can also be overridden by project settings. .. warning:: This grabs the first child shape as a reference and doesn't use the diff --git a/openpype/hosts/maya/tools/mayalookassigner/app.py b/openpype/hosts/maya/tools/mayalookassigner/app.py index f9508657e5..2a8775fff6 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/app.py +++ b/openpype/hosts/maya/tools/mayalookassigner/app.py @@ -24,6 +24,7 @@ from .commands import ( remove_unused_looks ) from .vray_proxies import vrayproxy_assign_look +from . import arnold_standin module = sys.modules[__name__] module.window = None @@ -43,7 +44,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): filename = get_workfile() self.setObjectName("lookManager") - self.setWindowTitle("Look Manager 1.3.0 - [{}]".format(filename)) + self.setWindowTitle("Look Manager 1.4.0 - [{}]".format(filename)) self.setWindowFlags(QtCore.Qt.Window) self.setParent(parent) @@ -240,18 +241,37 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): )) nodes = item["nodes"] + # Assign Vray Proxy look. if cmds.pluginInfo('vrayformaya', query=True, loaded=True): self.echo("Getting vray proxy nodes ...") vray_proxies = set(cmds.ls(type="VRayProxy", long=True)) - if vray_proxies: - for vp in vray_proxies: - if vp in nodes: - vrayproxy_assign_look(vp, subset_name) + for vp in vray_proxies: + if vp in nodes: + vrayproxy_assign_look(vp, subset_name) - nodes = list(set(item["nodes"]).difference(vray_proxies)) + nodes = list(set(item["nodes"]).difference(vray_proxies)) + else: + self.echo( + "Could not assign to VRayProxy because vrayformaya plugin " + "is not loaded." + ) - # Assign look + # Assign Arnold Standin look. + if cmds.pluginInfo("mtoa", query=True, loaded=True): + arnold_standins = set(cmds.ls(type="aiStandIn", long=True)) + for standin in arnold_standins: + if standin in nodes: + arnold_standin.assign_look(standin, subset_name) + else: + self.echo( + "Could not assign to aiStandIn because mtoa plugin is not " + "loaded." + ) + + nodes = list(set(item["nodes"]).difference(arnold_standins)) + + # Assign look if nodes: assign_look_by_version(nodes, version_id=version["_id"]) diff --git a/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py new file mode 100644 index 0000000000..7eeeb72553 --- /dev/null +++ b/openpype/hosts/maya/tools/mayalookassigner/arnold_standin.py @@ -0,0 +1,247 @@ +import os +import json +from collections import defaultdict +import logging + +from maya import cmds + +from openpype.pipeline import legacy_io +from openpype.client import get_last_version_by_subset_name +from openpype.hosts.maya import api +from . import lib + + +log = logging.getLogger(__name__) + + +ATTRIBUTE_MAPPING = { + "primaryVisibility": "visibility", # Camera + "castsShadows": "visibility", # Shadow + "receiveShadows": "receive_shadows", + "aiSelfShadows": "self_shadows", + "aiOpaque": "opaque", + "aiMatte": "matte", + "aiVisibleInDiffuseTransmission": "visibility", + "aiVisibleInSpecularTransmission": "visibility", + "aiVisibleInVolume": "visibility", + "aiVisibleInDiffuseReflection": "visibility", + "aiVisibleInSpecularReflection": "visibility", + "aiSubdivUvSmoothing": "subdiv_uv_smoothing", + "aiDispHeight": "disp_height", + "aiDispPadding": "disp_padding", + "aiDispZeroValue": "disp_zero_value", + "aiStepSize": "step_size", + "aiVolumePadding": "volume_padding", + "aiSubdivType": "subdiv_type", + "aiSubdivIterations": "subdiv_iterations" +} + + +def calculate_visibility_mask(attributes): + # https://arnoldsupport.com/2018/11/21/backdoor-setting-visibility/ + mapping = { + "primaryVisibility": 1, # Camera + "castsShadows": 2, # Shadow + "aiVisibleInDiffuseTransmission": 4, + "aiVisibleInSpecularTransmission": 8, + "aiVisibleInVolume": 16, + "aiVisibleInDiffuseReflection": 32, + "aiVisibleInSpecularReflection": 64 + } + mask = 255 + for attr, value in mapping.items(): + if attributes.get(attr, True): + continue + + mask -= value + + return mask + + +def get_nodes_by_id(standin): + """Get node id from aiStandIn via json sidecar. + + Args: + standin (string): aiStandIn node. + + Returns: + (dict): Dictionary with node full name/path and id. + """ + path = cmds.getAttr(standin + ".dso") + json_path = None + for f in os.listdir(os.path.dirname(path)): + if f.endswith(".json"): + json_path = os.path.join(os.path.dirname(path), f) + break + + if not json_path: + log.warning("Could not find json file for {}.".format(standin)) + return {} + + with open(json_path, "r") as f: + return json.load(f) + + +def shading_engine_assignments(shading_engine, attribute, nodes, assignments): + """Full assignments with shader or disp_map. + + Args: + shading_engine (string): Shading engine for material. + attribute (string): "surfaceShader" or "displacementShader" + nodes: (list): Nodes paths relative to aiStandIn. + assignments (dict): Assignments by nodes. + """ + shader_inputs = cmds.listConnections( + shading_engine + "." + attribute, source=True + ) + if not shader_inputs: + log.info( + "Shading engine \"{}\" missing input \"{}\"".format( + shading_engine, attribute + ) + ) + return + + # Strip off component assignments + for i, node in enumerate(nodes): + if "." in node: + log.warning( + "Converting face assignment to full object assignment. This " + "conversion can be lossy: {}".format(node) + ) + nodes[i] = node.split(".")[0] + + shader_type = "shader" if attribute == "surfaceShader" else "disp_map" + assignment = "{}='{}'".format(shader_type, shader_inputs[0]) + for node in nodes: + assignments[node].append(assignment) + + +def assign_look(standin, subset): + log.info("Assigning {} to {}.".format(subset, standin)) + + nodes_by_id = get_nodes_by_id(standin) + + # Group by asset id so we run over the look per asset + node_ids_by_asset_id = defaultdict(set) + for node_id in nodes_by_id: + asset_id = node_id.split(":", 1)[0] + node_ids_by_asset_id[asset_id].add(node_id) + + project_name = legacy_io.active_project() + for asset_id, node_ids in node_ids_by_asset_id.items(): + + # Get latest look version + version = get_last_version_by_subset_name( + project_name, + subset_name=subset, + asset_id=asset_id, + fields=["_id"] + ) + if not version: + log.info("Didn't find last version for subset name {}".format( + subset + )) + continue + + relationships = lib.get_look_relationships(version["_id"]) + shader_nodes, container_node = lib.load_look(version["_id"]) + namespace = shader_nodes[0].split(":")[0] + + # Get only the node ids and paths related to this asset + # And get the shader edits the look supplies + asset_nodes_by_id = { + node_id: nodes_by_id[node_id] for node_id in node_ids + } + edits = list( + api.lib.iter_shader_edits( + relationships, shader_nodes, asset_nodes_by_id + ) + ) + + # Create assignments + node_assignments = {} + for edit in edits: + for node in edit["nodes"]: + if node not in node_assignments: + node_assignments[node] = [] + + if edit["action"] == "assign": + if not cmds.ls(edit["shader"], type="shadingEngine"): + log.info("Skipping non-shader: %s" % edit["shader"]) + continue + + shading_engine_assignments( + shading_engine=edit["shader"], + attribute="surfaceShader", + nodes=edit["nodes"], + assignments=node_assignments + ) + shading_engine_assignments( + shading_engine=edit["shader"], + attribute="displacementShader", + nodes=edit["nodes"], + assignments=node_assignments + ) + + if edit["action"] == "setattr": + visibility = False + for attr, value in edit["attributes"].items(): + if attr not in ATTRIBUTE_MAPPING: + log.warning( + "Skipping setting attribute {} on {} because it is" + " not recognized.".format(attr, edit["nodes"]) + ) + continue + + if isinstance(value, str): + value = "'{}'".format(value) + + if ATTRIBUTE_MAPPING[attr] == "visibility": + visibility = True + continue + + assignment = "{}={}".format(ATTRIBUTE_MAPPING[attr], value) + + for node in edit["nodes"]: + node_assignments[node].append(assignment) + + if visibility: + mask = calculate_visibility_mask(edit["attributes"]) + assignment = "visibility={}".format(mask) + + for node in edit["nodes"]: + node_assignments[node].append(assignment) + + # Assign shader + # Clear all current shader assignments + plug = standin + ".operators" + num = cmds.getAttr(plug, size=True) + for i in reversed(range(num)): + cmds.removeMultiInstance("{}[{}]".format(plug, i), b=True) + + # Create new assignment overrides + index = 0 + for node, assignments in node_assignments.items(): + if not assignments: + continue + + with api.lib.maintained_selection(): + operator = cmds.createNode("aiSetParameter") + operator = cmds.rename(operator, namespace + ":" + operator) + + cmds.setAttr(operator + ".selection", node, type="string") + for i, assignment in enumerate(assignments): + cmds.setAttr( + "{}.assignment[{}]".format(operator, i), + assignment, + type="string" + ) + + cmds.connectAttr( + operator + ".out", "{}[{}]".format(plug, index) + ) + + index += 1 + + cmds.sets(operator, edit=True, addElement=container_node) diff --git a/openpype/hosts/maya/tools/mayalookassigner/commands.py b/openpype/hosts/maya/tools/mayalookassigner/commands.py index 2e7a51efde..c5e6c973cf 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/commands.py +++ b/openpype/hosts/maya/tools/mayalookassigner/commands.py @@ -13,6 +13,7 @@ from openpype.pipeline import ( from openpype.hosts.maya.api import lib from .vray_proxies import get_alembic_ids_cache +from . import arnold_standin log = logging.getLogger(__name__) @@ -44,33 +45,11 @@ def get_namespace_from_node(node): return parts[0] if len(parts) > 1 else u":" -def list_descendents(nodes): - """Include full descendant hierarchy of given nodes. - - This is a workaround to cmds.listRelatives(allDescendents=True) because - this way correctly keeps children instance paths (see Maya documentation) - - This fixes LKD-26: assignments not working as expected on instanced shapes. - - Return: - list: List of children descendents of nodes - - """ - result = [] - while True: - nodes = cmds.listRelatives(nodes, - fullPath=True) - if nodes: - result.extend(nodes) - else: - return result - - def get_selected_nodes(): """Get information from current selection""" selection = cmds.ls(selection=True, long=True) - hierarchy = list_descendents(selection) + hierarchy = lib.get_all_children(selection) return list(set(selection + hierarchy)) @@ -80,21 +59,7 @@ def get_all_asset_nodes(): Returns: list: list of dictionaries """ - - host = registered_host() - - nodes = [] - for container in host.ls(): - # We are not interested in looks but assets! - if container["loader"] == "LookLoader": - continue - - # Gather all information - container_name = container["objectName"] - nodes += lib.get_container_members(container_name) - - nodes = list(set(nodes)) - return nodes + return cmds.ls(dag=True, noIntermediate=True, long=True) def create_asset_id_hash(nodes): @@ -119,10 +84,12 @@ def create_asset_id_hash(nodes): path = cmds.getAttr("{}.fileName".format(node)) ids = get_alembic_ids_cache(path) for k, _ in ids.items(): - pid = k.split(":")[0] - if node not in node_id_hash[pid]: - node_id_hash[pid].append(node) - + id = k.split(":")[0] + node_id_hash[id].append(node) + elif cmds.nodeType(node) == "aiStandIn": + for id, _ in arnold_standin.get_nodes_by_id(node).items(): + id = id.split(":")[0] + node_id_hash[id].append(node) else: value = lib.get_id(node) if value is None: diff --git a/openpype/hosts/maya/tools/mayalookassigner/lib.py b/openpype/hosts/maya/tools/mayalookassigner/lib.py new file mode 100644 index 0000000000..fddaf6112d --- /dev/null +++ b/openpype/hosts/maya/tools/mayalookassigner/lib.py @@ -0,0 +1,87 @@ +import json +import logging + +from openpype.pipeline import ( + legacy_io, + get_representation_path, + registered_host, + discover_loader_plugins, + loaders_from_representation, + load_container +) +from openpype.client import get_representation_by_name +from openpype.hosts.maya.api import lib + + +log = logging.getLogger(__name__) + + +def get_look_relationships(version_id): + # type: (str) -> dict + """Get relations for the look. + + Args: + version_id (str): Parent version Id. + + Returns: + dict: Dictionary of relations. + """ + + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, representation_name="json", version_id=version_id + ) + + # Load relationships + shader_relation = get_representation_path(json_representation) + with open(shader_relation, "r") as f: + relationships = json.load(f) + + return relationships + + +def load_look(version_id): + # type: (str) -> list + """Load look from version. + + Get look from version and invoke Loader for it. + + Args: + version_id (str): Version ID + + Returns: + list of shader nodes. + + """ + + project_name = legacy_io.active_project() + # Get representations of shader file and relationships + look_representation = get_representation_by_name( + project_name, representation_name="ma", version_id=version_id + ) + + # See if representation is already loaded, if so reuse it. + host = registered_host() + representation_id = str(look_representation['_id']) + for container in host.ls(): + if (container['loader'] == "LookLoader" and + container['representation'] == representation_id): + log.info("Reusing loaded look ...") + container_node = container['objectName'] + break + else: + log.info("Using look for the first time ...") + + # Load file + all_loaders = discover_loader_plugins() + loaders = loaders_from_representation(all_loaders, representation_id) + loader = next( + (i for i in loaders if i.__name__ == "LookLoader"), None) + if loader is None: + raise RuntimeError("Could not find LookLoader, this is a bug") + + # Reference the look file + with lib.maintained_selection(): + container_node = load_container(loader, look_representation)[0] + + return lib.get_container_members(container_node), container_node diff --git a/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py b/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py index 889396e555..1d2ec5fd87 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py +++ b/openpype/hosts/maya/tools/mayalookassigner/vray_proxies.py @@ -3,26 +3,16 @@ import os from collections import defaultdict import logging -import json import six import alembic.Abc from maya import cmds -from openpype.client import ( - get_representation_by_name, - get_last_version_by_subset_name, -) -from openpype.pipeline import ( - legacy_io, - load_container, - loaders_from_representation, - discover_loader_plugins, - get_representation_path, - registered_host, -) -from openpype.hosts.maya.api import lib +from openpype.client import get_last_version_by_subset_name +from openpype.pipeline import legacy_io +import openpype.hosts.maya.lib as maya_lib +from . import lib log = logging.getLogger(__name__) @@ -149,79 +139,6 @@ def assign_vrayproxy_shaders(vrayproxy, assignments): index += 1 -def get_look_relationships(version_id): - # type: (str) -> dict - """Get relations for the look. - - Args: - version_id (str): Parent version Id. - - Returns: - dict: Dictionary of relations. - """ - - project_name = legacy_io.active_project() - json_representation = get_representation_by_name( - project_name, representation_name="json", version_id=version_id - ) - - # Load relationships - shader_relation = get_representation_path(json_representation) - with open(shader_relation, "r") as f: - relationships = json.load(f) - - return relationships - - -def load_look(version_id): - # type: (str) -> list - """Load look from version. - - Get look from version and invoke Loader for it. - - Args: - version_id (str): Version ID - - Returns: - list of shader nodes. - - """ - - project_name = legacy_io.active_project() - # Get representations of shader file and relationships - look_representation = get_representation_by_name( - project_name, representation_name="ma", version_id=version_id - ) - - # See if representation is already loaded, if so reuse it. - host = registered_host() - representation_id = str(look_representation['_id']) - for container in host.ls(): - if (container['loader'] == "LookLoader" and - container['representation'] == representation_id): - log.info("Reusing loaded look ...") - container_node = container['objectName'] - break - else: - log.info("Using look for the first time ...") - - # Load file - all_loaders = discover_loader_plugins() - loaders = loaders_from_representation(all_loaders, representation_id) - loader = next( - (i for i in loaders if i.__name__ == "LookLoader"), None) - if loader is None: - raise RuntimeError("Could not find LookLoader, this is a bug") - - # Reference the look file - with lib.maintained_selection(): - container_node = load_container(loader, look_representation) - - # Get container members - shader_nodes = lib.get_container_members(container_node) - return shader_nodes - - def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): # type: (str, str) -> None """Assign look to vray proxy. @@ -263,8 +180,8 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): )) continue - relationships = get_look_relationships(version["_id"]) - shadernodes = load_look(version["_id"]) + relationships = lib.get_look_relationships(version["_id"]) + shadernodes, _ = lib.load_look(version["_id"]) # Get only the node ids and paths related to this asset # And get the shader edits the look supplies @@ -272,8 +189,10 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): node_id: nodes_by_id[node_id] for node_id in node_ids } edits = list( - lib.iter_shader_edits( - relationships, shadernodes, asset_nodes_by_id)) + maya_lib.iter_shader_edits( + relationships, shadernodes, asset_nodes_by_id + ) + ) # Create assignments assignments = {} diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2a14096f0e..fe3a2d2bd1 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -23,6 +23,9 @@ from openpype.client import ( from openpype.host import HostDirmap from openpype.tools.utils import host_tools +from openpype.pipeline.workfile.workfile_template_builder import ( + TemplateProfileNotFound +) from openpype.lib import ( env_value_to_bool, Logger, @@ -148,7 +151,7 @@ def get_main_window(): def set_node_data(node, knobname, data): """Write data to node invisible knob - Will create new in case it doesnt exists + Will create new in case it doesn't exists or update the one already created. Args: @@ -506,7 +509,7 @@ def get_avalon_knob_data(node, prefix="avalon:", create=True): try: # check if data available on the node test = node[AVALON_DATA_GROUP].value() - log.debug("Only testing if data avalable: `{}`".format(test)) + log.debug("Only testing if data available: `{}`".format(test)) except NameError as e: # if it doesn't then create it log.debug("Creating avalon knob: `{}`".format(e)) @@ -908,11 +911,11 @@ def get_view_process_node(): continue if not ipn_node: - # in case a Viewer node is transfered from + # in case a Viewer node is transferred from # different workfile with old values raise NameError(( "Input process node name '{}' set in " - "Viewer '{}' is does't exists in nodes" + "Viewer '{}' is doesn't exists in nodes" ).format(ipn, v_.name())) ipn_node.setSelected(True) @@ -1662,7 +1665,7 @@ def create_write_node_legacy( tile_color = _data.get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) - # overrie knob values from settings + # override knob values from settings for knob in knob_overrides: knob_type = knob["type"] knob_name = knob["name"] @@ -2117,7 +2120,7 @@ class WorkfileSettings(object): write_node[knob["name"]].setValue(value) except TypeError: log.warning( - "Legacy workflow didnt work, switching to current") + "Legacy workflow didn't work, switching to current") set_node_knobs_from_settings( write_node, nuke_imageio_writes["knobs"]) @@ -2543,7 +2546,7 @@ def reset_selection(): def select_nodes(nodes): - """Selects all inputed nodes + """Selects all inputted nodes Arguments: nodes (list): nuke nodes to be selected @@ -2560,7 +2563,7 @@ def launch_workfiles_app(): Trigger to show workfiles tool on application launch. Can be executed only once all other calls are ignored. - Workfiles tool show is deffered after application initialization using + Workfiles tool show is deferred after application initialization using QTimer. """ @@ -2581,7 +2584,7 @@ def launch_workfiles_app(): # Show workfiles tool using timer # - this will be probably triggered during initialization in that case # the application is not be able to show uis so it must be - # deffered using timer + # deferred using timer # - timer should be processed when initialization ends # When applications starts to process events. timer = QtCore.QTimer() @@ -2684,7 +2687,10 @@ def start_workfile_template_builder(): # to avoid looping of the callback, remove it! log.info("Starting workfile template builder...") - build_workfile_template(workfile_creation_enabled=True) + try: + build_workfile_template(workfile_creation_enabled=True) + except TemplateProfileNotFound: + log.warning("Template profile not found. Skipping...") # remove callback since it would be duplicating the workfile nuke.removeOnCreate(start_workfile_template_builder, nodeClass="Root") diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index aec87be5ab..3566cb64c1 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -208,6 +208,12 @@ class NukeCreator(NewCreator): def collect_instances(self): cached_instances = _collect_and_cache_nodes(self) + attr_def_keys = { + attr_def.key + for attr_def in self.get_instance_attr_defs() + } + attr_def_keys.discard(None) + for (node, data) in cached_instances[self.identifier]: created_instance = CreatedInstance.from_existing( data, self @@ -215,6 +221,12 @@ class NukeCreator(NewCreator): created_instance.transient_data["node"] = node self._add_instance_to_context(created_instance) + for key in ( + set(created_instance["creator_attributes"].keys()) + - attr_def_keys + ): + created_instance["creator_attributes"].pop(key) + def update_instances(self, update_list): for created_inst, _changes in update_list: instance_node = created_inst.transient_data["node"] @@ -301,8 +313,11 @@ class NukeWriteCreator(NukeCreator): def get_instance_attr_defs(self): attr_defs = [ self._get_render_target_enum(), - self._get_reviewable_bool() ] + # add reviewable attribute + if "reviewable" in self.instance_attributes: + attr_defs.append(self._get_reviewable_bool()) + return attr_defs def _get_render_target_enum(self): @@ -322,7 +337,7 @@ class NukeWriteCreator(NukeCreator): def _get_reviewable_bool(self): return BoolDef( "review", - default=("reviewable" in self.instance_attributes), + default=True, label="Review" ) @@ -594,7 +609,7 @@ class ExporterReview(object): Defaults to None. range (bool, optional): flag for adding ranges. Defaults to False. - custom_tags (list[str], optional): user inputed custom tags. + custom_tags (list[str], optional): user inputted custom tags. Defaults to None. """ add_tags = tags or [] @@ -1110,7 +1125,7 @@ class AbstractWriteRender(OpenPypeCreator): def is_legacy(self): """Check if it needs to run legacy code - In case where `type` key is missing in singe + In case where `type` key is missing in single knob it is legacy project anatomy. Returns: diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index 6bcb752dd1..2b3c35c23a 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -87,7 +87,7 @@ def bake_gizmos_recursively(in_group=None): def colorspace_exists_on_node(node, colorspace_name): """ Check if colorspace exists on node - Look through all options in the colorpsace knob, and see if we have an + Look through all options in the colorspace knob, and see if we have an exact match to one of the items. Args: diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index fb0afb3d55..72d4ffb476 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -42,7 +42,7 @@ class NukeTemplateBuilder(AbstractTemplateBuilder): get_template_preset implementation) Returns: - bool: Wether the template was successfully imported or not + bool: Whether the template was successfully imported or not """ # TODO check if the template is already imported @@ -219,19 +219,22 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): # fix the problem of z_order for backdrops self._fix_z_order(placeholder) - self._imprint_siblings(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) if placeholder.data["nb_children"] == 0: - # save initial nodes postions and dimensions, update them + # save initial nodes positions and dimensions, update them # and set inputs and outputs of loaded nodes + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) self._set_loaded_connections(placeholder) elif placeholder.data["siblings"]: # create copies of placeholder siblings for the new loaded nodes, - # set their inputs and outpus and update all nodes positions and + # set their inputs and outputs and update all nodes positions and # dimensions and siblings names siblings = get_nodes_by_names(placeholder.data["siblings"]) @@ -629,19 +632,23 @@ class NukePlaceholderCreatePlugin( # fix the problem of z_order for backdrops self._fix_z_order(placeholder) - self._imprint_siblings(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) if placeholder.data["nb_children"] == 0: - # save initial nodes postions and dimensions, update them + # save initial nodes positions and dimensions, update them # and set inputs and outputs of created nodes - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_created) + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_created) + self._set_created_connections(placeholder) elif placeholder.data["siblings"]: # create copies of placeholder siblings for the new created nodes, - # set their inputs and outpus and update all nodes positions and + # set their inputs and outputs and update all nodes positions and # dimensions and siblings names siblings = get_nodes_by_names(placeholder.data["siblings"]) diff --git a/openpype/hosts/nuke/plugins/create/convert_legacy.py b/openpype/hosts/nuke/plugins/create/convert_legacy.py index d7341c625f..c143e4cb27 100644 --- a/openpype/hosts/nuke/plugins/create/convert_legacy.py +++ b/openpype/hosts/nuke/plugins/create/convert_legacy.py @@ -39,7 +39,7 @@ class LegacyConverted(SubsetConvertorPlugin): break if legacy_found: - # if not item do not add legacy instance convertor + # if not item do not add legacy instance converter self.add_convertor_item("Convert legacy instances") def convert(self): diff --git a/openpype/hosts/nuke/plugins/create/create_source.py b/openpype/hosts/nuke/plugins/create/create_source.py index 06cf4e6cbf..57504b5d53 100644 --- a/openpype/hosts/nuke/plugins/create/create_source.py +++ b/openpype/hosts/nuke/plugins/create/create_source.py @@ -85,4 +85,4 @@ class CreateSource(NukeCreator): raise NukeCreatorError("Creator error: No active selection") else: NukeCreatorError( - "Creator error: only supprted with active selection") + "Creator error: only supported with active selection") diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index d38253ab2f..b74cea5dae 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -63,13 +63,6 @@ class CreateWriteImage(napi.NukeWriteCreator): default=nuke.frame() ) - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): linked_knobs_ = [] if "use_range_limit" in self.instance_attributes: diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 8103cb7c4d..387768b1dd 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -41,13 +41,6 @@ class CreateWritePrerender(napi.NukeWriteCreator): ] return attr_defs - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): linked_knobs_ = [] if "use_range_limit" in self.instance_attributes: diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 23efa62e36..09257f662e 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -38,13 +38,6 @@ class CreateWriteRender(napi.NukeWriteCreator): ] return attr_defs - def get_instance_attr_defs(self): - attr_defs = [ - self._get_render_target_enum(), - self._get_reviewable_bool() - ] - return attr_defs - def create_instance_node(self, subset_name, instance_data): # add fpath_template write_data = { diff --git a/openpype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py index e562c74c58..3227a7ed98 100644 --- a/openpype/hosts/nuke/plugins/load/actions.py +++ b/openpype/hosts/nuke/plugins/load/actions.py @@ -74,8 +74,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): return # Include handles - handles = version_data.get("handles", 0) - start -= handles - end += handles + start -= version_data.get("handleStart", 0) + end += version_data.get("handleEnd", 0) lib.update_frame_range(start, end) diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index f227aa161a..67c7877e60 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -54,22 +54,19 @@ class LoadBackdropNodes(load.LoaderPlugin): version = context['version'] version_data = version.get("data", {}) vname = version.get("name", None) - first = version_data.get("frameStart", None) - last = version_data.get("frameEnd", None) namespace = namespace or context['asset']['name'] colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) # prepare data for imprinting # add additional metadata from the version to imprint to Avalon knob - add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", - "source", "author", "fps"] + add_keys = ["source", "author", "fps"] - data_imprint = {"frameStart": first, - "frameEnd": last, - "version": vname, - "colorspaceInput": colorspace, - "objectName": object_name} + data_imprint = { + "version": vname, + "colorspaceInput": colorspace, + "objectName": object_name + } for k in add_keys: data_imprint.update({k: version_data[k]}) @@ -204,18 +201,13 @@ class LoadBackdropNodes(load.LoaderPlugin): name = container['name'] version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) - first = version_data.get("frameStart", None) - last = version_data.get("frameEnd", None) namespace = container['namespace'] colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd", - "source", "author", "fps"] + add_keys = ["source", "author", "fps"] data_imprint = {"representation": str(representation["_id"]), - "frameStart": first, - "frameEnd": last, "version": vname, "colorspaceInput": colorspace, "objectName": object_name} diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index 90581c2f22..53e9a76003 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -138,7 +138,6 @@ class LinkAsGroup(load.LoaderPlugin): "version": version_doc.get("name"), "colorspace": version_data.get("colorspace"), "source": version_data.get("source"), - "handles": version_data.get("handles"), "fps": version_data.get("fps"), "author": version_data.get("author") }) diff --git a/openpype/hosts/nuke/plugins/publish/collect_backdrop.py b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py index 8eaefa6854..7d51af7e9e 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py @@ -51,38 +51,10 @@ class CollectBackdrops(pyblish.api.InstancePlugin): instance.data["label"] = "{0} ({1} nodes)".format( bckn.name(), len(instance.data["transientData"]["childNodes"])) - instance.data["families"].append(instance.data["family"]) - - # Get frame range - handle_start = instance.context.data["handleStart"] - handle_end = instance.context.data["handleEnd"] - first_frame = int(nuke.root()["first_frame"].getValue()) - last_frame = int(nuke.root()["last_frame"].getValue()) - # get version version = instance.context.data.get('version') - if not version: - raise RuntimeError("Script name has no version in the name.") + if version: + instance.data['version'] = version - instance.data['version'] = version - - # Add version data to instance - version_data = { - "handles": handle_start, - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": first_frame + handle_start, - "frameEnd": last_frame - handle_end, - "version": int(version), - "families": [instance.data["family"]] + instance.data["families"], - "subset": instance.data["subset"], - "fps": instance.context.data["fps"] - } - - instance.data.update({ - "versionData": version_data, - "frameStart": first_frame, - "frameEnd": last_frame - }) self.log.info("Backdrop instance collected: `{}`".format(instance)) diff --git a/openpype/hosts/nuke/plugins/publish/collect_context_data.py b/openpype/hosts/nuke/plugins/publish/collect_context_data.py index b487c946f0..f1b4965205 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_context_data.py +++ b/openpype/hosts/nuke/plugins/publish/collect_context_data.py @@ -49,8 +49,6 @@ class CollectContextData(pyblish.api.ContextPlugin): "resolutionHeight": resolution_height, "pixelAspect": pixel_aspect, - # backward compatibility handles - "handles": handle_start, "handleStart": handle_start, "handleEnd": handle_end, "step": 1, diff --git a/openpype/hosts/nuke/plugins/publish/collect_gizmo.py b/openpype/hosts/nuke/plugins/publish/collect_gizmo.py index 3a877fc194..e3c40a7a90 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/collect_gizmo.py @@ -28,7 +28,6 @@ class CollectGizmo(pyblish.api.InstancePlugin): # Add version data to instance version_data = { - "handles": handle_start, "handleStart": handle_start, "handleEnd": handle_end, "frameStart": first_frame + handle_start, diff --git a/openpype/hosts/nuke/plugins/publish/collect_model.py b/openpype/hosts/nuke/plugins/publish/collect_model.py index 9da056052b..3fdf376d0c 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_model.py +++ b/openpype/hosts/nuke/plugins/publish/collect_model.py @@ -28,7 +28,6 @@ class CollectModel(pyblish.api.InstancePlugin): # Add version data to instance version_data = { - "handles": handle_start, "handleStart": handle_start, "handleEnd": handle_end, "frameStart": first_frame + handle_start, diff --git a/openpype/hosts/nuke/plugins/publish/collect_reads.py b/openpype/hosts/nuke/plugins/publish/collect_reads.py index a1144fbcc3..831ae29a27 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_reads.py +++ b/openpype/hosts/nuke/plugins/publish/collect_reads.py @@ -103,7 +103,6 @@ class CollectNukeReads(pyblish.api.InstancePlugin): # Add version data to instance version_data = { - "handles": handle_start, "handleStart": handle_start, "handleEnd": handle_end, "frameStart": first_frame + handle_start, @@ -123,7 +122,8 @@ class CollectNukeReads(pyblish.api.InstancePlugin): "frameStart": first_frame, "frameEnd": last_frame, "colorspace": colorspace, - "handles": int(asset_doc["data"].get("handles", 0)), + "handleStart": handle_start, + "handleEnd": handle_end, "step": 1, "fps": int(nuke.root()['fps'].value()) }) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 0008a756bc..536a0698f3 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -189,7 +189,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, }) # make sure rendered sequence on farm will - # be used for exctract review + # be used for extract review if not instance.data["review"]: instance.data["useSequenceForReview"] = False diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data.py b/openpype/hosts/nuke/plugins/publish/extract_review_data.py index dee8248295..c221af40fb 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data.py @@ -25,7 +25,7 @@ class ExtractReviewData(publish.Extractor): # review can be removed since `ProcessSubmittedJobOnFarm` will create # reviewable representation if needed if ( - "render.farm" in instance.data["families"] + instance.data.get("farm") and "review" in instance.data["families"] ): instance.data["families"].remove("review") diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 67779e9599..e4b7b155cd 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -49,7 +49,12 @@ class ExtractReviewDataLut(publish.Extractor): exporter.stagingDir, exporter.file).replace("\\", "/") instance.data["representations"] += data["representations"] - if "render.farm" in families: + # review can be removed since `ProcessSubmittedJobOnFarm` will create + # reviewable representation if needed + if ( + instance.data.get("farm") + and "review" in instance.data["families"] + ): instance.data["families"].remove("review") self.log.debug( diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 3fcfc2a4b5..956d1a54a3 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -105,10 +105,7 @@ class ExtractReviewDataMov(publish.Extractor): self, instance, o_name, o_data["extension"], multiple_presets) - if ( - "render.farm" in families or - "prerender.farm" in families - ): + if instance.data.get("farm"): if "review" in instance.data["families"]: instance.data["families"].remove("review") diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index a1a0e241c0..f391ca1e7c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -31,7 +31,7 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): - if "render.farm" in instance.data["families"]: + if instance.data.get("farm"): return with napi.maintained_selection(): diff --git a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py index 208d4a2498..5f4a5c3ab0 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py @@ -48,7 +48,7 @@ class SelectCenterInNodeGraph(pyblish.api.Action): class ValidateBackdrop(pyblish.api.InstancePlugin): """ Validate amount of nodes on backdrop node in case user - forgoten to add nodes above the publishing backdrop node. + forgotten to add nodes above the publishing backdrop node. """ order = pyblish.api.ValidatorOrder diff --git a/openpype/hosts/photoshop/api/extension/host/index.jsx b/openpype/hosts/photoshop/api/extension/host/index.jsx index 2acec1ebc1..e2711fb960 100644 --- a/openpype/hosts/photoshop/api/extension/host/index.jsx +++ b/openpype/hosts/photoshop/api/extension/host/index.jsx @@ -199,7 +199,7 @@ function getActiveDocumentName(){ function getActiveDocumentFullName(){ /** * Returns file name of active document with file path. - * activeDocument.fullName returns path in URI (eg /c/.. insted of c:/) + * activeDocument.fullName returns path in URI (eg /c/.. instead of c:/) * */ if (documents.length == 0){ return null; @@ -225,7 +225,7 @@ function getSelectedLayers(doc) { * Returns json representation of currently selected layers. * Works in three steps - 1) creates new group with selected layers * 2) traverses this group - * 3) deletes newly created group, not neede + * 3) deletes newly created group, not needed * Bit weird, but Adobe.. **/ if (doc == null){ @@ -284,7 +284,7 @@ function selectLayers(selectedLayers){ existing_ids.push(existing_layers[y]["id"]); } for (var i = 0; i < selectedLayers.length; i++) { - // a check to see if the id stil exists + // a check to see if the id still exists var id = selectedLayers[i]; if(existing_ids.toString().indexOf(id)>=0){ layers[i] = charIDToTypeID( "Lyr " ); diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py index 89ba6ad4e6..25732446b5 100644 --- a/openpype/hosts/photoshop/api/launch_logic.py +++ b/openpype/hosts/photoshop/api/launch_logic.py @@ -66,11 +66,11 @@ class MainThreadItem: return self._result def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 01022ce0b2..9d7eff0211 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -129,7 +129,6 @@ class ExtractReview(publish.Extractor): "frameStart": 1, "frameEnd": no_of_frames, "fps": fps, - "preview": True, "tags": self.mov_options['tags'] }) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index f41eb36caf..b3ad20df39 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -250,7 +250,7 @@ def create_timeline_item(media_pool_item: object, media_pool_item, timeline) assert output_timeline_item, AssertionError( - "Track Item with name `{}` doesnt exist on the timeline: `{}`".format( + "Track Item with name `{}` doesn't exist on the timeline: `{}`".format( clip_name, timeline.GetName() )) return output_timeline_item @@ -571,7 +571,7 @@ def create_compound_clip(clip_data, name, folder): # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) - # check if clip doesnt exist already: + # check if clip doesn't exist already: clips = folder.GetClipList() cct = next((c for c in clips if c.GetName() in name), None) @@ -582,7 +582,7 @@ def create_compound_clip(clip_data, name, folder): # Create empty timeline in current folder and give name: cct = mp.CreateEmptyTimeline(name) - # check if clip doesnt exist already: + # check if clip doesn't exist already: clips = folder.GetClipList() cct = next((c for c in clips if c.GetName() in name), None) diff --git a/openpype/hosts/resolve/api/menu_style.qss b/openpype/hosts/resolve/api/menu_style.qss index d2d3d1ed37..3d51c7139f 100644 --- a/openpype/hosts/resolve/api/menu_style.qss +++ b/openpype/hosts/resolve/api/menu_style.qss @@ -61,7 +61,7 @@ QVBoxLayout { background-color: #282828; } -#Devider { +#Divider { border: 1px solid #090909; background-color: #585858; } diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 77e30149fd..609cff60f7 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -715,7 +715,7 @@ class PublishClip: # increasing steps by index of rename iteration self.count_steps *= self.rename_index - hierarchy_formating_data = dict() + hierarchy_formatting_data = dict() _data = self.timeline_item_default_data.copy() if self.ui_inputs: # adding tag metadata from ui @@ -749,13 +749,13 @@ class PublishClip: # fill up pythonic expresisons in hierarchy data for k, _v in self.hierarchy_data.items(): - hierarchy_formating_data[k] = _v["value"].format(**_data) + hierarchy_formatting_data[k] = _v["value"].format(**_data) else: # if no gui mode then just pass default data - hierarchy_formating_data = self.hierarchy_data + hierarchy_formatting_data = self.hierarchy_data tag_hierarchy_data = self._solve_tag_hierarchy_data( - hierarchy_formating_data + hierarchy_formatting_data ) tag_hierarchy_data.update({"heroTrack": True}) @@ -792,18 +792,17 @@ class PublishClip: else: self.tag_data.update({"reviewTrack": None}) - - def _solve_tag_hierarchy_data(self, hierarchy_formating_data): + def _solve_tag_hierarchy_data(self, hierarchy_formatting_data): """ Solve tag data from hierarchy data and templates. """ # fill up clip name and hierarchy keys - hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data) - clip_name_filled = self.clip_name.format(**hierarchy_formating_data) + hierarchy_filled = self.hierarchy.format(**hierarchy_formatting_data) + clip_name_filled = self.clip_name.format(**hierarchy_formatting_data) return { "newClipName": clip_name_filled, "hierarchy": hierarchy_filled, "parents": self.parents, - "hierarchyData": hierarchy_formating_data, + "hierarchyData": hierarchy_formatting_data, "subset": self.subset, "family": self.subset_family, "families": ["clip"] diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 7925b0ecf3..6c3b0c3efd 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -83,9 +83,9 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): self.log.info(f"Created new instance: {instance_name}") - def convertor(value): + def converter(value): return str(value) self.log.debug("Instance data: {}".format( - json.dumps(new_instance.data, indent=4, default=convertor) + json.dumps(new_instance.data, indent=4, default=converter) )) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py index 2bf3917e2f..96aaae23dc 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py @@ -104,7 +104,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): if repr.get(k): repr.pop(k) - # convert files to list if it isnt + # convert files to list if it isn't if not isinstance(files, (tuple, list)): files = [files] @@ -174,7 +174,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): continue files = repre["files"] - # Convert files to list if it isnt + # Convert files to list if it isn't if not isinstance(files, (tuple, list)): files = [files] @@ -255,7 +255,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): if ext.startswith("."): component["ext"] = ext[1:] - if component["preview"]: + # Remove 'preview' key from representation data + preview = component.pop("preview") + if preview: instance.data["families"].append("review") component["tags"] = ["review"] self.log.debug("Adding review family") diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py index 8633d4bf9d..391cace761 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py @@ -116,7 +116,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): kwargs = {} if extension == ".edl": # EDL has no frame rate embedded so needs explicit - # frame rate else 24 is asssumed. + # frame rate else 24 is assumed. kwargs["rate"] = get_current_project_asset()["data"]["fps"] instance.data["otio_timeline"] = otio.adapters.read_from_file( diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py index 074c62ea0e..e46fbe6098 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_frame_ranges.py @@ -29,7 +29,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): for pattern in self.skip_timelines_check): self.log.info("Skipping for {} task".format(instance.data["task"])) - # TODO repace query with using 'instance.data["assetEntity"]' + # TODO replace query with using 'instance.data["assetEntity"]' asset_data = get_current_project_asset(instance.data["asset"])["data"] frame_start = asset_data["frameStart"] frame_end = asset_data["frameEnd"] diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 293db542a9..e8f76bd314 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -8,10 +8,10 @@ from openpype.pipeline.create import CreatorError class ShotMetadataSolver: """ Solving hierarchical metadata - Used during editorial publishing. Works with imput + Used during editorial publishing. Works with input clip name and settings defining python formatable template. Settings also define searching patterns - and its token keys used for formating in templates. + and its token keys used for formatting in templates. """ NO_DECOR_PATERN = re.compile(r"\{([a-z]*?)\}") @@ -40,13 +40,13 @@ class ShotMetadataSolver: """Shot renaming function Args: - data (dict): formating data + data (dict): formatting data Raises: CreatorError: If missing keys Returns: - str: formated new name + str: formatted new name """ shot_rename_template = self.shot_rename[ "shot_rename_template"] @@ -58,7 +58,7 @@ class ShotMetadataSolver: "Make sure all keys in settings are correct:: \n\n" f"From template string {shot_rename_template} > " f"`{_E}` has no equivalent in \n" - f"{list(data.keys())} input formating keys!" + f"{list(data.keys())} input formatting keys!" )) def _generate_tokens(self, clip_name, source_data): @@ -68,7 +68,7 @@ class ShotMetadataSolver: Args: clip_name (str): name of clip in editorial - source_data (dict): data for formating + source_data (dict): data for formatting Raises: CreatorError: if missing key @@ -106,14 +106,14 @@ class ShotMetadataSolver: return output_data def _create_parents_from_settings(self, parents, data): - """Formating parent components. + """formatting parent components. Args: parents (list): list of dict parent components - data (dict): formating data + data (dict): formatting data Raises: - CreatorError: missing formating key + CreatorError: missing formatting key CreatorError: missing token key KeyError: missing parent token @@ -126,7 +126,7 @@ class ShotMetadataSolver: # fill parent keys data template from anatomy data try: - _parent_tokens_formating_data = { + _parent_tokens_formatting_data = { parent_token["name"]: parent_token["value"].format(**data) for parent_token in hierarchy_parents } @@ -143,17 +143,17 @@ class ShotMetadataSolver: for _index, _parent in enumerate( shot_hierarchy["parents_path"].split("/") ): - # format parent token with value which is formated + # format parent token with value which is formatted try: parent_name = _parent.format( - **_parent_tokens_formating_data) + **_parent_tokens_formatting_data) except KeyError as _E: raise CreatorError(( "Make sure all keys in settings are correct : \n\n" f"`{_E}` from template string " f"{shot_hierarchy['parents_path']}, " f" has no equivalent in \n" - f"{list(_parent_tokens_formating_data.keys())} parents" + f"{list(_parent_tokens_formatting_data.keys())} parents" )) parent_token_name = ( @@ -225,7 +225,7 @@ class ShotMetadataSolver: visual_hierarchy = [asset_doc] current_doc = asset_doc - # looping trought all available visual parents + # looping through all available visual parents # if they are not available anymore than it breaks while True: visual_parent_id = current_doc["data"]["visualParent"] @@ -288,7 +288,7 @@ class ShotMetadataSolver: Args: clip_name (str): clip name - source_data (dict): formating data + source_data (dict): formatting data Returns: (str, dict): shot name and hierarchy data @@ -301,19 +301,19 @@ class ShotMetadataSolver: # match clip to shot name at start shot_name = clip_name - # parse all tokens and generate formating data - formating_data = self._generate_tokens(shot_name, source_data) + # parse all tokens and generate formatting data + formatting_data = self._generate_tokens(shot_name, source_data) # generate parents from selected asset parents = self._get_parents_from_selected_asset(asset_doc, project_doc) if self.shot_rename["enabled"]: - shot_name = self._rename_template(formating_data) + shot_name = self._rename_template(formatting_data) self.log.info(f"Renamed shot name: {shot_name}") if self.shot_hierarchy["enabled"]: parents = self._create_parents_from_settings( - parents, formating_data) + parents, formatting_data) if self.shot_add_tasks: tasks = self._generate_tasks_from_settings( diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 73be43444e..0630dfb3da 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -260,7 +260,7 @@ or updating already created. Publishing will create OTIO file. ) if not first_otio_timeline: - # assing otio timeline for multi file to layer + # assign otio timeline for multi file to layer first_otio_timeline = otio_timeline # create otio editorial instance @@ -283,7 +283,7 @@ or updating already created. Publishing will create OTIO file. Args: subset_name (str): name of subset - data (dict): instnance data + data (dict): instance data sequence_path (str): path to sequence file media_path (str): path to media file otio_timeline (otio.Timeline): otio timeline object @@ -315,7 +315,7 @@ or updating already created. Publishing will create OTIO file. kwargs = {} if extension == ".edl": # EDL has no frame rate embedded so needs explicit - # frame rate else 24 is asssumed. + # frame rate else 24 is assumed. kwargs["rate"] = fps kwargs["ignore_timecode_mismatch"] = True @@ -358,7 +358,7 @@ or updating already created. Publishing will create OTIO file. sequence_file_name, first_otio_timeline=None ): - """Helping function fro creating clip instance + """Helping function for creating clip instance Args: otio_timeline (otio.Timeline): otio timeline object @@ -527,7 +527,7 @@ or updating already created. Publishing will create OTIO file. Args: otio_clip (otio.Clip): otio clip object - preset (dict): sigle family preset + preset (dict): single family preset instance_data (dict): instance data parenting_data (dict): shot instance parent data @@ -767,7 +767,7 @@ or updating already created. Publishing will create OTIO file. ] def _validate_clip_for_processing(self, otio_clip): - """Validate otio clip attribues + """Validate otio clip attributes Args: otio_clip (otio.Clip): otio clip object @@ -843,7 +843,7 @@ or updating already created. Publishing will create OTIO file. single_item=False, label="Media files", ), - # TODO: perhpas better would be timecode and fps input + # TODO: perhaps better would be timecode and fps input NumberDef( "timeline_offset", default=0, diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 183195a515..c081216481 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -14,7 +14,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): There is also possibility to have reviewable representation which can be stored under 'reviewable' attribute stored on instance data. If there was - already created representation with the same files as 'revieable' containes + already created representation with the same files as 'reviewable' contains Representations can be marked for review and in that case is also added 'review' family to instance families. For review can be marked only one diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index e94e64e04a..6f76c25e0c 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -389,11 +389,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 575e6aa755..58fbd09545 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -504,14 +504,9 @@ def set_context_settings(project_name, asset_doc): print("Frame range was not found!") return - handles = asset_doc["data"].get("handles") or 0 handle_start = asset_doc["data"].get("handleStart") handle_end = asset_doc["data"].get("handleEnd") - if handle_start is None or handle_end is None: - handle_start = handles - handle_end = handles - # Always start from 0 Mark In and set only Mark Out mark_in = 0 mark_out = mark_in + (frame_end - frame_start) + handle_start + handle_end diff --git a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py index 538c6e4c5e..5cfa1faa50 100644 --- a/openpype/hosts/tvpaint/plugins/create/convert_legacy.py +++ b/openpype/hosts/tvpaint/plugins/create/convert_legacy.py @@ -55,7 +55,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): self._convert_render_layers( to_convert["renderLayer"], current_instances) self._convert_render_passes( - to_convert["renderpass"], current_instances) + to_convert["renderPass"], current_instances) self._convert_render_scenes( to_convert["renderScene"], current_instances) self._convert_workfiles( @@ -116,7 +116,7 @@ class TVPaintLegacyConverted(SubsetConvertorPlugin): render_layers_by_group_id = {} for instance in current_instances: if instance.get("creator_identifier") == "render.layer": - group_id = instance["creator_identifier"]["group_id"] + group_id = instance["creator_attributes"]["group_id"] render_layers_by_group_id[group_id] = instance for render_pass in render_passes: diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 9711024c79..2369c7329f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -415,11 +415,11 @@ class CreateRenderPass(TVPaintCreator): .get("creator_attributes", {}) .get("render_layer_instance_id") ) - render_layer_info = render_layers.get(render_layer_instance_id) + render_layer_info = render_layers.get(render_layer_instance_id, {}) self.update_instance_labels( instance_data, - render_layer_info["variant"], - render_layer_info["template_data"] + render_layer_info.get("variant"), + render_layer_info.get("template_data") ) instance = CreatedInstance.from_existing(instance_data, self) self._add_instance_to_context(instance) @@ -607,11 +607,11 @@ class CreateRenderPass(TVPaintCreator): current_instances = self.host.list_instances() render_layers = [ { - "value": instance["instance_id"], - "label": instance["subset"] + "value": inst["instance_id"], + "label": inst["subset"] } - for instance in current_instances - if instance["creator_identifier"] == CreateRenderlayer.identifier + for inst in current_instances + if inst.get("creator_identifier") == CreateRenderlayer.identifier ] if not render_layers: render_layers.append({"value": None, "label": "N/A"}) @@ -697,6 +697,7 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator): ["create"] ["auto_detect_render"] ) + self.enabled = plugin_settings.get("enabled", False) self.allow_group_rename = plugin_settings["allow_group_rename"] self.group_name_template = plugin_settings["group_name_template"] self.group_idx_offset = plugin_settings["group_idx_offset"] diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py index 5eb702a1da..63f04cf3ce 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instance_frames.py @@ -22,9 +22,11 @@ class CollectOutputFrameRange(pyblish.api.InstancePlugin): context = instance.context frame_start = asset_doc["data"]["frameStart"] + fps = asset_doc["data"]["fps"] frame_end = frame_start + ( context.data["sceneMarkOut"] - context.data["sceneMarkIn"] ) + instance.data["fps"] = fps instance.data["frameStart"] = frame_start instance.data["frameEnd"] = frame_end self.log.info( diff --git a/openpype/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml b/openpype/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml index e7be735888..5832c74350 100644 --- a/openpype/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml +++ b/openpype/hosts/tvpaint/plugins/publish/help/validate_layers_visibility.xml @@ -1,7 +1,7 @@ -Layers visiblity +Layers visibility ## All layers are not visible Layers visibility was changed during publishing which caused that all layers for subset "{instance_name}" are hidden. diff --git a/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml b/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml index 7397f6ef0b..0fc03c2948 100644 --- a/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml +++ b/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_metadata.xml @@ -11,7 +11,7 @@ Your scene does not contain metadata about {missing_metadata}. Resave the scene using Workfiles tool or hit the "Repair" button on the right. -### How this could happend? +### How this could happen? You're using scene file that was not created using Workfiles tool. diff --git a/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml b/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml index c4ffafc8b5..bb57e93bf2 100644 --- a/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml +++ b/openpype/hosts/tvpaint/plugins/publish/help/validate_workfile_project_name.xml @@ -13,7 +13,7 @@ If the workfile belongs to project "{env_project_name}" then use Workfiles tool Otherwise close TVPaint and launch it again from project you want to publish in. -### How this could happend? +### How this could happen? You've opened workfile from different project. You've opened TVPaint on a task from "{env_project_name}" then you've opened TVPaint again on task from "{workfile_project_name}" without closing the TVPaint. Because TVPaint can run only once the project didn't change. diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py index 7e35726030..9347960d3f 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_asset_name.py @@ -1,5 +1,8 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.pipeline import ( list_instances, write_instances, @@ -31,8 +34,11 @@ class FixAssetNames(pyblish.api.Action): write_instances(new_instance_items) -class ValidateAssetNames(pyblish.api.ContextPlugin): - """Validate assset name present on instance. +class ValidateAssetName( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): + """Validate asset name present on instance. Asset name on instance should be the same as context's. """ @@ -43,6 +49,8 @@ class ValidateAssetNames(pyblish.api.ContextPlugin): actions = [FixAssetNames] def process(self, context): + if not self.is_active(context.data): + return context_asset_name = context.data["asset"] for instance in context: asset_name = instance.data.get("asset") diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py index 6a496a2e49..8e52a636f4 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py @@ -11,7 +11,7 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin): families = ["review", "render"] def process(self, instance): - layers = instance.data["layers"] + layers = instance.data.get("layers") # Instance have empty layers # - it is not job of this validator to check that if not layers: diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py index 0030b0fd1c..7b2cc62bb5 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_marks.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_marks.py @@ -1,7 +1,10 @@ import json import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.lib import execute_george @@ -23,7 +26,10 @@ class ValidateMarksRepair(pyblish.api.Action): ) -class ValidateMarks(pyblish.api.ContextPlugin): +class ValidateMarks( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate mark in and out are enabled and it's duration. Mark In/Out does not have to match frameStart and frameEnd but duration is @@ -59,6 +65,9 @@ class ValidateMarks(pyblish.api.ContextPlugin): } def process(self, context): + if not self.is_active(context.data): + return + current_data = { "markIn": context.data["sceneMarkIn"], "markInState": context.data["sceneMarkInState"], diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py index 4473e4b1b7..0ab8e811f5 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_scene_settings.py @@ -1,11 +1,17 @@ import json import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) # TODO @iLliCiTiT add fix action for fps -class ValidateProjectSettings(pyblish.api.ContextPlugin): +class ValidateProjectSettings( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate scene settings against database.""" label = "Validate Scene Settings" @@ -13,6 +19,9 @@ class ValidateProjectSettings(pyblish.api.ContextPlugin): optional = True def process(self, context): + if not self.is_active(context.data): + return + expected_data = context.data["assetEntity"]["data"] scene_data = { "fps": context.data.get("sceneFps"), diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py index 066e54c670..229ccfcd18 100644 --- a/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py +++ b/openpype/hosts/tvpaint/plugins/publish/validate_start_frame.py @@ -1,5 +1,8 @@ import pyblish.api -from openpype.pipeline import PublishXmlValidationError +from openpype.pipeline import ( + PublishXmlValidationError, + OptionalPyblishPluginMixin, +) from openpype.hosts.tvpaint.api.lib import execute_george @@ -14,7 +17,10 @@ class RepairStartFrame(pyblish.api.Action): execute_george("tv_startframe 0") -class ValidateStartFrame(pyblish.api.ContextPlugin): +class ValidateStartFrame( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Validate start frame being at frame 0.""" label = "Validate Start Frame" @@ -24,6 +30,9 @@ class ValidateStartFrame(pyblish.api.ContextPlugin): optional = True def process(self, context): + if not self.is_active(context.data): + return + start_frame = execute_george("tv_startframe") if start_frame == 0: return diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 8a5a459194..1a7c626984 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -306,7 +306,7 @@ def imprint(node, data): def show_tools_popup(): """Show popup with tools. - Popup will disappear on click or loosing focus. + Popup will disappear on click or losing focus. """ from openpype.hosts.unreal.api import tools_ui diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp index 008025e816..34faba1f49 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp @@ -31,7 +31,7 @@ bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& } /** - * Returns all poperties on given object + * Returns all properties on given object * @param cls - class * @return TArray of properties */ diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h index c960bbf190..322a23a3e8 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -16,7 +16,7 @@ /** * @brief This enum values are humanly readable mapping of error codes. * Here should be all error codes to be possible find what went wrong. -* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... +* TODO: In the future a web document should exists with the mapped error code & what problem occurred & how to repair it... */ UENUM() namespace EOP_ActionResult @@ -27,11 +27,11 @@ namespace EOP_ActionResult ProjectNotCreated, ProjectNotLoaded, ProjectNotSaved, - //....Here insert another values + //....Here insert another values //Do not remove! //Usable for looping through enum values - __Last UMETA(Hidden) + __Last UMETA(Hidden) }; } @@ -63,10 +63,10 @@ public: private: /** @brief Action status */ - EOP_ActionResult::Type Status; + EOP_ActionResult::Type Status; /** @brief Optional reason of fail */ - FText Reason; + FText Reason; public: /** @@ -77,7 +77,7 @@ public: EOP_ActionResult::Type& GetStatus(); FText& GetReason(); -private: +private: void TryLog() const; }; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp index 008025e816..34faba1f49 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp @@ -31,7 +31,7 @@ bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& } /** - * Returns all poperties on given object + * Returns all properties on given object * @param cls - class * @return TArray of properties */ diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h index c960bbf190..322a23a3e8 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -16,7 +16,7 @@ /** * @brief This enum values are humanly readable mapping of error codes. * Here should be all error codes to be possible find what went wrong. -* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... +* TODO: In the future a web document should exists with the mapped error code & what problem occurred & how to repair it... */ UENUM() namespace EOP_ActionResult @@ -27,11 +27,11 @@ namespace EOP_ActionResult ProjectNotCreated, ProjectNotLoaded, ProjectNotSaved, - //....Here insert another values + //....Here insert another values //Do not remove! //Usable for looping through enum values - __Last UMETA(Hidden) + __Last UMETA(Hidden) }; } @@ -63,10 +63,10 @@ public: private: /** @brief Action status */ - EOP_ActionResult::Type Status; + EOP_ActionResult::Type Status; /** @brief Optional reason of fail */ - FText Reason; + FText Reason; public: /** @@ -77,7 +77,7 @@ public: EOP_ActionResult::Type& GetStatus(); FText& GetReason(); -private: +private: void TryLog() const; }; diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index ca6b0ce736..2496440e5f 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -171,7 +171,7 @@ class CameraLoader(plugin.Loader): project_name = legacy_io.active_project() # TODO refactor - # - Creationg of hierarchy should be a function in unreal integration + # - Creating of hierarchy should be a function in unreal integration # - it's used in multiple loaders but must not be loader's logic # - hard to say what is purpose of the loop # - variables does not match their meaning diff --git a/openpype/hosts/unreal/ue_workers.py b/openpype/hosts/unreal/ue_workers.py index 00f83a7d7a..d1740124a8 100644 --- a/openpype/hosts/unreal/ue_workers.py +++ b/openpype/hosts/unreal/ue_workers.py @@ -5,29 +5,38 @@ import re import subprocess from distutils import dir_util from pathlib import Path -from typing import List +from typing import List, Union import openpype.hosts.unreal.lib as ue_lib from qtpy import QtCore -def parse_comp_progress(line: str, progress_signal: QtCore.Signal(int)) -> int: - match = re.search('\[[1-9]+/[0-9]+\]', line) +def parse_comp_progress(line: str, progress_signal: QtCore.Signal(int)): + match = re.search(r"\[[1-9]+/[0-9]+]", line) if match is not None: - split: list[str] = match.group().split('/') + split: list[str] = match.group().split("/") curr: float = float(split[0][1:]) total: float = float(split[1][:-1]) progress_signal.emit(int((curr / total) * 100.0)) -def parse_prj_progress(line: str, progress_signal: QtCore.Signal(int)) -> int: - match = re.search('@progress', line) +def parse_prj_progress(line: str, progress_signal: QtCore.Signal(int)): + match = re.search("@progress", line) if match is not None: - percent_match = re.search('\d{1,3}', line) + percent_match = re.search(r"\d{1,3}", line) progress_signal.emit(int(percent_match.group())) +def retrieve_exit_code(line: str): + match = re.search(r"ExitCode=\d+", line) + if match is not None: + split: list[str] = match.group().split("=") + return int(split[1]) + + return None + + class UEProjectGenerationWorker(QtCore.QObject): finished = QtCore.Signal(str) failed = QtCore.Signal(str) @@ -77,16 +86,19 @@ class UEProjectGenerationWorker(QtCore.QObject): if self.dev_mode: stage_count = 4 - self.stage_begin.emit(f'Generating a new UE project ... 1 out of ' - f'{stage_count}') + self.stage_begin.emit( + ("Generating a new UE project ... 1 out of " + f"{stage_count}")) - commandlet_cmd = [f'{ue_editor_exe.as_posix()}', - f'{cmdlet_project.as_posix()}', - f'-run=OPGenerateProject', - f'{project_file.resolve().as_posix()}'] + commandlet_cmd = [ + f"{ue_editor_exe.as_posix()}", + f"{cmdlet_project.as_posix()}", + "-run=OPGenerateProject", + f"{project_file.resolve().as_posix()}", + ] if self.dev_mode: - commandlet_cmd.append('-GenerateCode') + commandlet_cmd.append("-GenerateCode") gen_process = subprocess.Popen(commandlet_cmd, stdout=subprocess.PIPE, @@ -94,24 +106,27 @@ class UEProjectGenerationWorker(QtCore.QObject): for line in gen_process.stdout: decoded_line = line.decode(errors="replace") - print(decoded_line, end='') + print(decoded_line, end="") self.log.emit(decoded_line) gen_process.stdout.close() return_code = gen_process.wait() if return_code and return_code != 0: - msg = 'Failed to generate ' + self.project_name \ - + f' project! Exited with return code {return_code}' + msg = ( + f"Failed to generate {self.project_name} " + f"project! Exited with return code {return_code}" + ) self.failed.emit(msg, return_code) raise RuntimeError(msg) print("--- Project has been generated successfully.") - self.stage_begin.emit(f'Writing the Engine ID of the build UE ... 1' - f' out of {stage_count}') + self.stage_begin.emit( + (f"Writing the Engine ID of the build UE ... 1" + f" out of {stage_count}")) if not project_file.is_file(): - msg = "Failed to write the Engine ID into .uproject file! Can " \ - "not read!" + msg = ("Failed to write the Engine ID into .uproject file! Can " + "not read!") self.failed.emit(msg) raise RuntimeError(msg) @@ -125,13 +140,14 @@ class UEProjectGenerationWorker(QtCore.QObject): pf.seek(0) json.dump(pf_json, pf, indent=4) pf.truncate() - print(f'--- Engine ID has been written into the project file') + print("--- Engine ID has been written into the project file") self.progress.emit(90) if self.dev_mode: # 2nd stage - self.stage_begin.emit(f'Generating project files ... 2 out of ' - f'{stage_count}') + self.stage_begin.emit( + (f"Generating project files ... 2 out of " + f"{stage_count}")) self.progress.emit(0) ubt_path = ue_lib.get_path_to_ubt(self.engine_path, @@ -154,8 +170,8 @@ class UEProjectGenerationWorker(QtCore.QObject): stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in gen_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) parse_prj_progress(decoded_line, self.progress) @@ -163,13 +179,13 @@ class UEProjectGenerationWorker(QtCore.QObject): return_code = gen_proc.wait() if return_code and return_code != 0: - msg = 'Failed to generate project files! ' \ - f'Exited with return code {return_code}' + msg = ("Failed to generate project files! " + f"Exited with return code {return_code}") self.failed.emit(msg, return_code) raise RuntimeError(msg) - self.stage_begin.emit(f'Building the project ... 3 out of ' - f'{stage_count}') + self.stage_begin.emit( + f"Building the project ... 3 out of {stage_count}") self.progress.emit(0) # 3rd stage build_prj_cmd = [ubt_path.as_posix(), @@ -177,16 +193,16 @@ class UEProjectGenerationWorker(QtCore.QObject): arch, "Development", "-TargetType=Editor", - f'-Project={project_file}', - f'{project_file}', + f"-Project={project_file}", + f"{project_file}", "-IgnoreJunk"] build_prj_proc = subprocess.Popen(build_prj_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) for line in build_prj_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) parse_comp_progress(decoded_line, self.progress) @@ -194,16 +210,17 @@ class UEProjectGenerationWorker(QtCore.QObject): return_code = build_prj_proc.wait() if return_code and return_code != 0: - msg = 'Failed to build project! ' \ - f'Exited with return code {return_code}' + msg = ("Failed to build project! " + f"Exited with return code {return_code}") self.failed.emit(msg, return_code) raise RuntimeError(msg) # ensure we have PySide2 installed in engine self.progress.emit(0) - self.stage_begin.emit(f'Checking PySide2 installation... {stage_count}' - f' out of {stage_count}') + self.stage_begin.emit( + (f"Checking PySide2 installation... {stage_count} " + f" out of {stage_count}")) python_path = None if platform.system().lower() == "windows": python_path = self.engine_path / ("Engine/Binaries/ThirdParty/" @@ -225,9 +242,30 @@ class UEProjectGenerationWorker(QtCore.QObject): msg = f"Unreal Python not found at {python_path}" self.failed.emit(msg, 1) raise RuntimeError(msg) - subprocess.check_call( - [python_path.as_posix(), "-m", "pip", "install", "pyside2"] - ) + pyside_cmd = [python_path.as_posix(), + "-m", + "pip", + "install", + "pyside2"] + + pyside_install = subprocess.Popen(pyside_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + for line in pyside_install.stdout: + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") + self.log.emit(decoded_line) + + pyside_install.stdout.close() + return_code = pyside_install.wait() + + if return_code and return_code != 0: + msg = ("Failed to create the project! " + "The installation of PySide2 has failed!") + self.failed.emit(msg, return_code) + raise RuntimeError(msg) + self.progress.emit(100) self.finished.emit("Project successfully built!") @@ -266,26 +304,30 @@ class UEPluginInstallWorker(QtCore.QObject): # in order to successfully build the plugin, # It must be built outside the Engine directory and then moved - build_plugin_cmd: List[str] = [f'{uat_path.as_posix()}', - 'BuildPlugin', - f'-Plugin={uplugin_path.as_posix()}', - f'-Package={temp_dir.as_posix()}'] + build_plugin_cmd: List[str] = [f"{uat_path.as_posix()}", + "BuildPlugin", + f"-Plugin={uplugin_path.as_posix()}", + f"-Package={temp_dir.as_posix()}"] build_proc = subprocess.Popen(build_plugin_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return_code: Union[None, int] = None for line in build_proc.stdout: - decoded_line: str = line.decode(errors='replace') - print(decoded_line, end='') + decoded_line: str = line.decode(errors="replace") + print(decoded_line, end="") self.log.emit(decoded_line) + if return_code is None: + return_code = retrieve_exit_code(decoded_line) parse_comp_progress(decoded_line, self.progress) build_proc.stdout.close() - return_code = build_proc.wait() + build_proc.wait() if return_code and return_code != 0: - msg = 'Failed to build plugin' \ - f' project! Exited with return code {return_code}' + msg = ("Failed to build plugin" + f" project! Exited with return code {return_code}") + dir_util.remove_tree(temp_dir.as_posix()) self.failed.emit(msg, return_code) raise RuntimeError(msg) diff --git a/openpype/hosts/webpublisher/lib.py b/openpype/hosts/webpublisher/lib.py index 4bc3f1db80..b207f85b46 100644 --- a/openpype/hosts/webpublisher/lib.py +++ b/openpype/hosts/webpublisher/lib.py @@ -30,7 +30,7 @@ def parse_json(path): Returns: (dict) or None if unparsable Raises: - AsssertionError if 'path' doesn't exist + AssertionError if 'path' doesn't exist """ path = path.strip('\"') assert os.path.isfile(path), ( diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 7cc296f47b..8adae34827 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -889,7 +889,8 @@ class ApplicationLaunchContext: self.modules_manager = ModulesManager() # Logger - logger_name = "{}-{}".format(self.__class__.__name__, self.app_name) + logger_name = "{}-{}".format(self.__class__.__name__, + self.application.full_name) self.log = Logger.get_logger(logger_name) self.executable = executable @@ -968,7 +969,7 @@ class ApplicationLaunchContext: """Helper to collect application launch hooks from addons. Module have to have implemented 'get_launch_hook_paths' method which - can expect appliction as argument or nothing. + can expect application as argument or nothing. Returns: List[str]: Paths to launch hook directories. @@ -1246,7 +1247,7 @@ class ApplicationLaunchContext: args_len_str = " ({})".format(len(args)) self.log.info( "Launching \"{}\" with args{}: {}".format( - self.app_name, args_len_str, args + self.application.full_name, args_len_str, args ) ) self.launch_args = args @@ -1271,7 +1272,9 @@ class ApplicationLaunchContext: exc_info=True ) - self.log.debug("Launch of {} finished.".format(self.app_name)) + self.log.debug("Launch of {} finished.".format( + self.application.full_name + )) return self.process @@ -1508,8 +1511,8 @@ def prepare_app_environments( if key in source_env: source_env[key] = value - # `added_env_keys` has debug purpose - added_env_keys = {app.group.name, app.name} + # `app_and_tool_labels` has debug purpose + app_and_tool_labels = [app.full_name] # Environments for application environments = [ app.group.environment, @@ -1532,15 +1535,14 @@ def prepare_app_environments( for group_name in sorted(groups_by_name.keys()): group = groups_by_name[group_name] environments.append(group.environment) - added_env_keys.add(group_name) for tool_name in sorted(tool_by_group_name[group_name].keys()): tool = tool_by_group_name[group_name][tool_name] environments.append(tool.environment) - added_env_keys.add(tool.name) + app_and_tool_labels.append(tool.full_name) log.debug( "Will add environments for apps and tools: {}".format( - ", ".join(added_env_keys) + ", ".join(app_and_tool_labels) ) ) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index b5cd15f41a..6054d2a92a 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -9,7 +9,7 @@ from abc import ABCMeta, abstractmethod, abstractproperty import six import clique -# Global variable which store attribude definitions by type +# Global variable which store attribute definitions by type # - default types are registered on import _attr_defs_by_type = {} @@ -93,7 +93,7 @@ class AbstractAttrDefMeta(ABCMeta): @six.add_metaclass(AbstractAttrDefMeta) class AbstractAttrDef(object): - """Abstraction of attribute definiton. + """Abstraction of attribute definition. Each attribute definition must have implemented validation and conversion method. @@ -427,7 +427,7 @@ class EnumDef(AbstractAttrDef): """Enumeration of single item from items. Args: - items: Items definition that can be coverted using + items: Items definition that can be converted using 'prepare_enum_items'. default: Default value. Must be one key(value) from passed items. """ diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 096201312f..bed00fe659 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -156,7 +156,7 @@ class EventCallback(object): self._enabled = enabled def deregister(self): - """Calling this funcion will cause that callback will be removed.""" + """Calling this function will cause that callback will be removed.""" # Fake reference self._ref_valid = False diff --git a/openpype/lib/execute.py b/openpype/lib/execute.py index 759a4db0cb..ef456395e7 100644 --- a/openpype/lib/execute.py +++ b/openpype/lib/execute.py @@ -8,6 +8,8 @@ import tempfile from .log import Logger from .vendor_bin_utils import find_executable +from .openpype_version import is_running_from_build + # MSDN process creation flag (Windows only) CREATE_NO_WINDOW = 0x08000000 @@ -102,6 +104,10 @@ def run_subprocess(*args, **kwargs): if ( platform.system().lower() == "windows" and "creationflags" not in kwargs + # shell=True already tries to hide the console window + # and passing these creationflags then shows the window again + # so we avoid it for shell=True cases + and kwargs.get("shell") is not True ): kwargs["creationflags"] = ( subprocess.CREATE_NEW_PROCESS_GROUP @@ -157,18 +163,20 @@ def run_subprocess(*args, **kwargs): def clean_envs_for_openpype_process(env=None): - """Modify environemnts that may affect OpenPype process. + """Modify environments that may affect OpenPype process. Main reason to implement this function is to pop PYTHONPATH which may be affected by in-host environments. """ if env is None: env = os.environ - return { - key: value - for key, value in env.items() - if key not in ("PYTHONPATH",) - } + + # Exclude some environment variables from a copy of the environment + env = env.copy() + for key in ["PYTHONPATH", "PYTHONHOME"]: + env.pop(key, None) + + return env def run_openpype_process(*args, **kwargs): @@ -196,6 +204,11 @@ def run_openpype_process(*args, **kwargs): # Skip envs that can affect OpenPype process # - fill more if you find more env = clean_envs_for_openpype_process(os.environ) + + # Only keep OpenPype version if we are running from build. + if not is_running_from_build(): + env.pop("OPENPYPE_VERSION", None) + return run_subprocess(args, env=env, **kwargs) diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index fe70b37cb1..80f4e81f2c 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -13,6 +13,16 @@ else: from shutil import copyfile +class DuplicateDestinationError(ValueError): + """Error raised when transfer destination already exists in queue. + + The error is only raised if `allow_queue_replacements` is False on the + FileTransaction instance and the added file to transfer is of a different + src file than the one already detected in the queue. + + """ + + class FileTransaction(object): """File transaction with rollback options. @@ -44,7 +54,7 @@ class FileTransaction(object): MODE_COPY = 0 MODE_HARDLINK = 1 - def __init__(self, log=None): + def __init__(self, log=None, allow_queue_replacements=False): if log is None: log = logging.getLogger("FileTransaction") @@ -60,6 +70,8 @@ class FileTransaction(object): # Backup file location mapping to original locations self._backup_to_original = {} + self._allow_queue_replacements = allow_queue_replacements + def add(self, src, dst, mode=MODE_COPY): """Add a new file to transfer queue. @@ -82,6 +94,14 @@ class FileTransaction(object): src, dst)) return else: + if not self._allow_queue_replacements: + raise DuplicateDestinationError( + "Transfer to destination is already in queue: " + "{} -> {}. It's not allowed to be replaced by " + "a new transfer from {}".format( + queued_src, dst, src + )) + self.log.warning("File transfer in queue replaced..") self.log.debug( "Removed from queue: {} -> {} replaced by {} -> {}".format( @@ -110,7 +130,7 @@ class FileTransaction(object): path_same = self._same_paths(src, dst) if path_same: self.log.debug( - "Source and destionation are same files {} -> {}".format( + "Source and destination are same files {} -> {}".format( src, dst)) continue diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index 0f99efb430..9be1736abf 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -256,17 +256,18 @@ class TemplatesDict(object): elif isinstance(templates, dict): self._raw_templates = copy.deepcopy(templates) self._templates = templates - self._objected_templates = self.create_ojected_templates(templates) + self._objected_templates = self.create_objected_templates( + templates) else: raise TypeError("<{}> argument must be a dict, not {}.".format( self.__class__.__name__, str(type(templates)) )) def __getitem__(self, key): - return self.templates[key] + return self.objected_templates[key] def get(self, key, *args, **kwargs): - return self.templates.get(key, *args, **kwargs) + return self.objected_templates.get(key, *args, **kwargs) @property def raw_templates(self): @@ -280,8 +281,21 @@ class TemplatesDict(object): def objected_templates(self): return self._objected_templates - @classmethod - def create_ojected_templates(cls, templates): + def _create_template_object(self, template): + """Create template object from a template string. + + Separated into method to give option change class of templates. + + Args: + template (str): Template string. + + Returns: + StringTemplate: Object of template. + """ + + return StringTemplate(template) + + def create_objected_templates(self, templates): if not isinstance(templates, dict): raise TypeError("Expected dict object, got {}".format( str(type(templates)) @@ -297,7 +311,7 @@ class TemplatesDict(object): for key in tuple(item.keys()): value = item[key] if isinstance(value, six.string_types): - item[key] = StringTemplate(value) + item[key] = self._create_template_object(value) elif isinstance(value, dict): inner_queue.append(value) return objected_templates diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 799693554f..57968b3700 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -540,7 +540,7 @@ def convert_for_ffmpeg( continue # Remove attributes that have string value longer than allowed length - # for ffmpeg or when containt unallowed symbols + # for ffmpeg or when contain unallowed symbols erase_reason = "Missing reason" erase_attribute = False if len(attr_value) > MAX_FFMPEG_STRING_LEN: @@ -680,7 +680,7 @@ def convert_input_paths_for_ffmpeg( continue # Remove attributes that have string value longer than allowed - # length for ffmpeg or when containt unallowed symbols + # length for ffmpeg or when containing unallowed symbols erase_reason = "Missing reason" erase_attribute = False if len(attr_value) > MAX_FFMPEG_STRING_LEN: @@ -968,7 +968,7 @@ def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): if source_ffmpeg_cmd: # Define bitrate arguments bit_rate_args = ("-b:v", "-vb",) - # Seprate the two variables in case something else should be copied + # Separate the two variables in case something else should be copied # from source command copy_args = [] copy_args.extend(bit_rate_args) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 00dd1955fe..f27c78d486 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -224,18 +224,26 @@ def find_tool_in_custom_paths(paths, tool, validation_func=None): def _check_args_returncode(args): try: - # Python 2 compatibility where DEVNULL is not available + kwargs = {} + if platform.system().lower() == "windows": + kwargs["creationflags"] = ( + subprocess.CREATE_NEW_PROCESS_GROUP + | getattr(subprocess, "DETACHED_PROCESS", 0) + | getattr(subprocess, "CREATE_NO_WINDOW", 0) + ) + if hasattr(subprocess, "DEVNULL"): proc = subprocess.Popen( args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + **kwargs ) proc.wait() else: with open(os.devnull, "w") as devnull: proc = subprocess.Popen( - args, stdout=devnull, stderr=devnull, + args, stdout=devnull, stderr=devnull, **kwargs ) proc.wait() @@ -252,7 +260,7 @@ def _oiio_executable_validation(filepath): that it can be executed. For that is used '--help' argument which is fast and does not need any other inputs. - Any possible crash of missing libraries or invalid build should be catched. + Any possible crash of missing libraries or invalid build should be caught. Main reason is to validate if executable can be executed on OS just running which can be issue ob linux machines. @@ -321,7 +329,7 @@ def _ffmpeg_executable_validation(filepath): that it can be executed. For that is used '-version' argument which is fast and does not need any other inputs. - Any possible crash of missing libraries or invalid build should be catched. + Any possible crash of missing libraries or invalid build should be caught. Main reason is to validate if executable can be executed on OS just running which can be issue ob linux machines. diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 0fd21492e8..ed1eeb04cd 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -472,7 +472,7 @@ class OpenPypeModule: Args: application (Application): Application that is launched. - env (dict): Current environemnt variables. + env (dict): Current environment variables. """ pass @@ -622,7 +622,7 @@ class ModulesManager: # Check if class is abstract (Developing purpose) if inspect.isabstract(modules_item): - # Find missing implementations by convetion on `abc` module + # Find abstract attributes by convention on `abc` module not_implemented = [] for attr_name in dir(modules_item): attr = getattr(modules_item, attr_name, None) @@ -708,13 +708,13 @@ class ModulesManager: ] def collect_global_environments(self): - """Helper to collect global enviornment variabled from modules. + """Helper to collect global environment variabled from modules. Returns: dict: Global environment variables from enabled modules. Raises: - AssertionError: Gobal environment variables must be unique for + AssertionError: Global environment variables must be unique for all modules. """ module_envs = {} @@ -1174,7 +1174,7 @@ class TrayModulesManager(ModulesManager): def get_module_settings_defs(): - """Check loaded addons/modules for existence of thei settings definition. + """Check loaded addons/modules for existence of their settings definition. Check if OpenPype addon/module as python module has class that inherit from `ModuleSettingsDef` in python module variables (imported @@ -1204,7 +1204,7 @@ def get_module_settings_defs(): continue if inspect.isabstract(attr): - # Find missing implementations by convetion on `abc` module + # Find missing implementations by convention on `abc` module not_implemented = [] for attr_name in dir(attr): attr = getattr(attr, attr_name, None) @@ -1293,7 +1293,7 @@ class BaseModuleSettingsDef: class ModuleSettingsDef(BaseModuleSettingsDef): - """Settings definiton with separated system and procect settings parts. + """Settings definition with separated system and procect settings parts. Reduce conditions that must be checked and adds predefined methods for each case. diff --git a/openpype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py index 80979c83ab..47af002f7a 100644 --- a/openpype/modules/clockify/clockify_api.py +++ b/openpype/modules/clockify/clockify_api.py @@ -247,7 +247,7 @@ class ClockifyAPI: current_timer = self.get_in_progress() # Check if is currently run another times and has same values - # DO not restart the timer, if it is already running for curent task + # DO not restart the timer, if it is already running for current task if current_timer: current_timer_hierarchy = current_timer.get("description") current_project_id = current_timer.get("projectId") diff --git a/openpype/modules/clockify/clockify_module.py b/openpype/modules/clockify/clockify_module.py index 200a268ad7..b6efec7907 100644 --- a/openpype/modules/clockify/clockify_module.py +++ b/openpype/modules/clockify/clockify_module.py @@ -76,7 +76,7 @@ class ClockifyModule(OpenPypeModule, ITrayModule, IPluginPaths): return def get_plugin_paths(self): - """Implementaton of IPluginPaths to get plugin paths.""" + """Implementation of IPluginPaths to get plugin paths.""" actions_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "launcher_actions" ) diff --git a/openpype/modules/clockify/widgets.py b/openpype/modules/clockify/widgets.py index 8c28f38b6e..86e67569f2 100644 --- a/openpype/modules/clockify/widgets.py +++ b/openpype/modules/clockify/widgets.py @@ -34,7 +34,7 @@ class MessageWidget(QtWidgets.QWidget): def _ui_layout(self, messages): if not messages: - messages = ["*Misssing messages (This is a bug)*", ] + messages = ["*Missing messages (This is a bug)*", ] elif not isinstance(messages, (tuple, list)): messages = [messages, ] diff --git a/openpype/modules/deadline/plugins/publish/collect_pools.py b/openpype/modules/deadline/plugins/publish/collect_pools.py index 48130848d5..e221eb00ea 100644 --- a/openpype/modules/deadline/plugins/publish/collect_pools.py +++ b/openpype/modules/deadline/plugins/publish/collect_pools.py @@ -3,21 +3,60 @@ """ import pyblish.api +from openpype.lib import TextDef +from openpype.pipeline.publish import OpenPypePyblishPluginMixin -class CollectDeadlinePools(pyblish.api.InstancePlugin): +class CollectDeadlinePools(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): """Collect pools from instance if present, from Setting otherwise.""" order = pyblish.api.CollectorOrder + 0.420 label = "Collect Deadline Pools" - families = ["rendering", "render.farm", "renderFarm", "renderlayer"] + families = ["rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender"] primary_pool = None secondary_pool = None + @classmethod + def apply_settings(cls, project_settings, system_settings): + # deadline.publish.CollectDeadlinePools + settings = project_settings["deadline"]["publish"]["CollectDeadlinePools"] # noqa + cls.primary_pool = settings.get("primary_pool", None) + cls.secondary_pool = settings.get("secondary_pool", None) + def process(self, instance): + + attr_values = self.get_attr_values_from_data(instance.data) if not instance.data.get("primaryPool"): - instance.data["primaryPool"] = self.primary_pool or "none" + instance.data["primaryPool"] = ( + attr_values.get("primaryPool") or self.primary_pool or "none" + ) if not instance.data.get("secondaryPool"): - instance.data["secondaryPool"] = self.secondary_pool or "none" + instance.data["secondaryPool"] = ( + attr_values.get("secondaryPool") or self.secondary_pool or "none" # noqa + ) + + @classmethod + def get_attribute_defs(cls): + # TODO: Preferably this would be an enum for the user + # but the Deadline server URL can be dynamic and + # can be set per render instance. Since get_attribute_defs + # can't be dynamic unfortunately EnumDef isn't possible (yet?) + # pool_names = self.deadline_module.get_deadline_pools(deadline_url, + # self.log) + # secondary_pool_names = ["-"] + pool_names + + return [ + TextDef("primaryPool", + label="Primary Pool", + default=cls.primary_pool), + TextDef("secondaryPool", + label="Secondary Pool", + default=cls.secondary_pool) + ] diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index 038ee4fc03..bcf0850768 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -106,7 +106,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): # define chunk and priority chunk_size = instance.context.data.get("chunk") - if chunk_size == 0: + if not chunk_size: chunk_size = self.deadline_chunk_size # search for %02d pattern in name, and padding number diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index aff34c7e4a..5c598df94b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -32,7 +32,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, label = "Submit Nuke to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["nuke"] - families = ["render", "prerender.farm"] + families = ["render", "prerender"] optional = True targets = ["local"] @@ -66,7 +66,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, ), NumberDef( "concurrency", - label="Concurency", + label="Concurrency", default=cls.concurrent_tasks, decimals=0, minimum=1, @@ -76,13 +76,26 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, "use_gpu", default=cls.use_gpu, label="Use GPU" + ), + BoolDef( + "suspend_publish", + default=False, + label="Suspend publish" ) ] def process(self, instance): + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + instance.data["attributeValues"] = self.get_attr_values_from_data( instance.data) + # add suspend_publish attributeValue to instance data + instance.data["suspend_publish"] = instance.data["attributeValues"][ + "suspend_publish"] + instance.data["toBeRenderedOn"] = "deadline" families = instance.data["families"] @@ -168,10 +181,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, resp.json()["_id"]) # redefinition of families - if "render.farm" in families: + if "render" in instance.data["family"]: instance.data['family'] = 'write' families.insert(0, "render2d") - elif "prerender.farm" in families: + elif "prerender" in instance.data["family"]: instance.data['family'] = 'write' families.insert(0, "prerender") instance.data["families"] = families diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 53c09ad22f..4765772bcf 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -85,10 +85,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): These jobs are dependent on a deadline or muster job submission prior to this plug-in. - - In case of Deadline, it creates dependend job on farm publishing + - In case of Deadline, it creates dependent job on farm publishing rendered image sequence. - - In case of Muster, there is no need for such thing as dependend job, + - In case of Muster, there is no need for such thing as dependent job, post action will be executed and rendered sequence will be published. Options in instance.data: @@ -108,7 +108,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): - publishJobState (str, Optional): "Active" or "Suspended" This defaults to "Suspended" - - expectedFiles (list or dict): explained bellow + - expectedFiles (list or dict): explained below """ @@ -158,8 +158,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # regex for finding frame number in string R_FRAME_NUMBER = re.compile(r'.+\.(?P[0-9]+)\..+') - # mapping of instance properties to be transfered to new instance for every - # specified family + # mapping of instance properties to be transferred to new instance + # for every specified family instance_transfer = { "slate": ["slateFrames", "slate"], "review": ["lutPath"], @@ -398,7 +398,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): continue r_col.indexes.remove(frame) - # now we need to translate published names from represenation + # now we need to translate published names from representation # back. This is tricky, right now we'll just use same naming # and only switch frame numbers resource_files = [] @@ -535,7 +535,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if preview: new_instance["review"] = True - # create represenation + # create representation if isinstance(col, (list, tuple)): files = [os.path.basename(f) for f in col] else: @@ -748,7 +748,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # type: (pyblish.api.Instance) -> None """Process plugin. - Detect type of renderfarm submission and create and post dependend job + Detect type of renderfarm submission and create and post dependent job in case of Deadline. It creates json file with metadata needed for publishing in directory of render. @@ -756,6 +756,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instance (pyblish.api.Instance): Instance data. """ + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + data = instance.data.copy() context = instance.context self.context = context @@ -940,17 +944,28 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # we cannot attach AOVs to other subsets as we consider every # AOV subset of its own. - config = instance.data["colorspaceConfig"] additional_data = { "renderProducts": instance.data["renderProducts"], "colorspaceConfig": instance.data["colorspaceConfig"], "display": instance.data["colorspaceDisplay"], - "view": instance.data["colorspaceView"], - "colorspaceTemplate": config.replace( - str(context.data["anatomy"].roots["work"]), "{root[work]}" - ) + "view": instance.data["colorspaceView"] } + # Get templated path from absolute config path. + anatomy = instance.context.data["anatomy"] + colorspaceTemplate = instance.data["colorspaceConfig"] + success, rootless_staging_dir = ( + anatomy.find_root_template_from_path(colorspaceTemplate) + ) + if success: + colorspaceTemplate = rootless_staging_dir + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(colorspaceTemplate)) + additional_data["colorspaceTemplate"] = colorspaceTemplate + if len(data.get("attachTo")) > 0: assert len(data.get("expectedFiles")[0].keys()) == 1, ( "attaching multiple AOVs or renderable cameras to " @@ -982,7 +997,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instances = [instance_skeleton_data] # if we are attaching to other subsets, create copy of existing - # instances, change data to match thats subset and replace + # instances, change data to match its subset and replace # existing instances with modified data if instance.data.get("attachTo"): self.log.info("Attaching render to subset:") diff --git a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py index 78eed17c98..7c8ab62d4d 100644 --- a/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/openpype/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -17,10 +17,18 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, label = "Validate Deadline Pools" order = pyblish.api.ValidatorOrder - families = ["rendering", "render.farm", "renderFarm", "renderlayer"] + families = ["rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender"] optional = True def process(self, instance): + if not instance.data.get("farm"): + self.log.info("Skipping local instance.") + return + # get default deadline webservice url from deadline module deadline_url = instance.context.data["defaultDeadline"] self.log.info("deadline_url::{}".format(deadline_url)) diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index ead647b41d..be1d3ff920 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -44,7 +44,7 @@ class AddonSettingsDef(JsonFilesSettingsDef): class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): - """This Addon has defined it's settings and interface. + """This Addon has defined its settings and interface. This example has system settings with an enabled option. And use few other interfaces: diff --git a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py index 1ad7a17785..333228c699 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py @@ -44,7 +44,7 @@ def clone_review_session(session, entity): class CloneReviewSession(ServerAction): '''Generate Client Review action - `label` a descriptive string identifing your action. + `label` a descriptive string identifying your action. `varaint` To group actions together, give them the same label and specify a unique variant per action. `identifier` a unique identifier for your action. diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py index 21382007a0..42a279e333 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -230,7 +230,7 @@ class CreateDailyReviewSessionServerAction(ServerAction): if not today_session_name: continue - # Find matchin review session + # Find matching review session project_review_sessions = review_sessions_by_project_id[project_id] todays_session = None yesterdays_session = None diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 332648cd02..02231cbe3c 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -124,7 +124,7 @@ class PrepareProjectServer(ServerAction): root_items.append({ "type": "label", "value": ( - "

NOTE: Roots are crutial for path filling" + "

NOTE: Roots are crucial for path filling" " (and creating folder structure).

" ) }) diff --git a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py index 1209375f82..a698195c59 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py @@ -9,7 +9,7 @@ from openpype_modules.ftrack.lib import ( class PushHierValuesToNonHier(ServerAction): - """Action push hierarchical custom attribute values to non hierarchical. + """Action push hierarchical custom attribute values to non-hierarchical. Hierarchical value is also pushed to their task entities. @@ -119,17 +119,109 @@ class PushHierValuesToNonHier(ServerAction): self.join_query_keys(object_ids) )).all() - output = {} + attrs_by_obj_id = collections.defaultdict(list) hiearchical = [] for attr in attrs: if attr["is_hierarchical"]: hiearchical.append(attr) continue obj_id = attr["object_type_id"] - if obj_id not in output: - output[obj_id] = [] - output[obj_id].append(attr) - return output, hiearchical + attrs_by_obj_id[obj_id].append(attr) + return attrs_by_obj_id, hiearchical + + def query_attr_value( + self, + session, + hier_attrs, + attrs_by_obj_id, + dst_object_type_ids, + task_entity_ids, + non_task_entity_ids, + parent_id_by_entity_id + ): + all_non_task_ids_with_parents = set() + for entity_id in non_task_entity_ids: + all_non_task_ids_with_parents.add(entity_id) + _entity_id = entity_id + while True: + parent_id = parent_id_by_entity_id.get(_entity_id) + if ( + parent_id is None + or parent_id in all_non_task_ids_with_parents + ): + break + all_non_task_ids_with_parents.add(parent_id) + _entity_id = parent_id + + all_entity_ids = ( + set(all_non_task_ids_with_parents) + | set(task_entity_ids) + ) + attr_ids = {attr["id"] for attr in hier_attrs} + for obj_id in dst_object_type_ids: + attrs = attrs_by_obj_id.get(obj_id) + if attrs is not None: + for attr in attrs: + attr_ids.add(attr["id"]) + + real_values_by_entity_id = { + entity_id: {} + for entity_id in all_entity_ids + } + + attr_values = query_custom_attributes( + session, attr_ids, all_entity_ids, True + ) + for item in attr_values: + entity_id = item["entity_id"] + attr_id = item["configuration_id"] + real_values_by_entity_id[entity_id][attr_id] = item["value"] + + # Fill hierarchical values + hier_attrs_key_by_id = { + hier_attr["id"]: hier_attr + for hier_attr in hier_attrs + } + hier_values_per_entity_id = {} + for entity_id in all_non_task_ids_with_parents: + real_values = real_values_by_entity_id[entity_id] + hier_values_per_entity_id[entity_id] = {} + for attr_id, attr in hier_attrs_key_by_id.items(): + key = attr["key"] + hier_values_per_entity_id[entity_id][key] = ( + real_values.get(attr_id) + ) + + output = {} + for entity_id in non_task_entity_ids: + output[entity_id] = {} + for attr in hier_attrs_key_by_id.values(): + key = attr["key"] + value = hier_values_per_entity_id[entity_id][key] + tried_ids = set() + if value is None: + tried_ids.add(entity_id) + _entity_id = entity_id + while value is None: + parent_id = parent_id_by_entity_id.get(_entity_id) + if not parent_id: + break + value = hier_values_per_entity_id[parent_id][key] + if value is not None: + break + _entity_id = parent_id + tried_ids.add(parent_id) + + if value is None: + value = attr["default"] + + if value is not None: + for ent_id in tried_ids: + hier_values_per_entity_id[ent_id][key] = value + + output[entity_id][key] = value + + return real_values_by_entity_id, output def propagate_values(self, session, event, selected_entities): ftrack_settings = self.get_ftrack_settings( @@ -156,29 +248,24 @@ class PushHierValuesToNonHier(ServerAction): } task_object_type = object_types_by_low_name["task"] - destination_object_types = [task_object_type] + dst_object_type_ids = {task_object_type["id"]} for ent_type in interest_entity_types: obj_type = object_types_by_low_name.get(ent_type) - if obj_type and obj_type not in destination_object_types: - destination_object_types.append(obj_type) - - destination_object_type_ids = set( - obj_type["id"] - for obj_type in destination_object_types - ) + if obj_type: + dst_object_type_ids.add(obj_type["id"]) interest_attributes = action_settings["interest_attributes"] # Find custom attributes definitions attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, destination_object_type_ids, interest_attributes + session, dst_object_type_ids, interest_attributes ) # Filter destination object types if they have any object specific # custom attribute - for obj_id in tuple(destination_object_type_ids): + for obj_id in tuple(dst_object_type_ids): if obj_id not in attrs_by_obj_id: - destination_object_type_ids.remove(obj_id) + dst_object_type_ids.remove(obj_id) - if not destination_object_type_ids: + if not dst_object_type_ids: # TODO report that there are not matching custom attributes return { "success": True, @@ -192,14 +279,14 @@ class PushHierValuesToNonHier(ServerAction): session, selected_ids, project_entity, - destination_object_type_ids + dst_object_type_ids ) self.log.debug("Preparing whole project hierarchy by ids.") entities_by_obj_id = { obj_id: [] - for obj_id in destination_object_type_ids + for obj_id in dst_object_type_ids } self.log.debug("Filtering Task entities.") @@ -223,10 +310,16 @@ class PushHierValuesToNonHier(ServerAction): "message": "Nothing to do in your selection." } - self.log.debug("Getting Hierarchical custom attribute values parents.") - hier_values_by_entity_id = self.get_hier_values( + self.log.debug("Getting Custom attribute values.") + ( + real_values_by_entity_id, + hier_values_by_entity_id + ) = self.query_attr_value( session, hier_attrs, + attrs_by_obj_id, + dst_object_type_ids, + task_entity_ids, non_task_entity_ids, parent_id_by_entity_id ) @@ -237,7 +330,8 @@ class PushHierValuesToNonHier(ServerAction): hier_attrs, task_entity_ids, hier_values_by_entity_id, - parent_id_by_entity_id + parent_id_by_entity_id, + real_values_by_entity_id ) self.log.debug("Setting values to entities themselves.") @@ -245,7 +339,8 @@ class PushHierValuesToNonHier(ServerAction): session, entities_by_obj_id, attrs_by_obj_id, - hier_values_by_entity_id + hier_values_by_entity_id, + real_values_by_entity_id ) return True @@ -322,112 +417,64 @@ class PushHierValuesToNonHier(ServerAction): return parent_id_by_entity_id, filtered_entities - def get_hier_values( - self, - session, - hier_attrs, - focus_entity_ids, - parent_id_by_entity_id - ): - all_ids_with_parents = set() - for entity_id in focus_entity_ids: - all_ids_with_parents.add(entity_id) - _entity_id = entity_id - while True: - parent_id = parent_id_by_entity_id.get(_entity_id) - if ( - not parent_id - or parent_id in all_ids_with_parents - ): - break - all_ids_with_parents.add(parent_id) - _entity_id = parent_id - - hier_attr_ids = tuple(hier_attr["id"] for hier_attr in hier_attrs) - hier_attrs_key_by_id = { - hier_attr["id"]: hier_attr["key"] - for hier_attr in hier_attrs - } - - values_per_entity_id = {} - for entity_id in all_ids_with_parents: - values_per_entity_id[entity_id] = {} - for key in hier_attrs_key_by_id.values(): - values_per_entity_id[entity_id][key] = None - - values = query_custom_attributes( - session, hier_attr_ids, all_ids_with_parents, True - ) - for item in values: - entity_id = item["entity_id"] - key = hier_attrs_key_by_id[item["configuration_id"]] - - values_per_entity_id[entity_id][key] = item["value"] - - output = {} - for entity_id in focus_entity_ids: - output[entity_id] = {} - for key in hier_attrs_key_by_id.values(): - value = values_per_entity_id[entity_id][key] - tried_ids = set() - if value is None: - tried_ids.add(entity_id) - _entity_id = entity_id - while value is None: - parent_id = parent_id_by_entity_id.get(_entity_id) - if not parent_id: - break - value = values_per_entity_id[parent_id][key] - if value is not None: - break - _entity_id = parent_id - tried_ids.add(parent_id) - - if value is not None: - for ent_id in tried_ids: - values_per_entity_id[ent_id][key] = value - - output[entity_id][key] = value - return output - def set_task_attr_values( self, session, hier_attrs, task_entity_ids, hier_values_by_entity_id, - parent_id_by_entity_id + parent_id_by_entity_id, + real_values_by_entity_id ): hier_attr_id_by_key = { attr["key"]: attr["id"] for attr in hier_attrs } + filtered_task_ids = set() for task_id in task_entity_ids: - parent_id = parent_id_by_entity_id.get(task_id) or {} + parent_id = parent_id_by_entity_id.get(task_id) parent_values = hier_values_by_entity_id.get(parent_id) - if not parent_values: - continue + if parent_values: + filtered_task_ids.add(task_id) + if not filtered_task_ids: + return + + for task_id in filtered_task_ids: + parent_id = parent_id_by_entity_id[task_id] + parent_values = hier_values_by_entity_id[parent_id] hier_values_by_entity_id[task_id] = {} + real_task_attr_values = real_values_by_entity_id[task_id] for key, value in parent_values.items(): hier_values_by_entity_id[task_id][key] = value + if value is None: + continue + configuration_id = hier_attr_id_by_key[key] _entity_key = collections.OrderedDict([ ("configuration_id", configuration_id), ("entity_id", task_id) ]) - - session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", + op = None + if configuration_id not in real_task_attr_values: + op = ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + _entity_key, + {"value": value} + ) + elif real_task_attr_values[configuration_id] != value: + op = ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", _entity_key, "value", - ftrack_api.symbol.NOT_SET, + real_task_attr_values[configuration_id], value ) - ) - if len(session.recorded_operations) > 100: - session.commit() + + if op is not None: + session.recorded_operations.push(op) + if len(session.recorded_operations) > 100: + session.commit() session.commit() @@ -436,39 +483,68 @@ class PushHierValuesToNonHier(ServerAction): session, entities_by_obj_id, attrs_by_obj_id, - hier_values_by_entity_id + hier_values_by_entity_id, + real_values_by_entity_id ): + """Push values from hierarchical custom attributes to non-hierarchical. + + Args: + session (ftrack_api.Sessison): Session which queried entities, + values and which is used for change propagation. + entities_by_obj_id (dict[str, list[str]]): TypedContext + ftrack entity ids where the attributes are propagated by their + object ids. + attrs_by_obj_id (dict[str, ftrack_api.Entity]): Objects of + 'CustomAttributeConfiguration' by their ids. + hier_values_by_entity_id (doc[str, dict[str, Any]]): Attribute + values by entity id and by their keys. + real_values_by_entity_id (doc[str, dict[str, Any]]): Real attribute + values of entities. + """ + for object_id, entity_ids in entities_by_obj_id.items(): attrs = attrs_by_obj_id.get(object_id) if not attrs or not entity_ids: continue - for attr in attrs: - for entity_id in entity_ids: - value = ( - hier_values_by_entity_id - .get(entity_id, {}) - .get(attr["key"]) - ) + for entity_id in entity_ids: + real_values = real_values_by_entity_id.get(entity_id) + hier_values = hier_values_by_entity_id.get(entity_id) + if hier_values is None: + continue + + for attr in attrs: + attr_id = attr["id"] + attr_key = attr["key"] + value = hier_values.get(attr_key) if value is None: continue _entity_key = collections.OrderedDict([ - ("configuration_id", attr["id"]), + ("configuration_id", attr_id), ("entity_id", entity_id) ]) - session.recorded_operations.push( - ftrack_api.operation.UpdateEntityOperation( - "ContextCustomAttributeValue", + op = None + if attr_id not in real_values: + op = ftrack_api.operation.CreateEntityOperation( + "CustomAttributeValue", + _entity_key, + {"value": value} + ) + elif real_values[attr_id] != value: + op = ftrack_api.operation.UpdateEntityOperation( + "CustomAttributeValue", _entity_key, "value", - ftrack_api.symbol.NOT_SET, + real_values[attr_id], value ) - ) - if len(session.recorded_operations) > 100: - session.commit() + + if op is not None: + session.recorded_operations.push(op) + if len(session.recorded_operations) > 100: + session.commit() session.commit() diff --git a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py index d160b7200d..f6899843a3 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py +++ b/openpype/modules/ftrack/event_handlers_server/action_tranfer_hierarchical_values.py @@ -12,7 +12,7 @@ from openpype_modules.ftrack.lib.avalon_sync import create_chunks class TransferHierarchicalValues(ServerAction): - """Transfer values across hierarhcical attributes. + """Transfer values across hierarchical attributes. Aalso gives ability to convert types meanwhile. That is limited to conversions between numbers and strings @@ -67,7 +67,7 @@ class TransferHierarchicalValues(ServerAction): "type": "label", "value": ( "Didn't found custom attributes" - " that can be transfered." + " that can be transferred." ) }] } diff --git a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py index a65ae46545..a100c34f67 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py +++ b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py @@ -279,7 +279,7 @@ class NextTaskUpdate(BaseEvent): except Exception: session.rollback() self.log.warning( - "\"{}\" status couldnt be set to \"{}\"".format( + "\"{}\" status couldn't be set to \"{}\"".format( ent_path, new_status["name"] ), exc_info=True diff --git a/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py index dc76920a57..ed630ad59d 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py @@ -1,6 +1,6 @@ import collections -import datetime import copy +from typing import Any import ftrack_api from openpype_modules.ftrack.lib import ( @@ -9,13 +9,30 @@ from openpype_modules.ftrack.lib import ( ) -class PushFrameValuesToTaskEvent(BaseEvent): +class PushHierValuesToNonHierEvent(BaseEvent): + """Push value changes between hierarchical and non-hierarchical attributes. + + Changes of non-hierarchical attributes are pushed to hierarchical and back. + The attributes must have same definition of custom attribute. + + Handler does not handle changes of hierarchical parents. So if entity does + not have explicitly set value of hierarchical attribute and any parent + would change it the change would not be propagated. + + The handler also push the value to task entity on task creation + and movement. To push values between hierarchical & non-hierarchical + add 'Task' to entity types in settings. + + Todos: + Task attribute values push on create/move should be possible to + enabled by settings. + """ + # Ignore event handler by default cust_attrs_query = ( "select id, key, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where key in ({}) and" - " (object_type_id in ({}) or is_hierarchical is true)" + " where key in ({})" ) _cached_task_object_id = None @@ -26,35 +43,35 @@ class PushFrameValuesToTaskEvent(BaseEvent): settings_key = "sync_hier_entity_attributes" - def session_user_id(self, session): - if self._cached_user_id is None: - user = session.query( - "User where username is \"{}\"".format(session.api_user) - ).one() - self._cached_user_id = user["id"] - return self._cached_user_id + def filter_entities_info( + self, event: ftrack_api.event.base.Event + ) -> dict[str, list[dict[str, Any]]]: + """Basic entities filter info we care about. - def launch(self, session, event): - filtered_entities_info = self.filter_entities_info(event) - if not filtered_entities_info: - return + This filtering is first of many filters. This does not query anything + from ftrack nor use settings. - for project_id, entities_info in filtered_entities_info.items(): - self.process_by_project(session, event, project_id, entities_info) + Args: + event (ftrack_api.event.base.Event): Ftrack event with update + information. + + Returns: + dict[str, list[dict[str, Any]]]: Filtered entity changes by + project id. + """ - def filter_entities_info(self, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") if not entities_info: return - entities_info_by_project_id = {} + entities_info_by_project_id = collections.defaultdict(list) for entity_info in entities_info: - # Care only about tasks - if entity_info.get("entityType") != "task": + # Ignore removed entities + if entity_info.get("action") == "remove": continue - # Care only about changes of status + # Care only about information with changes of entities changes = entity_info.get("changes") if not changes: continue @@ -69,367 +86,287 @@ class PushFrameValuesToTaskEvent(BaseEvent): if project_id is None: continue - # Skip `Task` entity type if parent didn't change - if entity_info["entity_type"].lower() == "task": - if ( - "parent_id" not in changes - or changes["parent_id"]["new"] is None - ): - continue - - if project_id not in entities_info_by_project_id: - entities_info_by_project_id[project_id] = [] entities_info_by_project_id[project_id].append(entity_info) return entities_info_by_project_id - def process_by_project(self, session, event, project_id, entities_info): - project_name = self.get_project_name_from_event( + def _get_attrs_configurations(self, session, interest_attributes): + """Get custom attribute configurations by name. + + Args: + session (ftrack_api.Session): Ftrack sesson. + interest_attributes (list[str]): Names of custom attributes + that should be synchronized. + + Returns: + tuple[dict[str, list], list]: Attributes by object id and + hierarchical attributes. + """ + + attrs = session.query(self.cust_attrs_query.format( + self.join_query_keys(interest_attributes) + )).all() + + attrs_by_obj_id = collections.defaultdict(list) + hier_attrs = [] + for attr in attrs: + if attr["is_hierarchical"]: + hier_attrs.append(attr) + continue + obj_id = attr["object_type_id"] + attrs_by_obj_id[obj_id].append(attr) + return attrs_by_obj_id, hier_attrs + + def _get_handler_project_settings( + self, + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str + ) -> tuple[set[str], set[str]]: + """Get handler settings based on the project. + + Args: + session (ftrack_api.Session): Ftrack session. + event (ftrack_api.event.base.Event): Ftrack event which triggered + the changes. + project_id (str): Project id where the current changes are handled. + + Returns: + tuple[set[str], set[str]]: Attribute names we care about and + entity types we care about. + """ + + project_name: str = self.get_project_name_from_event( session, event, project_id ) # Load settings - project_settings = self.get_project_settings_from_event( - event, project_name + project_settings: dict[str, Any] = ( + self.get_project_settings_from_event(event, project_name) ) # Load status mapping from presets - event_settings = ( + event_settings: dict[str, Any] = ( project_settings ["ftrack"] ["events"] - ["sync_hier_entity_attributes"] + [self.settings_key] ) # Skip if event is not enabled if not event_settings["enabled"]: self.log.debug("Project \"{}\" has disabled {}".format( project_name, self.__class__.__name__ )) - return + return set(), set() - interest_attributes = event_settings["interest_attributes"] + interest_attributes: list[str] = event_settings["interest_attributes"] if not interest_attributes: self.log.info(( "Project \"{}\" does not have filled 'interest_attributes'," " skipping." )) - return - interest_entity_types = event_settings["interest_entity_types"] + + interest_entity_types: list[str] = ( + event_settings["interest_entity_types"]) if not interest_entity_types: self.log.info(( "Project \"{}\" does not have filled 'interest_entity_types'," " skipping." )) - return - interest_attributes = set(interest_attributes) - interest_entity_types = set(interest_entity_types) + # Unify possible issues from settings ('Asset Build' -> 'assetbuild') + interest_entity_types: set[str] = { + entity_type.replace(" ", "").lower() + for entity_type in interest_entity_types + } + return set(interest_attributes), interest_entity_types - # Separate value changes and task parent changes - _entities_info = [] - added_entities = [] - added_entity_ids = set() - task_parent_changes = [] + def _entities_filter_by_settings( + self, + entities_info: list[dict[str, Any]], + interest_attributes: set[str], + interest_entity_types: set[str] + ): + new_entities_info = [] for entity_info in entities_info: - if entity_info["entity_type"].lower() == "task": - task_parent_changes.append(entity_info) - elif entity_info.get("action") == "add": - added_entities.append(entity_info) - added_entity_ids.add(entity_info["entityId"]) - else: - _entities_info.append(entity_info) - entities_info = _entities_info + entity_type_low = entity_info["entity_type"].lower() - # Filter entities info with changes - interesting_data, changed_keys_by_object_id = self.filter_changes( - session, event, entities_info, interest_attributes - ) - self.interesting_data_for_added( - session, - added_entities, - interest_attributes, - interesting_data, - changed_keys_by_object_id - ) - if not interesting_data and not task_parent_changes: - return + changes = entity_info["changes"] + # SPECIAL CASE: Capture changes of task created/moved under + # interested entity type + if ( + entity_type_low == "task" + and "parent_id" in changes + ): + # Direct parent is always second item in 'parents' and 'Task' + # must have at least one parent + parent_info = entity_info["parents"][1] + parent_entity_type = ( + parent_info["entity_type"] + .replace(" ", "") + .lower() + ) + if parent_entity_type in interest_entity_types: + new_entities_info.append(entity_info) + continue - # Prepare object types - object_types = session.query("select id, name from ObjectType").all() - object_types_by_name = {} - for object_type in object_types: - name_low = object_type["name"].lower() - object_types_by_name[name_low] = object_type + # Skip if entity type is not enabled for attr value sync + if entity_type_low not in interest_entity_types: + continue - # NOTE it would be nice to check if `interesting_data` do not contain - # value changs of tasks that were created or moved - # - it is a complex way how to find out - if interesting_data: - self.process_attribute_changes( - session, - object_types_by_name, - interesting_data, - changed_keys_by_object_id, - interest_entity_types, - interest_attributes, - added_entity_ids - ) + valid_attr_change = entity_info.get("action") == "add" + for attr_key in interest_attributes: + if valid_attr_change: + break - if task_parent_changes: - self.process_task_parent_change( - session, object_types_by_name, task_parent_changes, - interest_entity_types, interest_attributes - ) + if attr_key not in changes: + continue - def process_task_parent_change( + if changes[attr_key]["new"] is not None: + valid_attr_change = True + + if not valid_attr_change: + continue + + new_entities_info.append(entity_info) + + return new_entities_info + + def propagate_attribute_changes( self, session, - object_types_by_name, - task_parent_changes, - interest_entity_types, - interest_attributes + interest_attributes, + entities_info, + attrs_by_obj_id, + hier_attrs, + real_values_by_entity_id, + hier_values_by_entity_id, ): - """Push custom attribute values if task parent has changed. + hier_attr_ids_by_key = { + attr["key"]: attr["id"] + for attr in hier_attrs + } + filtered_interest_attributes = { + attr_name + for attr_name in interest_attributes + if attr_name in hier_attr_ids_by_key + } + attrs_keys_by_obj_id = {} + for obj_id, attrs in attrs_by_obj_id.items(): + attrs_keys_by_obj_id[obj_id] = { + attr["key"]: attr["id"] + for attr in attrs + } - Parent is changed if task is created or if is moved under different - entity. We don't care about all task changes only about those that - have it's parent in interest types (from settings). + op_changes = [] + for entity_info in entities_info: + entity_id = entity_info["entityId"] + obj_id = entity_info["objectTypeId"] + # Skip attributes sync if does not have object specific custom + # attribute + if obj_id not in attrs_keys_by_obj_id: + continue + attr_keys = attrs_keys_by_obj_id[obj_id] + real_values = real_values_by_entity_id[entity_id] + hier_values = hier_values_by_entity_id[entity_id] - Tasks hierarchical value should be unset or set based on parents - real hierarchical value and non hierarchical custom attribute value - should be set to hierarchical value. - """ - - # Store task ids which were created or moved under parent with entity - # type defined in settings (interest_entity_types). - task_ids = set() - # Store parent ids of matching task ids - matching_parent_ids = set() - # Store all entity ids of all entities to be able query hierarchical - # values. - whole_hierarchy_ids = set() - # Store parent id of each entity id - parent_id_by_entity_id = {} - for entity_info in task_parent_changes: - # Ignore entities with less parents than 2 - # NOTE entity itself is also part of "parents" value - parents = entity_info.get("parents") or [] - if len(parents) < 2: + changes = copy.deepcopy(entity_info["changes"]) + obj_id_attr_keys = { + attr_key + for attr_key in filtered_interest_attributes + if attr_key in attr_keys + } + if not obj_id_attr_keys: continue - parent_info = parents[1] - # Check if parent has entity type we care about. - if parent_info["entity_type"] not in interest_entity_types: - continue + value_by_key = {} + is_new_entity = entity_info.get("action") == "add" + for attr_key in obj_id_attr_keys: + if ( + attr_key in changes + and changes[attr_key]["new"] is not None + ): + value_by_key[attr_key] = changes[attr_key]["new"] - task_ids.add(entity_info["entityId"]) - matching_parent_ids.add(parent_info["entityId"]) - - # Store whole hierarchi of task entity - prev_id = None - for item in parents: - item_id = item["entityId"] - whole_hierarchy_ids.add(item_id) - - if prev_id is None: - prev_id = item_id + if not is_new_entity: continue - parent_id_by_entity_id[prev_id] = item_id - if item["entityType"] == "show": - break - prev_id = item_id + hier_attr_id = hier_attr_ids_by_key[attr_key] + attr_id = attr_keys[attr_key] + if hier_attr_id in real_values or attr_id in real_values: + continue - # Just skip if nothing is interesting for our settings - if not matching_parent_ids: - return + value_by_key[attr_key] = hier_values[hier_attr_id] - # Query object type ids of parent ids for custom attribute - # definitions query - entities = session.query( - "select object_type_id from TypedContext where id in ({})".format( - self.join_query_keys(matching_parent_ids) - ) - ) + for key, new_value in value_by_key.items(): + if new_value is None: + continue - # Prepare task object id - task_object_id = object_types_by_name["task"]["id"] + hier_id = hier_attr_ids_by_key[key] + std_id = attr_keys[key] + real_hier_value = real_values.get(hier_id) + real_std_value = real_values.get(std_id) + hier_value = hier_values[hier_id] + # Get right type of value for conversion + # - values in event are strings + type_value = real_hier_value + if type_value is None: + type_value = real_std_value + if type_value is None: + type_value = hier_value + # Skip if current values are not set + if type_value is None: + continue - # All object ids for which we're querying custom attribute definitions - object_type_ids = set() - object_type_ids.add(task_object_id) - for entity in entities: - object_type_ids.add(entity["object_type_id"]) + try: + new_value = type(type_value)(new_value) + except Exception: + self.log.warning(( + "Couldn't convert from {} to {}." + " Skipping update values." + ).format(type(new_value), type(type_value))) + continue - attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, object_type_ids, interest_attributes - ) + real_std_value_is_same = new_value == real_std_value + real_hier_value_is_same = new_value == real_hier_value + # New value does not match anything in current entity values + if ( + not is_new_entity + and not real_std_value_is_same + and not real_hier_value_is_same + ): + continue - # Skip if all task attributes are not available - task_attrs = attrs_by_obj_id.get(task_object_id) - if not task_attrs: - return + if not real_std_value_is_same: + op_changes.append(( + std_id, + entity_id, + new_value, + real_values.get(std_id), + std_id in real_values + )) - # Skip attributes that is not in both hierarchical and nonhierarchical - # TODO be able to push values if hierarchical is available - for key in interest_attributes: - if key not in hier_attrs: - task_attrs.pop(key, None) + if not real_hier_value_is_same: + op_changes.append(( + hier_id, + entity_id, + new_value, + real_values.get(hier_id), + hier_id in real_values + )) - elif key not in task_attrs: - hier_attrs.pop(key) + for change in op_changes: + ( + attr_id, + entity_id, + new_value, + old_value, + do_update + ) = change - # Skip if nothing remained - if not task_attrs: - return - - # Do some preparations for custom attribute values query - attr_key_by_id = {} - nonhier_id_by_key = {} - hier_attr_ids = [] - for key, attr_id in hier_attrs.items(): - attr_key_by_id[attr_id] = key - hier_attr_ids.append(attr_id) - - conf_ids = list(hier_attr_ids) - task_conf_ids = [] - for key, attr_id in task_attrs.items(): - attr_key_by_id[attr_id] = key - nonhier_id_by_key[key] = attr_id - conf_ids.append(attr_id) - task_conf_ids.append(attr_id) - - # Query custom attribute values - # - result does not contain values for all entities only result of - # query callback to ftrack server - result = query_custom_attributes( - session, list(hier_attr_ids), whole_hierarchy_ids, True - ) - result.extend( - query_custom_attributes( - session, task_conf_ids, whole_hierarchy_ids, False - ) - ) - - # Prepare variables where result will be stored - # - hierachical values should not contain attribute with value by - # default - hier_values_by_entity_id = { - entity_id: {} - for entity_id in whole_hierarchy_ids - } - # - real values of custom attributes - values_by_entity_id = { - entity_id: { - attr_id: None - for attr_id in conf_ids - } - for entity_id in whole_hierarchy_ids - } - for item in result: - attr_id = item["configuration_id"] - entity_id = item["entity_id"] - value = item["value"] - - values_by_entity_id[entity_id][attr_id] = value - - if attr_id in hier_attr_ids and value is not None: - hier_values_by_entity_id[entity_id][attr_id] = value - - # Prepare values for all task entities - # - going through all parents and storing first value value - # - store None to those that are already known that do not have set - # value at all - for task_id in tuple(task_ids): - for attr_id in hier_attr_ids: - entity_ids = [] - value = None - entity_id = task_id - while value is None: - entity_value = hier_values_by_entity_id[entity_id] - if attr_id in entity_value: - value = entity_value[attr_id] - if value is None: - break - - if value is None: - entity_ids.append(entity_id) - - entity_id = parent_id_by_entity_id.get(entity_id) - if entity_id is None: - break - - for entity_id in entity_ids: - hier_values_by_entity_id[entity_id][attr_id] = value - - # Prepare changes to commit - changes = [] - for task_id in tuple(task_ids): - parent_id = parent_id_by_entity_id[task_id] - for attr_id in hier_attr_ids: - attr_key = attr_key_by_id[attr_id] - nonhier_id = nonhier_id_by_key[attr_key] - - # Real value of hierarchical attribute on parent - # - If is none then should be unset - real_parent_value = values_by_entity_id[parent_id][attr_id] - # Current hierarchical value of a task - # - Will be compared to real parent value - hier_value = hier_values_by_entity_id[task_id][attr_id] - - # Parent value that can be inherited from it's parent entity - parent_value = hier_values_by_entity_id[parent_id][attr_id] - # Task value of nonhierarchical custom attribute - nonhier_value = values_by_entity_id[task_id][nonhier_id] - - if real_parent_value != hier_value: - changes.append({ - "new_value": real_parent_value, - "attr_id": attr_id, - "entity_id": task_id, - "attr_key": attr_key - }) - - if parent_value != nonhier_value: - changes.append({ - "new_value": parent_value, - "attr_id": nonhier_id, - "entity_id": task_id, - "attr_key": attr_key - }) - - self._commit_changes(session, changes) - - def _commit_changes(self, session, changes): - uncommited_changes = False - for idx, item in enumerate(changes): - new_value = item["new_value"] - old_value = item["old_value"] - attr_id = item["attr_id"] - entity_id = item["entity_id"] - attr_key = item["attr_key"] - - entity_key = collections.OrderedDict(( + entity_key = collections.OrderedDict([ ("configuration_id", attr_id), ("entity_id", entity_id) - )) - self._cached_changes.append({ - "attr_key": attr_key, - "entity_id": entity_id, - "value": new_value, - "time": datetime.datetime.now() - }) - old_value_is_set = ( - old_value is not ftrack_api.symbol.NOT_SET - and old_value is not None - ) - if new_value is None: - if not old_value_is_set: - continue - op = ftrack_api.operation.DeleteEntityOperation( - "CustomAttributeValue", - entity_key - ) - - elif old_value_is_set: + ]) + if do_update: op = ftrack_api.operation.UpdateEntityOperation( "CustomAttributeValue", entity_key, @@ -446,449 +383,116 @@ class PushFrameValuesToTaskEvent(BaseEvent): ) session.recorded_operations.push(op) - self.log.info(( - "Changing Custom Attribute \"{}\" to value" - " \"{}\" on entity: {}" - ).format(attr_key, new_value, entity_id)) - - if (idx + 1) % 20 == 0: - uncommited_changes = False - try: - session.commit() - except Exception: - session.rollback() - self.log.warning( - "Changing of values failed.", exc_info=True - ) - else: - uncommited_changes = True - if uncommited_changes: - try: + if len(session.recorded_operations) > 100: session.commit() - except Exception: - session.rollback() - self.log.warning("Changing of values failed.", exc_info=True) + session.commit() - def process_attribute_changes( + def process_by_project( self, - session, - object_types_by_name, - interesting_data, - changed_keys_by_object_id, - interest_entity_types, - interest_attributes, - added_entity_ids + session: ftrack_api.Session, + event: ftrack_api.event.base.Event, + project_id: str, + entities_info: list[dict[str, Any]] ): - # Prepare task object id - task_object_id = object_types_by_name["task"]["id"] + """Process changes in single project. - # Collect object type ids based on settings - interest_object_ids = [] - for entity_type in interest_entity_types: - _entity_type = entity_type.lower() - object_type = object_types_by_name.get(_entity_type) - if not object_type: - self.log.warning("Couldn't find object type \"{}\"".format( - entity_type - )) + Args: + session (ftrack_api.Session): Ftrack session. + event (ftrack_api.event.base.Event): Event which has all changes + information. + project_id (str): Project id related to changes. + entities_info (list[dict[str, Any]]): Changes of entities. + """ - interest_object_ids.append(object_type["id"]) - - # Query entities by filtered data and object ids - entities = self.get_entities( - session, interesting_data, interest_object_ids - ) - if not entities: + ( + interest_attributes, + interest_entity_types + ) = self._get_handler_project_settings(session, event, project_id) + if not interest_attributes or not interest_entity_types: return - # Pop not found entities from interesting data - entity_ids = set( - entity["id"] - for entity in entities + entities_info: list[dict[str, Any]] = ( + self._entities_filter_by_settings( + entities_info, + interest_attributes, + interest_entity_types + ) ) - for entity_id in tuple(interesting_data.keys()): - if entity_id not in entity_ids: - interesting_data.pop(entity_id) - - # Add task object type to list - attr_obj_ids = list(interest_object_ids) - attr_obj_ids.append(task_object_id) - - attrs_by_obj_id, hier_attrs = self.attrs_configurations( - session, attr_obj_ids, interest_attributes - ) - - task_attrs = attrs_by_obj_id.get(task_object_id) - - changed_keys = set() - # Skip keys that are not both in hierachical and type specific - for object_id, keys in changed_keys_by_object_id.items(): - changed_keys |= set(keys) - object_id_attrs = attrs_by_obj_id.get(object_id) - for key in keys: - if key not in hier_attrs: - attrs_by_obj_id[object_id].pop(key) - continue - - if ( - (not object_id_attrs or key not in object_id_attrs) - and (not task_attrs or key not in task_attrs) - ): - hier_attrs.pop(key) - - # Clean up empty values - for key, value in tuple(attrs_by_obj_id.items()): - if not value: - attrs_by_obj_id.pop(key) - - if not attrs_by_obj_id: - self.log.warning(( - "There is not created Custom Attributes {} " - " for entity types: {}" - ).format( - self.join_query_keys(interest_attributes), - self.join_query_keys(interest_entity_types) - )) + if not entities_info: return - # Prepare task entities - task_entities = [] - # If task entity does not contain changed attribute then skip - if task_attrs: - task_entities = self.get_task_entities(session, interesting_data) - - task_entity_ids = set() - parent_id_by_task_id = {} - for task_entity in task_entities: - task_id = task_entity["id"] - task_entity_ids.add(task_id) - parent_id_by_task_id[task_id] = task_entity["parent_id"] - - self.finalize_attribute_changes( - session, - interesting_data, - changed_keys, - attrs_by_obj_id, - hier_attrs, - task_entity_ids, - parent_id_by_task_id, - added_entity_ids - ) - - def finalize_attribute_changes( - self, - session, - interesting_data, - changed_keys, - attrs_by_obj_id, - hier_attrs, - task_entity_ids, - parent_id_by_task_id, - added_entity_ids - ): - attr_id_to_key = {} - for attr_confs in attrs_by_obj_id.values(): - for key in changed_keys: - custom_attr_id = attr_confs.get(key) - if custom_attr_id: - attr_id_to_key[custom_attr_id] = key - - for key in changed_keys: - custom_attr_id = hier_attrs.get(key) - if custom_attr_id: - attr_id_to_key[custom_attr_id] = key - - entity_ids = ( - set(interesting_data.keys()) | task_entity_ids - ) - attr_ids = set(attr_id_to_key.keys()) - - current_values_by_id = self.get_current_values( - session, - attr_ids, - entity_ids, - task_entity_ids, - hier_attrs - ) - - changes = [] - for entity_id, current_values in current_values_by_id.items(): - parent_id = parent_id_by_task_id.get(entity_id) - if not parent_id: - parent_id = entity_id - values = interesting_data[parent_id] - - added_entity = entity_id in added_entity_ids - for attr_id, old_value in current_values.items(): - if added_entity and attr_id in hier_attrs: - continue - - attr_key = attr_id_to_key.get(attr_id) - if not attr_key: - continue - - # Convert new value from string - new_value = values.get(attr_key) - new_value_is_valid = ( - old_value is not ftrack_api.symbol.NOT_SET - and new_value is not None - ) - if added_entity and not new_value_is_valid: - continue - - if new_value is not None and new_value_is_valid: - try: - new_value = type(old_value)(new_value) - except Exception: - self.log.warning(( - "Couldn't convert from {} to {}." - " Skipping update values." - ).format(type(new_value), type(old_value))) - if new_value == old_value: - continue - - changes.append({ - "new_value": new_value, - "attr_id": attr_id, - "old_value": old_value, - "entity_id": entity_id, - "attr_key": attr_key - }) - self._commit_changes(session, changes) - - def filter_changes( - self, session, event, entities_info, interest_attributes - ): - session_user_id = self.session_user_id(session) - user_data = event["data"].get("user") - changed_by_session = False - if user_data and user_data.get("userid") == session_user_id: - changed_by_session = True - - current_time = datetime.datetime.now() - - interesting_data = {} - changed_keys_by_object_id = {} - - for entity_info in entities_info: - # Care only about changes if specific keys - entity_changes = {} - changes = entity_info["changes"] - for key in interest_attributes: - if key in changes: - entity_changes[key] = changes[key]["new"] - - entity_id = entity_info["entityId"] - if changed_by_session: - for key, new_value in tuple(entity_changes.items()): - for cached in tuple(self._cached_changes): - if ( - cached["entity_id"] != entity_id - or cached["attr_key"] != key - ): - continue - - cached_value = cached["value"] - try: - new_value = type(cached_value)(new_value) - except Exception: - pass - - if cached_value == new_value: - self._cached_changes.remove(cached) - entity_changes.pop(key) - break - - delta = (current_time - cached["time"]).seconds - if delta > self._max_delta: - self._cached_changes.remove(cached) - - if not entity_changes: - continue - - entity_id = entity_info["entityId"] - object_id = entity_info["objectTypeId"] - interesting_data[entity_id] = entity_changes - if object_id not in changed_keys_by_object_id: - changed_keys_by_object_id[object_id] = set() - changed_keys_by_object_id[object_id] |= set(entity_changes.keys()) - - return interesting_data, changed_keys_by_object_id - - def interesting_data_for_added( - self, - session, - added_entities, - interest_attributes, - interesting_data, - changed_keys_by_object_id - ): - if not added_entities or not interest_attributes: - return - - object_type_ids = set() - entity_ids = set() - all_entity_ids = set() - object_id_by_entity_id = {} - project_id = None - entity_ids_by_parent_id = collections.defaultdict(set) - for entity_info in added_entities: - object_id = entity_info["objectTypeId"] - entity_id = entity_info["entityId"] - object_type_ids.add(object_id) - entity_ids.add(entity_id) - object_id_by_entity_id[entity_id] = object_id - - for item in entity_info["parents"]: - entity_id = item["entityId"] - all_entity_ids.add(entity_id) - parent_id = item["parentId"] - if not parent_id: - project_id = entity_id - else: - entity_ids_by_parent_id[parent_id].add(entity_id) - - hier_attrs = self.get_hierarchical_configurations( + attrs_by_obj_id, hier_attrs = self._get_attrs_configurations( session, interest_attributes ) - if not hier_attrs: + # Skip if attributes are not available + # - there is nothing to sync + if not attrs_by_obj_id or not hier_attrs: return - hier_attrs_key_by_id = { - attr_conf["id"]: attr_conf["key"] - for attr_conf in hier_attrs - } - default_values_by_key = { - attr_conf["key"]: attr_conf["default"] - for attr_conf in hier_attrs - } + entity_ids_by_parent_id = collections.defaultdict(set) + all_entity_ids = set() + for entity_info in entities_info: + entity_id = None + for item in entity_info["parents"]: + item_id = item["entityId"] + all_entity_ids.add(item_id) + if entity_id is not None: + entity_ids_by_parent_id[item_id].add(entity_id) + entity_id = item_id - values = query_custom_attributes( - session, list(hier_attrs_key_by_id.keys()), all_entity_ids, True + attr_ids = {attr["id"] for attr in hier_attrs} + for attrs in attrs_by_obj_id.values(): + attr_ids |= {attr["id"] for attr in attrs} + + # Query real custom attribute values + # - we have to know what are the real values, if are set and to what + # value + value_items = query_custom_attributes( + session, attr_ids, all_entity_ids, True ) - values_per_entity_id = {} - for entity_id in all_entity_ids: - values_per_entity_id[entity_id] = {} - for attr_name in interest_attributes: - values_per_entity_id[entity_id][attr_name] = None - - for item in values: - entity_id = item["entity_id"] - key = hier_attrs_key_by_id[item["configuration_id"]] - values_per_entity_id[entity_id][key] = item["value"] - - fill_queue = collections.deque() - fill_queue.append((project_id, default_values_by_key)) - while fill_queue: - item = fill_queue.popleft() - entity_id, values_by_key = item - entity_values = values_per_entity_id[entity_id] - new_values_by_key = copy.deepcopy(values_by_key) - for key, value in values_by_key.items(): - current_value = entity_values[key] - if current_value is None: - entity_values[key] = value - else: - new_values_by_key[key] = current_value - - for child_id in entity_ids_by_parent_id[entity_id]: - fill_queue.append((child_id, new_values_by_key)) - - for entity_id in entity_ids: - entity_changes = {} - for key, value in values_per_entity_id[entity_id].items(): - if value is not None: - entity_changes[key] = value - - if not entity_changes: - continue - - interesting_data[entity_id] = entity_changes - object_id = object_id_by_entity_id[entity_id] - if object_id not in changed_keys_by_object_id: - changed_keys_by_object_id[object_id] = set() - changed_keys_by_object_id[object_id] |= set(entity_changes.keys()) - - def get_current_values( - self, - session, - attr_ids, - entity_ids, - task_entity_ids, - hier_attrs - ): - current_values_by_id = {} - if not attr_ids or not entity_ids: - return current_values_by_id - - for entity_id in entity_ids: - current_values_by_id[entity_id] = {} - for attr_id in attr_ids: - current_values_by_id[entity_id][attr_id] = ( - ftrack_api.symbol.NOT_SET - ) - - values = query_custom_attributes( - session, attr_ids, entity_ids, True - ) - - for item in values: + real_values_by_entity_id = collections.defaultdict(dict) + for item in value_items: entity_id = item["entity_id"] attr_id = item["configuration_id"] - if entity_id in task_entity_ids and attr_id in hier_attrs: - continue + real_values_by_entity_id[entity_id][attr_id] = item["value"] - if entity_id not in current_values_by_id: - current_values_by_id[entity_id] = {} - current_values_by_id[entity_id][attr_id] = item["value"] - return current_values_by_id + hier_values_by_entity_id = {} + default_values = { + attr["id"]: attr["default"] + for attr in hier_attrs + } + hier_queue = collections.deque() + hier_queue.append((default_values, [project_id])) + while hier_queue: + parent_values, entity_ids = hier_queue.popleft() + for entity_id in entity_ids: + entity_values = copy.deepcopy(parent_values) + real_values = real_values_by_entity_id[entity_id] + for attr_id, value in real_values.items(): + entity_values[attr_id] = value + hier_values_by_entity_id[entity_id] = entity_values + hier_queue.append( + (entity_values, entity_ids_by_parent_id[entity_id]) + ) - def get_entities(self, session, interesting_data, interest_object_ids): - return session.query(( - "select id from TypedContext" - " where id in ({}) and object_type_id in ({})" - ).format( - self.join_query_keys(interesting_data.keys()), - self.join_query_keys(interest_object_ids) - )).all() - - def get_task_entities(self, session, interesting_data): - return session.query( - "select id, parent_id from Task where parent_id in ({})".format( - self.join_query_keys(interesting_data.keys()) - ) - ).all() - - def attrs_configurations(self, session, object_ids, interest_attributes): - attrs = session.query(self.cust_attrs_query.format( - self.join_query_keys(interest_attributes), - self.join_query_keys(object_ids) - )).all() - - output = {} - hiearchical = {} - for attr in attrs: - if attr["is_hierarchical"]: - hiearchical[attr["key"]] = attr["id"] - continue - obj_id = attr["object_type_id"] - if obj_id not in output: - output[obj_id] = {} - output[obj_id][attr["key"]] = attr["id"] - return output, hiearchical - - def get_hierarchical_configurations(self, session, interest_attributes): - hier_attr_query = ( - "select id, key, object_type_id, is_hierarchical, default" - " from CustomAttributeConfiguration" - " where key in ({}) and is_hierarchical is true" + self.propagate_attribute_changes( + session, + interest_attributes, + entities_info, + attrs_by_obj_id, + hier_attrs, + real_values_by_entity_id, + hier_values_by_entity_id, ) - if not interest_attributes: - return [] - return list(session.query(hier_attr_query.format( - self.join_query_keys(interest_attributes), - )).all()) + + def launch(self, session, event): + filtered_entities_info = self.filter_entities_info(event) + if not filtered_entities_info: + return + + for project_id, entities_info in filtered_entities_info.items(): + self.process_by_project(session, event, project_id, entities_info) def register(session): - PushFrameValuesToTaskEvent(session).register() + PushHierValuesToNonHierEvent(session).register() diff --git a/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py b/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py index 99ad3aec37..358a8d2310 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py +++ b/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py @@ -7,7 +7,7 @@ class RadioButtons(BaseEvent): ignore_me = True def launch(self, session, event): - '''Provides a readio button behaviour to any bolean attribute in + '''Provides a radio button behaviour to any boolean attribute in radio_button group.''' # start of event procedure ---------------------------------- diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 0058a428e3..0aa0b9f9f5 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -787,7 +787,7 @@ class SyncToAvalonEvent(BaseEvent): # Filter updates where name is changing for ftrack_id, ent_info in updated.items(): ent_keys = ent_info["keys"] - # Seprate update info from rename + # Separate update info from rename if "name" not in ent_keys: continue @@ -827,7 +827,7 @@ class SyncToAvalonEvent(BaseEvent): # 5.) Process updated self.process_updated() time_6 = time.time() - # 6.) Process changes in hierarchy or hier custom attribues + # 6.) Process changes in hierarchy or hier custom attributes self.process_hier_cleanup() time_7 = time.time() self.process_task_updates() @@ -1094,7 +1094,7 @@ class SyncToAvalonEvent(BaseEvent): def check_names_synchronizable(self, names): """Check if entities with specific names are importable. - This check should happend after removing entity or renaming entity. + This check should happen after removing entity or renaming entity. When entity was removed or renamed then it's name is possible to sync. """ joined_passed_names = ", ".join( @@ -1743,7 +1743,7 @@ class SyncToAvalonEvent(BaseEvent): def process_moved(self): """ - Handles moved entities to different place in hiearchy. + Handles moved entities to different place in hierarchy. (Not tasks - handled separately.) """ if not self.ftrack_moved: @@ -1792,7 +1792,7 @@ class SyncToAvalonEvent(BaseEvent): self.log.warning("{} <{}>".format(error_msg, ent_path)) continue - # THIS MUST HAPPEND AFTER CREATING NEW ENTITIES !!!! + # THIS MUST HAPPEN AFTER CREATING NEW ENTITIES !!!! # - because may be moved to new created entity if "data" not in self.updates[mongo_id]: self.updates[mongo_id]["data"] = {} @@ -2323,7 +2323,7 @@ class SyncToAvalonEvent(BaseEvent): items.append("{} - \"{}\"".format(ent_path, value)) self.report_items["error"][fps_msg] = items - # Get dictionary with not None hierarchical values to pull to childs + # Get dictionary with not None hierarchical values to pull to children project_values = {} for key, value in ( entities_dict[ftrack_project_id]["hier_attrs"].items() @@ -2460,7 +2460,7 @@ class SyncToAvalonEvent(BaseEvent): def update_entities(self): """ Update Avalon entities by mongo bulk changes. - Expects self.updates which are transfered to $set part of update + Expects self.updates which are transferred to $set part of update command. Resets self.updates afterwards. """ diff --git a/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py b/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py index a0e039926e..25fa3b0535 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py +++ b/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py @@ -291,7 +291,7 @@ class TaskStatusToParent(BaseEvent): except Exception: session.rollback() self.log.warning( - "\"{}\" status couldnt be set to \"{}\"".format( + "\"{}\" status couldn't be set to \"{}\"".format( ent_path, new_status["name"] ), exc_info=True @@ -399,7 +399,7 @@ class TaskStatusToParent(BaseEvent): # For cases there are multiple tasks in changes # - task status which match any new status item by order in the - # list `single_match` is preffered + # list `single_match` is preferred best_order = len(single_match) best_order_status = None for task_entity in task_entities: diff --git a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py index c4e48b92f0..9539a34f5e 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py +++ b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py @@ -10,11 +10,11 @@ from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY class UserAssigmentEvent(BaseEvent): """ - This script will intercept user assigment / de-assigment event and + This script will intercept user assignment / de-assignment event and run shell script, providing as much context as possible. It expects configuration file ``presets/ftrack/user_assigment_event.json``. - In it, you define paths to scripts to be run for user assigment event and + In it, you define paths to scripts to be run for user assignment event and for user-deassigment:: { "add": [ diff --git a/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py index e36c3eecd9..fb40fd6417 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py +++ b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py @@ -102,7 +102,7 @@ class VersionToTaskStatus(BaseEvent): asset_version_entities.append(asset_version) task_ids.add(asset_version["task_id"]) - # Skipt if `task_ids` are empty + # Skip if `task_ids` are empty if not task_ids: return diff --git a/openpype/modules/ftrack/event_handlers_user/action_applications.py b/openpype/modules/ftrack/event_handlers_user/action_applications.py index 102f04c956..30399b463d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_applications.py +++ b/openpype/modules/ftrack/event_handlers_user/action_applications.py @@ -124,6 +124,11 @@ class AppplicationsAction(BaseAction): if not avalon_project_apps: return False + settings = self.get_project_settings_from_event( + event, avalon_project_doc["name"]) + + only_available = settings["applications"]["only_available"] + items = [] for app_name in avalon_project_apps: app = self.application_manager.applications.get(app_name) @@ -133,6 +138,10 @@ class AppplicationsAction(BaseAction): if app.group.name in CUSTOM_LAUNCH_APP_GROUPS: continue + # Skip applications without valid executables + if only_available and not app.find_executable(): + continue + app_icon = app.icon if app_icon and self.icon_url: try: diff --git a/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py b/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py index c7fb1af98b..06d572601d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py +++ b/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py @@ -7,7 +7,7 @@ from openpype_modules.ftrack.lib import BaseAction, statics_icon class BatchTasksAction(BaseAction): '''Batch Tasks action - `label` a descriptive string identifing your action. + `label` a descriptive string identifying your action. `varaint` To group actions together, give them the same label and specify a unique variant per action. `identifier` a unique identifier for your action. diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index c19cfd1502..471a8c4182 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -82,9 +82,9 @@ config (dictionary) write_security_roles/read_security_roles (array of strings) - default: ["ALL"] - strings should be role names (e.g.: ["API", "Administrator"]) - - if set to ["ALL"] - all roles will be availabled + - if set to ["ALL"] - all roles will be available - if first is 'except' - roles will be set to all except roles in array - - Warning: Be carefull with except - roles can be different by company + - Warning: Be careful with except - roles can be different by company - example: write_security_roles = ["except", "User"] read_security_roles = ["ALL"] # (User is can only read) @@ -500,7 +500,7 @@ class CustomAttributes(BaseAction): data = {} # Get key, label, type data.update(self.get_required(cust_attr_data)) - # Get hierachical/ entity_type/ object_id + # Get hierarchical/ entity_type/ object_id data.update(self.get_entity_type(cust_attr_data)) # Get group, default, security roles data.update(self.get_optional(cust_attr_data)) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_folders.py b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py index 9806f83773..cbeff5343f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_folders.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py @@ -51,7 +51,7 @@ class CreateFolders(BaseAction): }, { "type": "label", - "value": "With all chilren entities" + "value": "With all children entities" }, { "name": "children_included", diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 03d029b0c1..72a5efbcfe 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -18,7 +18,7 @@ class DeleteAssetSubset(BaseAction): # Action label. label = "Delete Asset/Subsets" # Action description. - description = "Removes from Avalon with all childs and asset from Ftrack" + description = "Removes from Avalon with all children and asset from Ftrack" icon = statics_icon("ftrack", "action_icons", "DeleteAsset.svg") settings_key = "delete_asset_subset" diff --git a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index c543dc8834..ec14c6918b 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -27,7 +27,7 @@ class DeleteOldVersions(BaseAction): variant = "- Delete old versions" description = ( "Delete files from older publishes so project can be" - " archived with only lates versions." + " archived with only latest versions." ) icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") @@ -307,7 +307,7 @@ class DeleteOldVersions(BaseAction): file_path, seq_path = self.path_from_represenation(repre, anatomy) if file_path is None: self.log.warning(( - "Could not format path for represenation \"{}\"" + "Could not format path for representation \"{}\"" ).format(str(repre))) continue diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index a400c8f5f0..559de3a24d 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -601,7 +601,7 @@ class Delivery(BaseAction): return self.report(report_items) def report(self, report_items): - """Returns dict with final status of delivery (succes, fail etc.).""" + """Returns dict with final status of delivery (success, fail etc.).""" items = [] for msg, _items in report_items.items(): diff --git a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py index fb1cdf340e..36d29db96b 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py +++ b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py @@ -246,7 +246,7 @@ class FillWorkfileAttributeAction(BaseAction): project_name = project_entity["full_name"] - # Find matchin asset documents and map them by ftrack task entities + # Find matching asset documents and map them by ftrack task entities # - result stored to 'asset_docs_with_task_entities' is list with # tuple `(asset document, [task entitis, ...])` # Quety all asset documents diff --git a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py index f489c0c54c..dd68c75f84 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_job_killer.py +++ b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py @@ -54,14 +54,14 @@ class JobKiller(BaseAction): for job in jobs: try: data = json.loads(job["data"]) - desctiption = data["description"] + description = data["description"] except Exception: - desctiption = "*No description*" + description = "*No description*" user_id = job["user_id"] username = usernames_by_id.get(user_id) or "Unknown user" created = job["created_at"].strftime('%d.%m.%Y %H:%M:%S') label = "{} - {} - {}".format( - username, desctiption, created + username, description, created ) item_label = { "type": "label", diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index e825198180..19d5701e08 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -24,7 +24,7 @@ class PrepareProjectLocal(BaseAction): settings_key = "prepare_project" - # Key to store info about trigerring create folder structure + # Key to store info about triggering create folder structure create_project_structure_key = "create_folder_structure" create_project_structure_identifier = "create.project.structure" item_splitter = {"type": "label", "value": "---"} @@ -146,7 +146,7 @@ class PrepareProjectLocal(BaseAction): root_items.append({ "type": "label", "value": ( - "

NOTE: Roots are crutial for path filling" + "

NOTE: Roots are crucial for path filling" " (and creating folder structure).

" ) }) diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py index d05f0c47f6..39cf33d605 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -66,7 +66,7 @@ class RVAction(BaseAction): def get_components_from_entity(self, session, entity, components): """Get components from various entity types. - The components dictionary is modifid in place, so nothing is returned. + The components dictionary is modified in place, so nothing is returned. Args: entity (Ftrack entity) diff --git a/openpype/modules/ftrack/event_handlers_user/action_seed.py b/openpype/modules/ftrack/event_handlers_user/action_seed.py index 4021d70c0a..657cd07a9f 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_seed.py +++ b/openpype/modules/ftrack/event_handlers_user/action_seed.py @@ -325,8 +325,8 @@ class SeedDebugProject(BaseAction): ): index = 0 - self.log.debug("*** Commiting Assets") - self.log.debug("Commiting entities. {}/{}".format( + self.log.debug("*** Committing Assets") + self.log.debug("Committing entities. {}/{}".format( created_entities, to_create_length )) self.session.commit() @@ -414,8 +414,8 @@ class SeedDebugProject(BaseAction): ): index = 0 - self.log.debug("*** Commiting Shots") - self.log.debug("Commiting entities. {}/{}".format( + self.log.debug("*** Committing Shots") + self.log.debug("Committing entities. {}/{}".format( created_entities, to_create_length )) self.session.commit() @@ -423,7 +423,7 @@ class SeedDebugProject(BaseAction): def temp_commit(self, index, created_entities, to_create_length): if index < self.max_entities_created_at_one_commit: return False - self.log.debug("Commiting {} entities. {}/{}".format( + self.log.debug("Committing {} entities. {}/{}".format( index, created_entities, to_create_length )) self.session.commit() diff --git a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py index 8748f426bd..c9e0901623 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py @@ -184,7 +184,7 @@ class StoreThumbnailsToAvalon(BaseAction): self.db_con.install() for entity in entities: - # Skip if entity is not AssetVersion (never should happend, but..) + # Skip if entity is not AssetVersion (should never happen, but..) if entity.entity_type.lower() != "assetversion": continue diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index ad7ffd8e25..77f479ee20 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -33,7 +33,7 @@ class MongoPermissionsError(Exception): """Is used when is created multiple objects of same RestApi class.""" def __init__(self, message=None): if not message: - message = "Exiting because have issue with acces to MongoDB" + message = "Exiting because have issue with access to MongoDB" super().__init__(message) @@ -340,7 +340,7 @@ def main_loop(ftrack_url): return 1 # ====== STORER ======= - # Run backup thread which does not requeire mongo to work + # Run backup thread which does not require mongo to work if storer_thread is None: if storer_failed_count < max_fail_count: storer_thread = socket_thread.SocketThread( @@ -399,7 +399,7 @@ def main_loop(ftrack_url): elif not processor_thread.is_alive(): if processor_thread.mongo_error: raise Exception( - "Exiting because have issue with acces to MongoDB" + "Exiting because have issue with access to MongoDB" ) processor_thread.join() processor_thread = None diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 0341c25717..8b4c4619a1 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -891,7 +891,7 @@ class SyncEntitiesFactory: parent_dict = self.entities_dict.get(parent_id, {}) for child_id in parent_dict.get("children", []): - # keep original `remove` value for all childs + # keep original `remove` value for all children _remove = (remove is True) if not _remove: if self.entities_dict[child_id]["avalon_attrs"].get( @@ -1191,8 +1191,8 @@ class SyncEntitiesFactory: avalon_hier = [] for item in items: value = item["value"] - # WARNING It is not possible to propage enumerate hierachical - # attributes with multiselection 100% right. Unseting all values + # WARNING It is not possible to propagate enumerate hierarchical + # attributes with multiselection 100% right. Unsetting all values # will cause inheritance from parent. if ( value is None @@ -1231,7 +1231,7 @@ class SyncEntitiesFactory: items.append("{} - \"{}\"".format(ent_path, value)) self.report_items["error"][fps_msg] = items - # Get dictionary with not None hierarchical values to pull to childs + # Get dictionary with not None hierarchical values to pull to children top_id = self.ft_project_id project_values = {} for key, value in self.entities_dict[top_id]["hier_attrs"].items(): @@ -1749,7 +1749,7 @@ class SyncEntitiesFactory: # TODO logging ent_path = self.get_ent_path(ftrack_id) msg = ( - " It is not possible" + " It is not possible" " to change the hierarchy of an entity or it's parents," " if it already contained published data." ) @@ -2584,8 +2584,8 @@ class SyncEntitiesFactory: # # ent_dict = self.entities_dict[found_by_name_id] - # TODO report - CRITICAL entity with same name alread exists in - # different hierarchy - can't recreate entity + # TODO report - CRITICAL entity with same name already exists + # in different hierarchy - can't recreate entity continue _vis_parent = deleted_entity["data"]["visualParent"] diff --git a/openpype/modules/ftrack/lib/custom_attributes.py b/openpype/modules/ftrack/lib/custom_attributes.py index 2f53815368..3e40bb02f2 100644 --- a/openpype/modules/ftrack/lib/custom_attributes.py +++ b/openpype/modules/ftrack/lib/custom_attributes.py @@ -65,7 +65,7 @@ def get_openpype_attr(session, split_hierarchical=True, query_keys=None): cust_attrs_query = ( "select {}" " from CustomAttributeConfiguration" - # Kept `pype` for Backwards Compatiblity + # Kept `pype` for Backwards Compatibility " where group.name in (\"pype\", \"{}\")" ).format(", ".join(query_keys), CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() diff --git a/openpype/modules/ftrack/lib/ftrack_action_handler.py b/openpype/modules/ftrack/lib/ftrack_action_handler.py index b24fe5f12a..07b3a780a2 100644 --- a/openpype/modules/ftrack/lib/ftrack_action_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_action_handler.py @@ -12,7 +12,7 @@ def statics_icon(*icon_statics_file_parts): class BaseAction(BaseHandler): '''Custom Action base class - `label` a descriptive string identifing your action. + `label` a descriptive string identifying your action. `varaint` To group actions together, give them the same label and specify a unique variant per action. diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py index c0b03f8a41..55400c22ab 100644 --- a/openpype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -30,7 +30,7 @@ class PreregisterException(Exception): class BaseHandler(object): '''Custom Action base class -