From 6af973fc8fc0c1ad5a5adf0996ef64c413629fce Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 14 Dec 2022 08:59:14 +0000 Subject: [PATCH 001/356] Adjust xgen creation --- openpype/hosts/maya/plugins/create/create_xgen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_xgen.py b/openpype/hosts/maya/plugins/create/create_xgen.py index 8672c06a1e..70e23cf47b 100644 --- a/openpype/hosts/maya/plugins/create/create_xgen.py +++ b/openpype/hosts/maya/plugins/create/create_xgen.py @@ -2,9 +2,9 @@ from openpype.hosts.maya.api import plugin class CreateXgen(plugin.Creator): - """Xgen interactive export""" + """Xgen""" name = "xgen" - label = "Xgen Interactive" + label = "Xgen" family = "xgen" icon = "pagelines" From 91d935c948c18ddf85dbfa54db924907b0067fcb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 14 Dec 2022 08:59:26 +0000 Subject: [PATCH 002/356] Validate xgen --- .../maya/plugins/publish/validate_xgen.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_xgen.py diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py new file mode 100644 index 0000000000..7ccd02bb07 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -0,0 +1,28 @@ +import maya.cmds as cmds + +import pyblish.api + + +class ValidateXgen(pyblish.api.InstancePlugin): + """Ensure Xgen objectset only contains collections.""" + + label = "Xgen" + order = pyblish.api.ValidatorOrder + host = ["maya"] + families = ["xgen"] + + def process(self, instance): + nodes = ( + cmds.ls(instance, type="xgmPalette", long=True) + + cmds.ls(instance, type="transform", long=True) + + cmds.ls(instance, type="xgmDescription", long=True) + + cmds.ls(instance, type="xgmSubdPatch", long=True) + ) + remainder_nodes = [] + for node in instance: + if node in nodes: + continue + remainder_nodes.append(node) + + msg = "Invalid nodes in the objectset:\n{}".format(remainder_nodes) + assert not remainder_nodes, msg From 94f0f773f865857da894e824630fdacec8666b4c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 18 Dec 2022 21:08:25 +0000 Subject: [PATCH 003/356] Exclude xgen from default Maya loading. --- openpype/hosts/maya/plugins/load/actions.py | 15 ++++++++++++++- .../hosts/maya/plugins/load/load_reference.py | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 9cc9180d6e..ced0f7ccbb 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -93,7 +93,20 @@ class ImportMayaLoader(load.LoaderPlugin): """ representations = ["ma", "mb", "obj"] - families = ["*"] + families = [ + "model", + "pointcache", + "proxyAbc", + "animation", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "camera", + "rig", + "camerarig", + "staticMesh" + ] label = "Import" order = 10 diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index c6b07b036d..31a36a587c 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -25,7 +25,6 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): "camera", "rig", "camerarig", - "xgen", "staticMesh"] representations = ["ma", "abc", "fbx", "mb"] From bf2d4ee323eb3df631cdbb93fabeb58242da0886 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 18 Dec 2022 21:09:55 +0000 Subject: [PATCH 004/356] Initial working publish of xgen --- .../maya/plugins/publish/collect_xgen.py | 39 +++++ .../plugins/publish/extract_maya_scene_raw.py | 3 +- .../maya/plugins/publish/extract_xgen.py | 138 ++++++++++++++++++ .../maya/plugins/publish/validate_xgen.py | 11 +- .../plugins/publish/collect_resources_path.py | 3 +- 5 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/collect_xgen.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_xgen.py diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py new file mode 100644 index 0000000000..fa7ec3776d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -0,0 +1,39 @@ +from maya import cmds + +import pyblish.api +from openpype.hosts.maya.api.lib import get_attribute_input + + +class CollectXgen(pyblish.api.InstancePlugin): + """Collect Xgen""" + + order = pyblish.api.CollectorOrder + 0.499999 + label = "Collect Xgen" + families = ["xgen"] + + def process(self, instance): + data = { + "xgenPalettes": cmds.ls(instance, type="xgmPalette", long=True), + "xgmDescriptions": cmds.ls( + instance, type="xgmDescription", long=True + ), + "xgmSubdPatches": cmds.ls(instance, type="xgmSubdPatch", long=True) + } + data["xgenNodes"] = ( + data["xgenPalettes"] + + data["xgmDescriptions"] + + data["xgmSubdPatches"] + ) + + if data["xgenPalettes"]: + data["xgenPalette"] = data["xgenPalettes"][0] + + data["xgenConnections"] = {} + for node in data["xgmSubdPatches"]: + data["xgenConnections"][node] = {} + for attr in ["transform", "geometry"]: + input = get_attribute_input("{}.{}".format(node, attr)) + data["xgenConnections"][node][attr] = input + + self.log.info(data) + instance.data.update(data) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 3769ec3605..c2411ca651 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -20,8 +20,7 @@ class ExtractMayaSceneRaw(publish.Extractor): "mayaScene", "setdress", "layout", - "camerarig", - "xgen"] + "camerarig"] scene_type = "ma" def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py new file mode 100644 index 0000000000..69d1d3db40 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -0,0 +1,138 @@ +import os +import copy + +from maya import cmds +import pymel.core as pc +import xgenm + +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.lib import StringTemplate + + +class ExtractXgenCache(publish.Extractor): + """Extract Xgen""" + + label = "Extract Xgen" + hosts = ["maya"] + families = ["xgen"] + scene_type = "ma" + + def process(self, instance): + if "representations" not in instance.data: + instance.data["representations"] = [] + + staging_dir = self.staging_dir(instance) + maya_filename = "{}.{}".format(instance.data["name"], self.scene_type) + maya_filepath = os.path.join(staging_dir, maya_filename) + + # Get published xgen file name. + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "xgen"}) + templates = instance.context.data["anatomy"].templates["publish"] + xgen_filename = StringTemplate(templates["file"]).format(template_data) + name = instance.data["xgenPalette"].replace(":", "__").replace("|", "") + value = xgen_filename.replace(".xgen", "__" + name + ".xgen") + attribute_data = { + "{}.xgFileName".format(instance.data["xgenPalette"]): xgen_filename + } + + # Export xgen palette files. + xgen_path = os.path.join(staging_dir, value).replace("\\", "/") + xgenm.exportPalette( + instance.data["xgenPalette"].replace("|", ""), xgen_path + ) + self.log.info("Extracted to {}".format(xgen_path)) + + representation = { + "name": name, + "ext": "xgen", + "files": value, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + # Collect nodes to export. + duplicate_nodes = [] + for node, connections in instance.data["xgenConnections"].items(): + transform_name = connections["transform"].split(".")[0] + + # Duplicate_transform subd patch geometry. + duplicate_transform = pc.duplicate(transform_name)[0] + pc.parent(duplicate_transform, world=True) + duplicate_transform.name(stripNamespace=True) + duplicate_shape = pc.listRelatives( + duplicate_transform, shapes=True + )[0] + + pc.connectAttr( + "{}.matrix".format(duplicate_transform), + "{}.transform".format(node), + force=True + ) + pc.connectAttr( + "{}.worldMesh".format(duplicate_shape), + "{}.geometry".format(node), + force=True + ) + + duplicate_nodes.append(duplicate_transform) + + # Import xgen onto the duplicate. + with maintained_selection(): + cmds.select(duplicate_nodes) + collection = xgenm.importPalette(xgen_path, []) + + # Export Maya file. + type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" + with attribute_values(attribute_data): + with maintained_selection(): + cmds.select(duplicate_nodes + [collection]) + cmds.file( + maya_filepath, + force=True, + type=type, + exportSelected=True, + preserveReferences=True, + constructionHistory=True, + shader=True, + constraints=True, + expressions=True + ) + + self.log.info("Extracted to {}".format(maya_filepath)) + + representation = { + "name": self.scene_type, + "ext": self.scene_type, + "files": maya_filename, + "stagingDir": staging_dir, + "data": {"xgenName": collection} + } + instance.data["representations"].append(representation) + + # Revert to original xgen connections. + for node, connections in instance.data["xgenConnections"].items(): + for attr, src in connections.items(): + cmds.connectAttr( + src, "{}.{}".format(node, attr), force=True + ) + + cmds.delete(duplicate_nodes + [collection]) + + # Setup transfers. + #needs to reduce resources to only what is used for the collections in + #the objectset + xgen_dir = os.path.join( + os.path.dirname(instance.context.data["currentFile"]), "xgen" + ) + transfers = [] + for root, dirs, files in os.walk(xgen_dir): + for file in files: + source = os.path.join(root, file) + destination = source.replace( + xgen_dir, instance.data["resourcesDir"] + ) + transfers.append((source, destination)) + + instance.data["transfers"] = transfers diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 7ccd02bb07..fc93f62846 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -4,19 +4,18 @@ import pyblish.api class ValidateXgen(pyblish.api.InstancePlugin): - """Ensure Xgen objectset only contains collections.""" + """Validate Xgen data.""" - label = "Xgen" + label = "Validate Xgen" order = pyblish.api.ValidatorOrder host = ["maya"] families = ["xgen"] def process(self, instance): + # Validate only xgen collections are in objectset. nodes = ( - cmds.ls(instance, type="xgmPalette", long=True) + - cmds.ls(instance, type="transform", long=True) + - cmds.ls(instance, type="xgmDescription", long=True) + - cmds.ls(instance, type="xgmSubdPatch", long=True) + instance.data["xgenNodes"] + + cmds.ls(instance, type="transform", long=True) ) remainder_nodes = [] for node in instance: diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index a2d5b95ab2..52aba0eca3 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -57,7 +57,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "background", "effect", "staticMesh", - "skeletalMesh" + "skeletalMesh", + "xgen" ] def process(self, instance): From a1890ee04d9b06031a9c136a276ffba70b2f67b3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 18 Dec 2022 21:10:07 +0000 Subject: [PATCH 005/356] Initial working loading of xgen --- openpype/hosts/maya/plugins/load/load_xgen.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 openpype/hosts/maya/plugins/load/load_xgen.py diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py new file mode 100644 index 0000000000..3b7451f95c --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -0,0 +1,90 @@ +import os +import shutil + +import maya.cmds as cmds +import pymel.core as pm + +import openpype.hosts.maya.api.plugin +from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api import current_file + + +class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): + """Load Xgen as reference""" + + families = ["xgen"] + representations = ["ma", "mb"] + + label = "Reference Xgen" + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, options): + maya_filepath = self.prepare_root_value( + self.fname, context["project"]["name"] + ) + project_path = os.path.dirname(current_file()) + + # Setup xgen palette file. + # Copy the xgen palette file from published version. + _, maya_extension = os.path.splitext(maya_filepath) + source = maya_filepath.replace(maya_extension, ".xgen") + destination = os.path.join( + project_path, + "{basename}__{namespace}__{name}.xgen".format( + basename=os.path.splitext(os.path.basename(current_file()))[0], + namespace=namespace, + name=context["representation"]["data"]["xgenName"] + ) + ) + shutil.copy(source, destination) + + # Modify xgDataPath and xgProjectPath to have current workspace first + # and published version directory second. This ensure that any newly + # created xgen files are created in the current workspace. + resources_path = os.path.join(os.path.dirname(source), "resources") + lines = [] + with open(destination, "r") as f: + for line in [line.rstrip() for line in f]: + if line.startswith("\txgDataPath"): + data_path = line.split("\t")[-1] + line = "\txgDataPath\t\t{}{}{}".format( + data_path, + os.pathsep, + data_path.replace( + "${PROJECT}xgen", resources_path.replace("\\", "/") + ) + ) + + if line.startswith("\txgProjectPath"): + line = "\txgProjectPath\t\t{}/".format( + project_path.replace("\\", "/") + ) + + lines.append(line) + + with open(destination, "w") as f: + f.write("\n".join(lines)) + + # Reference xgen. Xgen does not like being referenced in under a group. + new_nodes = [] + + with maintained_selection(): + nodes = cmds.file( + maya_filepath, + namespace=namespace, + sharedReferenceFile=False, + reference=True, + returnNewNodes=True + ) + + shapes = cmds.ls(nodes, shapes=True, long=True) + + new_nodes = (list(set(nodes) - set(shapes))) + + self[:] = new_nodes + + return new_nodes + + def update(self, container, representation): + pass From 2a7d05e090b6b3a5d30f52f7006f57f9137603bd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Dec 2022 07:25:00 +0000 Subject: [PATCH 006/356] Working destructive updating --- openpype/hosts/maya/plugins/load/load_xgen.py | 63 ++++++++++++++++--- .../maya/plugins/publish/extract_xgen.py | 13 ++-- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 3b7451f95c..2d58550bc7 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -2,11 +2,15 @@ import os import shutil import maya.cmds as cmds -import pymel.core as pm +import xgenm 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 +) from openpype.hosts.maya.api import current_file +from openpype.hosts.maya.api.plugin import get_reference_node +from openpype.pipeline import get_representation_path class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -19,13 +23,10 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" - def process_reference(self, context, name, namespace, options): - maya_filepath = self.prepare_root_value( - self.fname, context["project"]["name"] - ) + def setup_xgen_palette_file(self, maya_filepath, namespace, name): + # Setup xgen palette file. project_path = os.path.dirname(current_file()) - # Setup xgen palette file. # Copy the xgen palette file from published version. _, maya_extension = os.path.splitext(maya_filepath) source = maya_filepath.replace(maya_extension, ".xgen") @@ -34,9 +35,10 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): "{basename}__{namespace}__{name}.xgen".format( basename=os.path.splitext(os.path.basename(current_file()))[0], namespace=namespace, - name=context["representation"]["data"]["xgenName"] + name=name ) - ) + ).replace("\\", "/") + self.log.info("Copying {} to {}".format(source, destination)) shutil.copy(source, destination) # Modify xgDataPath and xgProjectPath to have current workspace first @@ -66,6 +68,18 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): with open(destination, "w") as f: f.write("\n".join(lines)) + return destination + + def process_reference(self, context, name, namespace, options): + maya_filepath = self.prepare_root_value( + self.fname, context["project"]["name"] + ) + + name = context["representation"]["data"]["xgenName"] + xgen_file = self.setup_xgen_palette_file( + maya_filepath, namespace, name + ) + # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] @@ -78,6 +92,13 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True ) + xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] + cmds.setAttr( + "{}.xgFileName".format(xgen_palette), + os.path.basename(xgen_file), + type="string" + ) + shapes = cmds.ls(nodes, shapes=True, long=True) new_nodes = (list(set(nodes) - set(shapes))) @@ -87,4 +108,26 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): return new_nodes def update(self, container, representation): - pass + super().update(container, representation) + + # Get reference node from container members + node = container["objectName"] + members = get_container_members(node) + reference_node = get_reference_node(members, self.log) + namespace = cmds.referenceQuery(reference_node, namespace=True)[1:] + + xgen_file = self.setup_xgen_palette_file( + get_representation_path(representation), + namespace, + representation["data"]["xgenName"] + ) + + xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] + cmds.setAttr( + "{}.xgFileName".format(xgen_palette), + os.path.basename(xgen_file), + type="string" + ) + + # Reload reference to update the xgen. + cmds.file(loadReference=reference_node, force=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 69d1d3db40..2daa6e238d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -32,13 +32,10 @@ class ExtractXgenCache(publish.Extractor): templates = instance.context.data["anatomy"].templates["publish"] xgen_filename = StringTemplate(templates["file"]).format(template_data) name = instance.data["xgenPalette"].replace(":", "__").replace("|", "") - value = xgen_filename.replace(".xgen", "__" + name + ".xgen") - attribute_data = { - "{}.xgFileName".format(instance.data["xgenPalette"]): xgen_filename - } + xgen_filename = xgen_filename.replace(".xgen", "__" + name + ".xgen") # Export xgen palette files. - xgen_path = os.path.join(staging_dir, value).replace("\\", "/") + xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") xgenm.exportPalette( instance.data["xgenPalette"].replace("|", ""), xgen_path ) @@ -47,7 +44,7 @@ class ExtractXgenCache(publish.Extractor): representation = { "name": name, "ext": "xgen", - "files": value, + "files": xgen_filename, "stagingDir": staging_dir, } instance.data["representations"].append(representation) @@ -83,6 +80,10 @@ class ExtractXgenCache(publish.Extractor): cmds.select(duplicate_nodes) collection = xgenm.importPalette(xgen_path, []) + attribute_data = { + "{}.xgFileName".format(collection): xgen_filename + } + # Export Maya file. type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" with attribute_values(attribute_data): From 89ea46189e3f8fe6df6006804ec84227003641ad Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Dec 2022 11:46:16 +0000 Subject: [PATCH 007/356] Initial non-destructive updating --- openpype/hosts/maya/plugins/load/load_xgen.py | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 2d58550bc7..99673440fd 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -79,6 +79,22 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): xgen_file = self.setup_xgen_palette_file( maya_filepath, namespace, name ) + xgd_file = xgen_file.replace(".xgen", ".xgd") + + # Create a placeholder xgen delta file. + #change author and date + xgd_template = """ +# XGen Delta File +# +# Version: C:/Program Files/Autodesk/Maya2022/plug-ins/xgen/ +# Author: tokejepsen +# Date: Tue Dec 20 09:03:29 2022 + +FileVersion 18 + +""" + with open(xgd_file, "w") as f: + f.write(xgd_template) # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] @@ -94,10 +110,16 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] cmds.setAttr( - "{}.xgFileName".format(xgen_palette), + "{}.xgBaseFile".format(xgen_palette), os.path.basename(xgen_file), type="string" ) + cmds.setAttr( + "{}.xgFileName".format(xgen_palette), + os.path.basename(xgd_file), + type="string" + ) + cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) shapes = cmds.ls(nodes, shapes=True, long=True) @@ -108,11 +130,9 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): return new_nodes def update(self, container, representation): - super().update(container, representation) - # Get reference node from container members - node = container["objectName"] - members = get_container_members(node) + container_node = container["objectName"] + members = get_container_members(container_node) reference_node = get_reference_node(members, self.log) namespace = cmds.referenceQuery(reference_node, namespace=True)[1:] @@ -121,13 +141,35 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): namespace, representation["data"]["xgenName"] ) + xgd_file = xgen_file.replace(".xgen", ".xgd") xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] + cmds.setAttr( "{}.xgFileName".format(xgen_palette), os.path.basename(xgen_file), type="string" ) + cmds.setAttr( + "{}.xgBaseFile".format(xgen_palette), + "", + type="string" + ) + cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), False) - # Reload reference to update the xgen. - cmds.file(loadReference=reference_node, force=True) + super().update(container, representation) + + # Apply xgen delta. + xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) + + cmds.setAttr( + "{}.xgBaseFile".format(xgen_palette), + os.path.basename(xgen_file), + type="string" + ) + cmds.setAttr( + "{}.xgFileName".format(xgen_palette), + os.path.basename(xgd_file), + type="string" + ) + cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) From c58c0d98784d3579b72bfa721d0dcb35442bf296 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Dec 2022 11:52:39 +0000 Subject: [PATCH 008/356] Refactor --- openpype/hosts/maya/plugins/load/load_xgen.py | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 99673440fd..61204522e6 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -6,7 +6,7 @@ import xgenm import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.lib import ( - maintained_selection, get_container_members + maintained_selection, get_container_members, attribute_values ) from openpype.hosts.maya.api import current_file from openpype.hosts.maya.api.plugin import get_reference_node @@ -145,31 +145,14 @@ FileVersion 18 xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] - cmds.setAttr( - "{}.xgFileName".format(xgen_palette), - os.path.basename(xgen_file), - type="string" - ) - cmds.setAttr( - "{}.xgBaseFile".format(xgen_palette), - "", - type="string" - ) - cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), False) + attribute_data = { + "{}.xgFileName".format(xgen_palette): os.path.basename(xgen_file), + "{}.xgBaseFile".format(xgen_palette): "", + "{}.xgExportAsDelta".format(xgen_palette): False + } - super().update(container, representation) + with attribute_values(attribute_data): + super().update(container, representation) - # Apply xgen delta. - xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) - - cmds.setAttr( - "{}.xgBaseFile".format(xgen_palette), - os.path.basename(xgen_file), - type="string" - ) - cmds.setAttr( - "{}.xgFileName".format(xgen_palette), - os.path.basename(xgd_file), - type="string" - ) - cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) + # Apply xgen delta. + xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) From 55e3ae2729248c5d9db380fa2f52c20624f3c892 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 09:23:57 +0000 Subject: [PATCH 009/356] Fix xgd missing file --- openpype/hosts/maya/plugins/load/load_xgen.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 61204522e6..f6b487e7e8 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -81,8 +81,12 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): ) xgd_file = xgen_file.replace(".xgen", ".xgd") - # Create a placeholder xgen delta file. - #change author and date + # Create a placeholder xgen delta file. This create an expression + # attribute of float. If we did not add any changes to the delta file, + # then Xgen deletes the xgd file on save to clean up (?). This gives + # errors when launching the workfile again due to trying to find the + # xgd file. + #change author, date and version path xgd_template = """ # XGen Delta File # @@ -92,6 +96,8 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): FileVersion 18 +Palette custom_float_ignore 0 + """ with open(xgd_file, "w") as f: f.write(xgd_template) @@ -130,7 +136,19 @@ FileVersion 18 return new_nodes def update(self, container, representation): - # Get reference node from container members + """Workflow for updating Xgen. + + - Copy and overwrite the workspace .xgen file. + - Set collection attributes to not include delta files. + - Update xgen maya file reference. + - Apply the delta file changes. + - Reset collection attributes to include delta files. + + We have to do this workflow because when using referencing of the xgen + collection, Maya implicitly imports the Xgen data from the xgen file so + we dont have any control over added the delta file changes. + """ + container_node = container["objectName"] members = get_container_members(container_node) reference_node = get_reference_node(members, self.log) @@ -150,9 +168,7 @@ FileVersion 18 "{}.xgBaseFile".format(xgen_palette): "", "{}.xgExportAsDelta".format(xgen_palette): False } - with attribute_values(attribute_data): super().update(container, representation) - # Apply xgen delta. xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) From 87e72491898df9a29c0b44bc044c9f6e08de40fc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 11:03:50 +0000 Subject: [PATCH 010/356] Avoid making placeholder xgd file --- openpype/hosts/maya/plugins/load/load_xgen.py | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index f6b487e7e8..23a22127e9 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -81,27 +81,6 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): ) xgd_file = xgen_file.replace(".xgen", ".xgd") - # Create a placeholder xgen delta file. This create an expression - # attribute of float. If we did not add any changes to the delta file, - # then Xgen deletes the xgd file on save to clean up (?). This gives - # errors when launching the workfile again due to trying to find the - # xgd file. - #change author, date and version path - xgd_template = """ -# XGen Delta File -# -# Version: C:/Program Files/Autodesk/Maya2022/plug-ins/xgen/ -# Author: tokejepsen -# Date: Tue Dec 20 09:03:29 2022 - -FileVersion 18 - -Palette custom_float_ignore 0 - -""" - with open(xgd_file, "w") as f: - f.write(xgd_template) - # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] @@ -127,6 +106,14 @@ Palette custom_float_ignore 0 ) cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) + # This create an expression attribute of float. If we did not add + # any changes to collection, then Xgen does not create an xgd file + # on save. This gives errors when launching the workfile again due + # to trying to find the xgd file. + xgenm.addCustomAttr( + "custom_float_ignore", xgen_palette.replace("|", "") + ) + shapes = cmds.ls(nodes, shapes=True, long=True) new_nodes = (list(set(nodes) - set(shapes))) From 2c9613b55e67e2854e149849dc3a057fc6583591 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 11:38:22 +0000 Subject: [PATCH 011/356] Validate against deactive modifiers --- .../maya/plugins/publish/validate_xgen.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index fc93f62846..9393520d6d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -1,4 +1,7 @@ +import json + import maya.cmds as cmds +import xgenm import pyblish.api @@ -25,3 +28,27 @@ class ValidateXgen(pyblish.api.InstancePlugin): msg = "Invalid nodes in the objectset:\n{}".format(remainder_nodes) assert not remainder_nodes, msg + + # Cant have deactive modifiers in collection cause Xgen will try and + # look for them when loading. + palette = instance.data["xgenPalette"].replace("|", "") + deactive_modifiers = {} + for description in instance.data["xgmDescriptions"]: + description = description.split("|")[-2] + modifier_names = xgenm.fxModules(palette, description) + for name in modifier_names: + attr = xgenm.getAttr("active", palette, description, name) + # Attribute value are lowercase strings of false/true. + if attr == "false": + try: + deactive_modifiers[description].append(name) + except KeyError: + deactive_modifiers[description] = [name] + + msg = ( + "There are deactive modifiers on the collection. " + "Please delete these:\n{}".format( + json.dumps(deactive_modifiers, indent=4, sort_keys=True) + ) + ) + assert not deactive_modifiers, msg From 0fc1e357a35c32bf505f4f1661eb631ffe340585 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 12:19:23 +0000 Subject: [PATCH 012/356] Validate against multiple collections per instance. --- openpype/hosts/maya/plugins/publish/validate_xgen.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 9393520d6d..1ef85c4cc6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -29,6 +29,13 @@ class ValidateXgen(pyblish.api.InstancePlugin): msg = "Invalid nodes in the objectset:\n{}".format(remainder_nodes) assert not remainder_nodes, msg + # Only one collection per instance. + palette_amount = len(instance.data["xgenPalettes"]) + msg = "Only one collection per instance allow. Found {}:\n{}".format( + palette_amount, instance.data["xgenPalettes"] + ) + assert palette_amount == 1, msg + # Cant have deactive modifiers in collection cause Xgen will try and # look for them when loading. palette = instance.data["xgenPalette"].replace("|", "") From 1bb6d77e4eab876b354f14637f70ad3a2872c777 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 12:37:24 +0000 Subject: [PATCH 013/356] Hound --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 2daa6e238d..4979f583b4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -122,13 +122,11 @@ class ExtractXgenCache(publish.Extractor): cmds.delete(duplicate_nodes + [collection]) # Setup transfers. - #needs to reduce resources to only what is used for the collections in - #the objectset xgen_dir = os.path.join( os.path.dirname(instance.context.data["currentFile"]), "xgen" ) transfers = [] - for root, dirs, files in os.walk(xgen_dir): + for root, _, files in os.walk(xgen_dir): for file in files: source = os.path.join(root, file) destination = source.replace( From 2875bb81fbe98d9504f582b05ae75033360ba140 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 21 Dec 2022 12:51:49 +0000 Subject: [PATCH 014/356] Update openpype/hosts/maya/plugins/publish/extract_xgen.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/publish/extract_xgen.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 4979f583b4..003b6aaa85 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -55,24 +55,26 @@ class ExtractXgenCache(publish.Extractor): transform_name = connections["transform"].split(".")[0] # Duplicate_transform subd patch geometry. - duplicate_transform = pc.duplicate(transform_name)[0] - pc.parent(duplicate_transform, world=True) - duplicate_transform.name(stripNamespace=True) - duplicate_shape = pc.listRelatives( - duplicate_transform, shapes=True + duplicate_transform = cmds.duplicate(transform_name)[0] + duplicate_shape = cmds.listRelatives( + duplicate_transform, + shapes=True, + fullPath=True )[0] - pc.connectAttr( + cmds.connectAttr( "{}.matrix".format(duplicate_transform), "{}.transform".format(node), force=True ) - pc.connectAttr( + cmds.connectAttr( "{}.worldMesh".format(duplicate_shape), "{}.geometry".format(node), force=True ) + duplicate_transform = cmds.parent(duplicate_transform, world=True)[0] + duplicate_nodes.append(duplicate_transform) # Import xgen onto the duplicate. From b1bef86858d92239868cfcaffa8e55d5319cfecc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 12:56:23 +0000 Subject: [PATCH 015/356] @BigRoy suggestion --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 003b6aaa85..d8045a90e7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -131,9 +131,7 @@ class ExtractXgenCache(publish.Extractor): for root, _, files in os.walk(xgen_dir): for file in files: source = os.path.join(root, file) - destination = source.replace( - xgen_dir, instance.data["resourcesDir"] - ) + destination = os.path.join(instance.data["resourcesDir"], file) transfers.append((source, destination)) instance.data["transfers"] = transfers From 9fdd402fec2a6b84da8da64753bd03bb7e101ecd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 12:56:52 +0000 Subject: [PATCH 016/356] Hound --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index d8045a90e7..89b73a2d56 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -2,7 +2,6 @@ import os import copy from maya import cmds -import pymel.core as pc import xgenm from openpype.pipeline import publish @@ -57,8 +56,8 @@ class ExtractXgenCache(publish.Extractor): # Duplicate_transform subd patch geometry. duplicate_transform = cmds.duplicate(transform_name)[0] duplicate_shape = cmds.listRelatives( - duplicate_transform, - shapes=True, + duplicate_transform, + shapes=True, fullPath=True )[0] @@ -73,7 +72,9 @@ class ExtractXgenCache(publish.Extractor): force=True ) - duplicate_transform = cmds.parent(duplicate_transform, world=True)[0] + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] duplicate_nodes.append(duplicate_transform) From 2609508ced1e5076a91288d290204ac30f146c6d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Dec 2022 13:01:16 +0000 Subject: [PATCH 017/356] deactive > inactive --- .../hosts/maya/plugins/publish/validate_xgen.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 1ef85c4cc6..442af794cd 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -36,10 +36,10 @@ class ValidateXgen(pyblish.api.InstancePlugin): ) assert palette_amount == 1, msg - # Cant have deactive modifiers in collection cause Xgen will try and + # Cant have inactive modifiers in collection cause Xgen will try and # look for them when loading. palette = instance.data["xgenPalette"].replace("|", "") - deactive_modifiers = {} + inactive_modifiers = {} for description in instance.data["xgmDescriptions"]: description = description.split("|")[-2] modifier_names = xgenm.fxModules(palette, description) @@ -48,14 +48,14 @@ class ValidateXgen(pyblish.api.InstancePlugin): # Attribute value are lowercase strings of false/true. if attr == "false": try: - deactive_modifiers[description].append(name) + inactive_modifiers[description].append(name) except KeyError: - deactive_modifiers[description] = [name] + inactive_modifiers[description] = [name] msg = ( - "There are deactive modifiers on the collection. " + "There are inactive modifiers on the collection. " "Please delete these:\n{}".format( - json.dumps(deactive_modifiers, indent=4, sort_keys=True) + json.dumps(inactive_modifiers, indent=4, sort_keys=True) ) ) - assert not deactive_modifiers, msg + assert not inactive_modifiers, msg From cbbb3dc8041994912ed6bf5d638ee9f06d36556d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 21 Dec 2022 13:04:01 +0000 Subject: [PATCH 018/356] Update openpype/hosts/maya/plugins/publish/validate_xgen.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/validate_xgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 442af794cd..bc25082aa8 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -16,7 +16,7 @@ class ValidateXgen(pyblish.api.InstancePlugin): def process(self, instance): # Validate only xgen collections are in objectset. - nodes = ( + nodes = set( instance.data["xgenNodes"] + cmds.ls(instance, type="transform", long=True) ) From 2a748047ed777067ada7e3b78eca8b6df0f65678 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 4 Jan 2023 12:41:12 +0000 Subject: [PATCH 019/356] Only collect resource files from instance collection --- .../maya/plugins/publish/extract_xgen.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 89b73a2d56..5b4c6b29f2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -124,15 +124,26 @@ class ExtractXgenCache(publish.Extractor): cmds.delete(duplicate_nodes + [collection]) - # Setup transfers. - xgen_dir = os.path.join( - os.path.dirname(instance.context.data["currentFile"]), "xgen" + # Collect all files under palette root as resources. + data_path = xgenm.getAttr( + "xgDataPath", instance.data["xgenPalette"].replace("|", "") + ).split(os.pathsep)[0] + data_path = data_path.replace( + "${PROJECT}", + xgenm.getAttr( + "xgProjectPath", instance.data["xgenPalette"].replace("|", "") + ) ) transfers = [] - for root, _, files in os.walk(xgen_dir): + for root, _, files in os.walk(data_path): for file in files: - source = os.path.join(root, file) - destination = os.path.join(instance.data["resourcesDir"], file) - transfers.append((source, destination)) + source = os.path.join(root, file).replace("\\", "/") + destination = os.path.join( + instance.data["resourcesDir"], + "collections", + os.path.basename(data_path), + source.replace(data_path, "")[1:] + ) + transfers.append((source, destination.replace("\\", "/"))) instance.data["transfers"] = transfers From 370bb96408c88e68273593a8ae7aabcbfff93f99 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 4 Jan 2023 15:03:30 +0000 Subject: [PATCH 020/356] Improve validation error message --- openpype/hosts/maya/plugins/publish/validate_xgen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index bc25082aa8..5ba9ddad52 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -26,7 +26,10 @@ class ValidateXgen(pyblish.api.InstancePlugin): continue remainder_nodes.append(node) - msg = "Invalid nodes in the objectset:\n{}".format(remainder_nodes) + msg = ( + "Only the collection is used when publishing. Found these invalid" + " nodes in the objectset:\n{}".format(remainder_nodes) + ) assert not remainder_nodes, msg # Only one collection per instance. From 8e233c46c8c1fceea3b0c05f0b31cf20172eb410 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 4 Jan 2023 16:54:25 +0000 Subject: [PATCH 021/356] Update openpype/hosts/maya/plugins/publish/validate_xgen.py Co-authored-by: Roy Nieterau --- .../maya/plugins/publish/validate_xgen.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 5ba9ddad52..b9d67dad25 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -16,21 +16,17 @@ class ValidateXgen(pyblish.api.InstancePlugin): def process(self, instance): # Validate only xgen collections are in objectset. - nodes = set( + valid_nodes = set( instance.data["xgenNodes"] + cmds.ls(instance, type="transform", long=True) ) - remainder_nodes = [] - for node in instance: - if node in nodes: - continue - remainder_nodes.append(node) - - msg = ( - "Only the collection is used when publishing. Found these invalid" - " nodes in the objectset:\n{}".format(remainder_nodes) - ) - assert not remainder_nodes, msg + invalid_nodes = [node for node in instance if node not in valid_nodes] + + if invalid_nodes: + raise KnownPublishError( + "Only the collection is used when publishing. Found these invalid" + " nodes in the objectset:\n{}".format(invalid_nodes) + ) # Only one collection per instance. palette_amount = len(instance.data["xgenPalettes"]) From 8918f8965c7d591aaec1b3da608dd8be77d68ea9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 4 Jan 2023 16:56:32 +0000 Subject: [PATCH 022/356] Fix suggestion --- openpype/hosts/maya/plugins/publish/validate_xgen.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index b9d67dad25..b5817d4920 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -4,6 +4,7 @@ import maya.cmds as cmds import xgenm import pyblish.api +from openpype.pipeline.publish import KnownPublishError class ValidateXgen(pyblish.api.InstancePlugin): @@ -21,11 +22,11 @@ class ValidateXgen(pyblish.api.InstancePlugin): cmds.ls(instance, type="transform", long=True) ) invalid_nodes = [node for node in instance if node not in valid_nodes] - + if invalid_nodes: raise KnownPublishError( - "Only the collection is used when publishing. Found these invalid" - " nodes in the objectset:\n{}".format(invalid_nodes) + "Only the collection is used when publishing. Found these " + "invalid nodes in the objectset:\n{}".format(invalid_nodes) ) # Only one collection per instance. From f47b52aea7da19ca19c1dc6602a7390b9575a91a Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 4 Jan 2023 18:22:48 +0100 Subject: [PATCH 023/356] fix features for gizmo menu --- openpype/hosts/nuke/api/gizmo_menu.py | 10 ++++++++-- .../schemas/schema_nuke_scriptsgizmo.json | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/gizmo_menu.py b/openpype/hosts/nuke/api/gizmo_menu.py index 9edfc62e3b..5838ee8a8a 100644 --- a/openpype/hosts/nuke/api/gizmo_menu.py +++ b/openpype/hosts/nuke/api/gizmo_menu.py @@ -53,12 +53,18 @@ class GizmoMenu(): item_type = item.get("sourcetype") - if item_type == ("python" or "file"): + if item_type == "python": parent.addCommand( item["title"], command=str(item["command"]), icon=item.get("icon"), - shortcut=item.get("hotkey") + shortcut=item.get("shortcut") + ) + elif item_type == "file": + parent.addCommand( + item['title'], + "nuke.createNode('{}')".format(item.get('file_name')), + shortcut=item.get('shortcut') ) # add separator diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json index abe14970c5..e4c65177a7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_scriptsgizmo.json @@ -72,6 +72,11 @@ "key": "command", "label": "Python command" }, + { + "type": "text", + "key": "icon", + "label": "Icon Path" + }, { "type": "text", "key": "shortcut", From 6575e304b8a8127e4fa0d0989efcacb3d773be46 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 5 Jan 2023 18:20:03 +0000 Subject: [PATCH 024/356] Connect Geometry action --- .../plugins/inventory/connect_geometry.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 openpype/hosts/maya/plugins/inventory/connect_geometry.py diff --git a/openpype/hosts/maya/plugins/inventory/connect_geometry.py b/openpype/hosts/maya/plugins/inventory/connect_geometry.py new file mode 100644 index 0000000000..375ad29ef0 --- /dev/null +++ b/openpype/hosts/maya/plugins/inventory/connect_geometry.py @@ -0,0 +1,139 @@ +from maya import cmds + +from openpype.pipeline import InventoryAction, get_representation_context + + +class ConnectGeometry(InventoryAction): + """Connect geometries within containers. + + Source container will connect to the target containers, by searching for + matching geometry IDs (cbid). + Source containers are of family; "animation" and "pointcache". + The connection with be done with a live world space blendshape. + """ + + label = "Connect Geometry" + 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 = {} + for container in containers: + family = get_representation_context( + container["representation"] + )["subset"]["data"]["family"] + try: + containers_by_family[family].append(container) + except KeyError: + containers_by_family[family] = [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] + + # Collect matching geometry transforms based cbId attribute. + target_containers = [] + for family, containers in containers_by_family.items(): + if family in ["animation", "pointcache"]: + continue + + target_containers.extend(containers) + + source_data = self.get_container_data(source_container["objectName"]) + matches = [] + node_types = [] + for target_container in target_containers: + target_data = self.get_container_data( + target_container["objectName"] + ) + node_types.extend(target_data["node_types"]) + for id, transform in target_data["ids"].items(): + source_match = source_data["ids"].get(id) + if source_match: + matches.append([source_match, transform]) + + # Message user about what is about to happen. + if not matches: + self.display_warning("No matching geometries found.") + return + + message = "Linking geometries:\n\n" + for match in matches: + message += "{} > {}\n".format(match[0], match[1]) + + choice = self.display_warning(message, show_cancel=True) + if choice is False: + return + + # Setup live worldspace blendshape connection. + for match in matches: + source = match[0] + target = match[1] + blendshape = cmds.blendShape(source, target)[0] + cmds.setAttr(blendshape + ".origin", 0) + cmds.setAttr(blendshape + "." + target.split(":")[-1], 1) + + # Update Xgen if in any of the containers. + if "xgmPalette" in node_types: + cmds.xgmPreview() + + def get_container_data(self, container): + data = {"node_types": [], "ids": {}} + ref_node = cmds.sets(container, query=True, nodesOnly=True)[0] + for node in cmds.referenceQuery(ref_node, nodes=True): + node_type = cmds.nodeType(node) + data["node_types"].append(node_type) + if node_type == "mesh": + transform = cmds.listRelatives(node, parent=True)[0] + id = cmds.getAttr(transform + ".cbId") + data["ids"][id] = transform + + return data + + def display_warning(self, message, show_cancel=False): + """Show feedback to user. + + Returns: + bool + """ + + from Qt 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 From 2facf4d49eb3c18f3bfe39a3d50bb2bac51abca7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 6 Jan 2023 10:19:21 +0000 Subject: [PATCH 025/356] Open workfile post initialization --- openpype/hooks/pre_add_last_workfile_arg.py | 12 ++++++++++ openpype/hosts/maya/startup/userSetup.py | 22 +++++++++++++++---- .../defaults/project_settings/maya.json | 1 + .../projects_schema/schema_project_maya.json | 5 +++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 3609620917..ffb116d2e4 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -1,5 +1,7 @@ import os + from openpype.lib import PreLaunchHook +from openpype.settings import get_project_settings class AddLastWorkfileToLaunchArgs(PreLaunchHook): @@ -40,5 +42,15 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): self.log.info("Current context does not have any workfile yet.") return + # Determine whether to open workfile post initialization. + if self.data["app"].host_name == "maya": + project_name = self.data["project_name"] + settings = get_project_settings(project_name) + key = "open_workfile_post_initialization" + if settings["maya"][key]: + self.log.debug("Opening workfile post initialization.") + self.data["env"]["OPENPYPE_" + key.upper()] = "1" + return + # Add path to workfile to arguments self.launch_context.launch_args.append(last_workfile) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 40cd51f2d8..b64ed93000 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,16 +1,27 @@ import os + from openpype.settings import get_project_settings from openpype.pipeline import install_host from openpype.hosts.maya.api import MayaHost + from maya import cmds host = MayaHost() install_host(host) -print("starting OpenPype usersetup") +print("Starting OpenPype usersetup...") -# build a shelf +# Open Workfile Post Initialization. +key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" +if bool(int(os.environ.get(key, "0"))): + cmds.evalDeferred( + "cmds.file(os.environ[\"AVALON_LAST_WORKFILE\"], open=True," + " force=True)", + lowestPriority=True + ) + +# Build a shelf. settings = get_project_settings(os.environ['AVALON_PROJECT']) shelf_preset = settings['maya'].get('project_shelf') @@ -26,7 +37,10 @@ if shelf_preset: print(import_string) exec(import_string) - cmds.evalDeferred("mlib.shelf(name=shelf_preset['name'], iconPath=icon_path, preset=shelf_preset)") + cmds.evalDeferred( + "mlib.shelf(name=shelf_preset['name'], iconPath=icon_path," + " preset=shelf_preset)" + ) -print("finished OpenPype usersetup") +print("Finished OpenPype usersetup.") diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 5f40c2a10c..dbc3282ce8 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,4 +1,5 @@ { + "open_workfile_post_initialization": false, "imageio": { "colorManagementPreference_v2": { "enabled": true, 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 b2d79797a3..b668d74afd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -5,6 +5,11 @@ "label": "Maya", "is_file": true, "children": [ + { + "type": "boolean", + "key": "open_workfile_post_initialization", + "label": "Open Workfile Post Initialization" + }, { "key": "imageio", "type": "dict", From c0da2c8afc711657b0b46eeba34b1742b9b5afa8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 7 Jan 2023 09:33:38 +0000 Subject: [PATCH 026/356] Simplify validation --- .../maya/plugins/publish/validate_xgen.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index b5817d4920..19cf612848 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -16,29 +16,27 @@ class ValidateXgen(pyblish.api.InstancePlugin): families = ["xgen"] def process(self, instance): - # Validate only xgen collections are in objectset. - valid_nodes = set( - instance.data["xgenNodes"] + - cmds.ls(instance, type="transform", long=True) - ) - invalid_nodes = [node for node in instance if node not in valid_nodes] + set_members = instance.data.get("setMembers") - if invalid_nodes: + # Only 1 collection/node per instance. + if len(set_members) != 1: raise KnownPublishError( - "Only the collection is used when publishing. Found these " - "invalid nodes in the objectset:\n{}".format(invalid_nodes) + "Only one collection per instance is allowed." + " Found:\n{}".format(set_members) ) - # Only one collection per instance. - palette_amount = len(instance.data["xgenPalettes"]) - msg = "Only one collection per instance allow. Found {}:\n{}".format( - palette_amount, instance.data["xgenPalettes"] - ) - assert palette_amount == 1, msg + # Only xgen palette node is allowed. + node_type = cmds.nodeType(set_members[0]) + if node_type != "xgmPalette": + raise KnownPublishError( + "Only node of type \"xgmPalette\" are allowed. Referred to as" + " \"collection\" in the Maya UI." + " Node type found: {}".format(node_type) + ) # Cant have inactive modifiers in collection cause Xgen will try and # look for them when loading. - palette = instance.data["xgenPalette"].replace("|", "") + palette = instance.data["xgmPalette"].replace("|", "") inactive_modifiers = {} for description in instance.data["xgmDescriptions"]: description = description.split("|")[-2] From 239995f715ea3df72af6cc9416152d8b043a3938 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 7 Jan 2023 09:34:20 +0000 Subject: [PATCH 027/356] Account for references and children of patches --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 5b4c6b29f2..99efcb34ce 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -61,6 +61,12 @@ class ExtractXgenCache(publish.Extractor): fullPath=True )[0] + # Discard the children. + shapes = cmds.listRelatives(duplicate_transform, shapes=True) + children = cmds.listRelatives(duplicate_transform, children=True) + cmds.delete(set(children) - set(shapes)) + + # Connect attributes. cmds.connectAttr( "{}.matrix".format(duplicate_transform), "{}.transform".format(node), @@ -97,7 +103,7 @@ class ExtractXgenCache(publish.Extractor): force=True, type=type, exportSelected=True, - preserveReferences=True, + preserveReferences=False, constructionHistory=True, shader=True, constraints=True, From efb7d42a7fa9d267b1f7e284bc1d659f3cd41583 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 7 Jan 2023 09:34:34 +0000 Subject: [PATCH 028/356] Code cosmetics --- openpype/hosts/maya/plugins/publish/collect_xgen.py | 8 ++++---- openpype/hosts/maya/plugins/publish/extract_xgen.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index fa7ec3776d..5a48b1d221 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -13,20 +13,20 @@ class CollectXgen(pyblish.api.InstancePlugin): def process(self, instance): data = { - "xgenPalettes": cmds.ls(instance, type="xgmPalette", long=True), + "xgmPalettes": cmds.ls(instance, type="xgmPalette", long=True), "xgmDescriptions": cmds.ls( instance, type="xgmDescription", long=True ), "xgmSubdPatches": cmds.ls(instance, type="xgmSubdPatch", long=True) } data["xgenNodes"] = ( - data["xgenPalettes"] + + data["xgmPalettes"] + data["xgmDescriptions"] + data["xgmSubdPatches"] ) - if data["xgenPalettes"]: - data["xgenPalette"] = data["xgenPalettes"][0] + if data["xgmPalettes"]: + data["xgmPalette"] = data["xgmPalettes"][0] data["xgenConnections"] = {} for node in data["xgmSubdPatches"]: diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 99efcb34ce..22260df2c7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -30,13 +30,13 @@ class ExtractXgenCache(publish.Extractor): template_data.update({"ext": "xgen"}) templates = instance.context.data["anatomy"].templates["publish"] xgen_filename = StringTemplate(templates["file"]).format(template_data) - name = instance.data["xgenPalette"].replace(":", "__").replace("|", "") + name = instance.data["xgmPalette"].replace(":", "__").replace("|", "") xgen_filename = xgen_filename.replace(".xgen", "__" + name + ".xgen") # Export xgen palette files. xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") xgenm.exportPalette( - instance.data["xgenPalette"].replace("|", ""), xgen_path + instance.data["xgmPalette"].replace("|", ""), xgen_path ) self.log.info("Extracted to {}".format(xgen_path)) @@ -132,12 +132,12 @@ class ExtractXgenCache(publish.Extractor): # Collect all files under palette root as resources. data_path = xgenm.getAttr( - "xgDataPath", instance.data["xgenPalette"].replace("|", "") + "xgDataPath", instance.data["xgmPalette"].replace("|", "") ).split(os.pathsep)[0] data_path = data_path.replace( "${PROJECT}", xgenm.getAttr( - "xgProjectPath", instance.data["xgenPalette"].replace("|", "") + "xgProjectPath", instance.data["xgmPalette"].replace("|", "") ) ) transfers = [] From 9ed90e67f8a2fbdc62d3c2a15cc89ed79111301d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 8 Jan 2023 18:03:34 +0000 Subject: [PATCH 029/356] Code cosmetics --- openpype/hosts/maya/plugins/inventory/connect_geometry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/connect_geometry.py b/openpype/hosts/maya/plugins/inventory/connect_geometry.py index 375ad29ef0..9fe4f9e195 100644 --- a/openpype/hosts/maya/plugins/inventory/connect_geometry.py +++ b/openpype/hosts/maya/plugins/inventory/connect_geometry.py @@ -52,7 +52,7 @@ class ConnectGeometry(InventoryAction): self.display_warning(message) return - source_container = source_containers[0] + source_object = source_containers[0]["objectName"] # Collect matching geometry transforms based cbId attribute. target_containers = [] @@ -62,7 +62,7 @@ class ConnectGeometry(InventoryAction): target_containers.extend(containers) - source_data = self.get_container_data(source_container["objectName"]) + source_data = self.get_container_data(source_object) matches = [] node_types = [] for target_container in target_containers: @@ -80,7 +80,7 @@ class ConnectGeometry(InventoryAction): self.display_warning("No matching geometries found.") return - message = "Linking geometries:\n\n" + message = "Connecting geometries:\n\n" for match in matches: message += "{} > {}\n".format(match[0], match[1]) From 0382ef29f5e9b02186d2c207063a3a15b66b3397 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sun, 8 Jan 2023 18:04:01 +0000 Subject: [PATCH 030/356] Connect and update xgen animation. --- openpype/hosts/maya/api/plugin.py | 31 ++++ .../maya/plugins/inventory/connect_xgen.py | 168 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 openpype/hosts/maya/plugins/inventory/connect_xgen.py diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 82df85a8be..f11757b2ab 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -300,6 +300,37 @@ class ReferenceLoader(Loader): str(representation["_id"]), type="string") + # Update any xgen containers. + compound_name = "xgenContainers" + object = "SplinePrimitive" + if cmds.objExists("{}.{}".format(node, compound_name)): + import xgenm + container_amount = cmds.getAttr( + "{}.{}".format(node, compound_name), size=True + ) + # loop through all compound children + for i in range(container_amount): + attr = "{}.{}[{}].container".format(node, compound_name, i) + objectset = cmds.listConnections(attr)[0] + reference_node = cmds.sets(objectset, query=True)[0] + palettes = cmds.ls( + cmds.referenceQuery(reference_node, nodes=True), + type="xgmPalette" + ) + for palette in palettes: + for description in xgenm.descriptions(palette): + xgenm.setAttr( + "cacheFileName", + path.replace("\\", "/"), + palette, + description, + object + ) + + # Refresh UI and viewport. + de = xgenm.xgGlobal.DescriptionEditor + de.refresh("Full") + def remove(self, container): """Remove an existing `container` from Maya scene diff --git a/openpype/hosts/maya/plugins/inventory/connect_xgen.py b/openpype/hosts/maya/plugins/inventory/connect_xgen.py new file mode 100644 index 0000000000..933a1b4025 --- /dev/null +++ b/openpype/hosts/maya/plugins/inventory/connect_xgen.py @@ -0,0 +1,168 @@ +from maya import cmds +import xgenm + +from openpype.pipeline import ( + InventoryAction, get_representation_context, get_representation_path +) + + +class ConnectXgen(InventoryAction): + """Connect Xgen with an animation or pointcache. + """ + + label = "Connect Xgen" + 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 = {} + for container in containers: + family = get_representation_context( + container["representation"] + )["subset"]["data"]["family"] + try: + containers_by_family[family].append(container) + except KeyError: + containers_by_family[family] = [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_object = source_container["objectName"] + + # Validate source representation is an alembic. + source_path = get_representation_path( + get_representation_context( + source_container["representation"] + )["representation"] + ).replace("\\", "/") + message = "Animation container \"{}\" is not an alembic:\n{}".format( + source_container["namespace"], source_path + ) + if not source_path.endswith(".abc"): + self.display_warning(message) + return + + # Target containers. + target_containers = [] + for family, containers in containers_by_family.items(): + if family in ["animation", "pointcache"]: + continue + + target_containers.extend(containers) + + # Inform user of connections from source representation to target + # descriptions. + descriptions_data = [] + connections_msg = "" + for target_container in target_containers: + reference_node = cmds.sets( + target_container["objectName"], query=True + )[0] + palettes = cmds.ls( + cmds.referenceQuery(reference_node, nodes=True), + type="xgmPalette" + ) + for palette in palettes: + for description in xgenm.descriptions(palette): + descriptions_data.append([palette, description]) + connections_msg += "\n{}/{}".format(palette, description) + + message = "Connecting \"{}\" to:\n".format( + source_container["namespace"] + ) + message += connections_msg + choice = self.display_warning(message, show_cancel=True) + if choice is False: + return + + # Recreate "xgenContainers" attribute to reset. + compound_name = "xgenContainers" + attr = "{}.{}".format(source_object, compound_name) + if cmds.objExists(attr): + cmds.deleteAttr(attr) + + cmds.addAttr( + source_object, + longName=compound_name, + attributeType="compound", + numberOfChildren=1, + multi=True + ) + + # Connect target containers. + for target_container in target_containers: + cmds.addAttr( + source_object, + longName="container", + attributeType="message", + parent=compound_name + ) + index = target_containers.index(target_container) + cmds.connectAttr( + target_container["objectName"] + ".message", + source_object + ".{}[{}].container".format( + compound_name, index + ) + ) + + # Setup cache on Xgen + object = "SplinePrimitive" + for palette, description in descriptions_data: + xgenm.setAttr("useCache", "true", palette, description, object) + xgenm.setAttr("liveMode", "false", palette, description, object) + xgenm.setAttr( + "cacheFileName", source_path, palette, description, object + ) + + # Refresh UI and viewport. + de = xgenm.xgGlobal.DescriptionEditor + de.refresh("Full") + + def display_warning(self, message, show_cancel=False): + """Show feedback to user. + + Returns: + bool + """ + + from Qt 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 From 0dd54b0a5c767a02bcb998e6740b235695f996c6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 9 Jan 2023 15:40:50 +0000 Subject: [PATCH 031/356] Validate workfile has been saved. --- openpype/hosts/maya/plugins/load/load_xgen.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 23a22127e9..565d1306b4 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -4,6 +4,8 @@ import shutil import maya.cmds as cmds import xgenm +from Qt import QtWidgets + import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.lib import ( maintained_selection, get_container_members, attribute_values @@ -71,6 +73,15 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): return destination def process_reference(self, context, name, namespace, options): + # Validate workfile has a path. + if current_file() is None: + QtWidgets.QMessageBox.warning( + None, + "", + "Current workfile has not been saved." + ) + return + maya_filepath = self.prepare_root_value( self.fname, context["project"]["name"] ) From 3021fea4762ec84e53091f1cb954883be6a53634 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 11 Jan 2023 10:04:17 +0000 Subject: [PATCH 032/356] Increment xgen files with workfile. --- openpype/hosts/maya/startup/userSetup.py | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index b64ed93000..5cd27500dc 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,8 +1,10 @@ import os +import shutil from openpype.settings import get_project_settings from openpype.pipeline import install_host -from openpype.hosts.maya.api import MayaHost +from openpype.hosts.maya.api import MayaHost, current_file +from openpype.lib import register_event_callback from maya import cmds @@ -21,6 +23,44 @@ if bool(int(os.environ.get(key, "0"))): lowestPriority=True ) + +# Setup Xgen save callback. +def xgen_on_save(): + """Increments the xgen side car files .xgen and .xgd + + Only works when incrementing to the same directory. + """ + + file_path = current_file() + current_dir = os.path.dirname(file_path) + basename = os.path.basename(file_path).split(".")[0] + attrs = ["xgFileName", "xgBaseFile"] + for palette in cmds.ls(type="xgmPalette"): + for attr in attrs: + source = os.path.join( + current_dir, cmds.getAttr(palette + "." + attr) + ) + if not os.path.exists(source): + continue + + destination_basename = "{}__{}{}".format( + basename, + palette.replace(":", "_"), + os.path.splitext(source)[1] + ) + destination = os.path.join(current_dir, destination_basename) + + if source == destination: + continue + + shutil.copy(source, destination) + cmds.setAttr( + palette + "." + attr, destination_basename, type="string" + ) + + +register_event_callback("save", xgen_on_save) + # Build a shelf. settings = get_project_settings(os.environ['AVALON_PROJECT']) shelf_preset = settings['maya'].get('project_shelf') From 4dc155d95fb89ccea3310721afcd534f91e07a10 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 12 Jan 2023 15:51:51 +0000 Subject: [PATCH 033/356] Support for rendering Opening the workfile in its published location will load the xgen correctly. --- .../plugins/publish/extract_workfile_xgen.py | 131 ++++++++++++++++++ .../plugins/publish/reset_xgen_attributes.py | 21 +++ 2 files changed, 152 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py create mode 100644 openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py new file mode 100644 index 0000000000..e12a870eaf --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -0,0 +1,131 @@ +import os +import shutil + +from maya import cmds + +import pyblish.api +from openpype.hosts.maya.api import current_file +from openpype.pipeline import publish + + +class ExtractWorkfileXgen(publish.Extractor): + """Extract Workfile Xgen.""" + + # Offset to run before workfile scene save. + order = pyblish.api.ExtractorOrder - 0.499 + label = "Extract Workfile Xgen" + families = ["workfile"] + hosts = ["maya"] + + def process(self, instance): + # Validate to extract only when we are publishing a renderlayer as + # well. + renderlayer = False + for i in instance.context: + is_renderlayer = ( + "renderlayer" in i.data.get("families", []) or + i.data["family"] == "renderlayer" + ) + if is_renderlayer and i.data["publish"]: + renderlayer = True + break + + if not renderlayer: + self.log.debug( + "No publishable renderlayers found in context. Abort Xgen" + " extraction." + ) + return + + # Collect Xgen and Delta files. + xgen_files = [] + sources = [] + file_path = current_file() + current_dir = os.path.dirname(file_path) + attrs = ["xgFileName", "xgBaseFile"] + for palette in cmds.ls(type="xgmPalette"): + for attr in attrs: + source = os.path.join( + current_dir, cmds.getAttr(palette + "." + attr) + ) + if not os.path.exists(source): + continue + + ext = os.path.splitext(source)[1] + if ext == ".xgen": + xgen_files.append(source) + if ext == ".xgd": + sources.append(source) + + # Copy .xgen file to temporary location and modify. + staging_dir = self.staging_dir(instance) + for source in xgen_files: + destination = os.path.join(staging_dir, os.path.basename(source)) + shutil.copy(source, destination) + + lines = [] + with open(destination, "r") as f: + for line in [line.rstrip() for line in f]: + if line.startswith("\txgProjectPath"): + line = "\txgProjectPath\t\t{}/".format( + instance.data["resourcesDir"].replace("\\", "/") + ) + + lines.append(line) + + with open(destination, "w") as f: + f.write("\n".join(lines)) + + sources.append(destination) + + # Add resource files to workfile instance. + transfers = [] + for source in sources: + basename = os.path.basename(source) + destination = os.path.join(instance.data["resourcesDir"], basename) + transfers.append((source, destination)) + + import xgenm + for palette in cmds.ls(type="xgmPalette"): + relative_data_path = xgenm.getAttr( + "xgDataPath", palette.replace("|", "") + ).split(os.pathsep)[0] + absolute_data_path = relative_data_path.replace( + "${PROJECT}", + xgenm.getAttr("xgProjectPath", palette.replace("|", "")) + ) + + for root, _, files in os.walk(absolute_data_path): + for file in files: + source = os.path.join(root, file).replace("\\", "/") + destination = os.path.join( + instance.data["resourcesDir"], + relative_data_path.replace("${PROJECT}", ""), + source.replace(absolute_data_path, "")[1:] + ) + transfers.append((source, destination.replace("\\", "/"))) + + for source, destination in transfers: + self.log.debug("Transfer: {} > {}".format(source, destination)) + + instance.data["transfers"] = transfers + + # Set palette attributes in preparation for workfile publish. + attrs = ["xgFileName", "xgBaseFile"] + data = {} + for palette in cmds.ls(type="xgmPalette"): + for attr in attrs: + value = cmds.getAttr(palette + "." + attr) + if value: + new_value = "resources/{}".format(value) + node_attr = "{}.{}".format(palette, attr) + self.log.info( + "Setting \"{}\" on \"{}\"".format(new_value, node_attr) + ) + cmds.setAttr(node_attr, new_value, type="string") + try: + data[palette][attr] = value + except KeyError: + data[palette] = {attr: value} + + instance.data["xgenAttributes"] = data diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py new file mode 100644 index 0000000000..9397d479d2 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -0,0 +1,21 @@ +from maya import cmds + +import pyblish.api + + +class ResetXgenAttributes(pyblish.api.InstancePlugin): + """Reset Xgen attributes.""" + + label = "Reset Xgen Attributes." + # Offset to run after global integrator. + order = pyblish.api.IntegratorOrder + 1.0 + families = ["workfile"] + + def process(self, instance): + for palette, data in instance.data.get("xgenAttributes", []).items(): + for attr, value in data.items(): + node_attr = "{}.{}".format(palette, attr) + self.log.info( + "Setting \"{}\" on \"{}\"".format(value, node_attr) + ) + cmds.setAttr(node_attr, value, type="string") From 05a8ef36f7472143bffa36837e3cf56c9593733e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 13 Jan 2023 12:59:25 +0000 Subject: [PATCH 034/356] Xgen documentation --- website/docs/artist_hosts_maya.md | 17 +++++ website/docs/artist_hosts_maya_xgen.md | 94 ++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 14 deletions(-) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index fb1af765d2..ced6887d08 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -601,3 +601,20 @@ about customizing review process refer to [admin section](project_settings/setti If you don't move `modelMain` into `reviewMain`, review will be generated but it will be published as separate entity. + + +## Inventory Actions + +### Connect Geometry + +This action will connect geometries between containers. + +#### Usage + +Select 1 container of type `animation` or `pointcache`, then 1+ container of any type. + +#### Details + +The action searches the selected containers for 1 animation container of type `animation` or `pointcache`. This animation container will be connected to the rest of the selected containers. Matching geometries between containers is done by comparing the attribute `cbId`. + +The connection between geometries is done with a live blendshape. diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index 8b0174a29f..629b600458 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -4,26 +4,92 @@ title: Xgen for Maya sidebar_label: Xgen --- -## Working with Xgen in OpenPype +## Setup -OpenPype support publishing and loading of Xgen interactive grooms. You can publish -them as mayaAscii files with scalps that can be loaded into another maya scene, or as -alembic caches. +### Settings -### Publishing Xgen Grooms +Go to project settings > Maya > enable "Open Workfile Post Initialization"; -To prepare xgen for publishing just select all the descriptions that should be published together and the create Xgen Subset in the scene using - **OpenPype menu** → **Create**... and select **Xgen Interactive**. Leave Use selection checked. +`project_settings/maya/open_workfile_post_initialization` -For actual publishing of your groom to go **OpenPype → Publish** and then press ▶ to publish. This will export `.ma` file containing your grooms with any geometries they are attached to and also a baked cache in `.abc` format +This is due to two errors occurring when opening workfile contaiing referenced xgen nodes on launch of Maya; +- ``Critical``: Duplicate collection errors on launching workfile. This is because Maya first imports Xgen when referencing in external Maya files, then imports Xgen again when the reference edits are applied. +``` +Importing XGen Collections... +# Error: XGen: Failed to find description ball_xgenMain_01_:parent in collection ball_xgenMain_01_:collection. Abort applying delta: P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_sh040_Lighting_v001__ball_xgenMain_01___collection.xgen # +# Error: XGen: Tried to import a duplicate collection, ball_xgenMain_02_:collection, from file P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_sh040_Lighting_v001__ball_xgenMain_02___collection.xgen. Aborting import. # +``` +- ``Non-critical``: Errors on opening workfile and failed opening of published xgen. This is because Maya imports Xgen when referencing in external Maya files but the reference edits that ensure the location of the Xgen files are correct, has not been applied yet. +``` +Importing XGen Collections... +# Error: XGen: Failed to open file: P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_ball_xgenMain_v035__ball_rigMain_01___collection.xgen # +# Error: XGen: Failed to import collection from file P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_ball_xgenMain_v035__ball_rigMain_01___collection.xgen # +``` -:::tip adding more descriptions -You can add multiple xgen description into the subset you are about to publish, simply by -adding them to the maya set that was created for you. Please make sure that only xgen description nodes are present inside of the set and not the scalp geometry. -::: +### Workfile Incremental Save -### Loading Xgen +When you increment the Maya workfile to a new version, all `.xgen` and `.xgd` files referenced by the Xgen collection in the workspace is incremented as well. -You can use published xgens by loading them using OpenPype Publisher. You can choose to reference or import xgen. We don't have any automatic mesh linking at the moment and it is expected, that groom is published with a scalp, that can then be manually attached to your animated mesh for example. +## Create -The alembic representation can be loaded too and it contains the groom converted to curves. Keep in mind that the density of the alembic directly depends on your viewport xgen density at the point of export. +Create an Xgen instance to publish. This needs to contain 1 Xgen collection only, but validation will check for this: + +`OpenPype > Create... > Xgen` + +You can create multiple Xgen instances if you have multiple collections to publish. + +### 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: + +- Maya file (`.ma`) - this contains the geometry and the connections to the Xgen collection and descriptions. +- Xgen file (`.xgen`) - this contains the Xgen collection and description. +- Resource files (`.ptx`, `.xuv`) - this contains Xgen side car files used in the collection and descriptions. + +## Load + +Open the Loader tool, `OpenPype > Loader...`, and navigate to the published Xgen version. On right-click you'll get the option `Reference Xgen (ma)` +When loading an Xgen version the following happens: + +- References in the Maya file. +- Copies the Xgen file (`.xgen`) to the current workspace. +- Modifies the Xgen file copy to load the current workspace first then the published Xgen collection. +- Makes a custom attribute on the Xgen collection, `float_ignore`, which can be seen under the `Expressions` tab of the `Xgen` UI. This is done to initialize the Xgen delta file workflow. +- Setup an Xgen delta file (`.xgd`) to store any workspace changes of the published Xgen version. + +When the loading is done, Xgen collection will be in the Xgen delta file workflow which means any changes done in the Maya workfile will be stored in the current workspace. The published Xgen collection will remain intact, even if the user assigns maps to any attributes or otherwise modifies any attribute. + +### Updating + +When there are changes to the Xgen version, the user will be notified when opening the workfile or publishing. Since the Xgen is referenced, it follows the standard Maya referencing system and overrides. + +For example publishing `xgenMain` version 1 with the attribute `renderer` set to `None`, then version 2 has `renderer` set to `Arnold Renderer`. When updating from version 1 to 2, the `renderer` attribute will be updated to `Arnold Renderer` unless there is an override. + +### Connect Patches + +When loading in an Xgen version, it does not have any connections to anything in the workfile, so its static in the position it was published in. Use the [Connect Geometry](artist_hosts_maya#connect-geometry) action to connect Xgen to any matching loaded animated geometry. + +### Connect Guides + +Along with patches you can also connect the Xgen guides to an Alembic cache. + +#### Usage + +Select 1 animation container, of family `animation` or `pointcache`, then the Xgen containers to connect to. Right-click > `Actions` > `Connect Xgen`. + +***Note: Only alembic (`.abc`) representations are allowed.*** + +#### Details + +Connecting the guide will make Xgen use the Alembic directly, setting the attributes under `Guide Animation`, so the Alembic needs to contain the same amount of curves as guides in the Xgen. + +The animation container gets connected with the Xgen container, so if the animation container is updated so will the Xgen container's attribute. + +## Rendering + +To render with Xgen, follow the [Rendering With OpenPype](artist_hosts_maya#rendering-with-openpype) guide. + +### Details + +When submitting a workfile with Xgen, all Xgen related files will be collected and published as the workfiles resources. This means the published workfile is no longer referencing the workspace Xgen files. From 27fc690209493147ef965ce0c364465fe5acf223 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 16 Jan 2023 10:04:55 +0000 Subject: [PATCH 035/356] Update website/docs/artist_hosts_maya_xgen.md Co-authored-by: Roy Nieterau --- website/docs/artist_hosts_maya_xgen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index 629b600458..cde56888d4 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -12,7 +12,7 @@ Go to project settings > Maya > enable "Open Workfile Post Initialization"; `project_settings/maya/open_workfile_post_initialization` -This is due to two errors occurring when opening workfile contaiing referenced xgen nodes on launch of Maya; +This is due to two errors occurring when opening workfile containing referenced xgen nodes on launch of Maya, specifically: - ``Critical``: Duplicate collection errors on launching workfile. This is because Maya first imports Xgen when referencing in external Maya files, then imports Xgen again when the reference edits are applied. ``` From 1b1cd149065a561741b5783823aceb88a82e5f62 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 16 Jan 2023 10:12:32 +0000 Subject: [PATCH 036/356] Update website/docs/artist_hosts_maya_xgen.md Co-authored-by: Roy Nieterau --- website/docs/artist_hosts_maya_xgen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index cde56888d4..e0c008929c 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -33,7 +33,7 @@ When you increment the Maya workfile to a new version, all `.xgen` and `.xgd` fi ## Create -Create an Xgen instance to publish. This needs to contain 1 Xgen collection only, but validation will check for this: +Create an Xgen instance to publish. This needs to contain only **one Xgen collection**. `OpenPype > Create... > Xgen` From 86012906f753e48dd1015516c0d795ca3ccd556f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 16 Jan 2023 10:13:30 +0000 Subject: [PATCH 037/356] Update website/docs/artist_hosts_maya_xgen.md Co-authored-by: Roy Nieterau --- website/docs/artist_hosts_maya_xgen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index e0c008929c..3f47428abe 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -64,7 +64,7 @@ When the loading is done, Xgen collection will be in the Xgen delta file workflo When there are changes to the Xgen version, the user will be notified when opening the workfile or publishing. Since the Xgen is referenced, it follows the standard Maya referencing system and overrides. -For example publishing `xgenMain` version 1 with the attribute `renderer` set to `None`, then version 2 has `renderer` set to `Arnold Renderer`. When updating from version 1 to 2, the `renderer` attribute will be updated to `Arnold Renderer` unless there is an override. +For example publishing `xgenMain` version 1 with the attribute `renderer` set to `None`, then version 2 has `renderer` set to `Arnold Renderer`. When updating from version 1 to 2, the `renderer` attribute will be updated to `Arnold Renderer` unless there is a local override. ### Connect Patches From b90e9b69c60ff03b165985350dd74b645793f824 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Jan 2023 21:44:24 +0100 Subject: [PATCH 038/356] added more specific functions for current context into host integration --- openpype/host/host.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 94416bb39a..56d01c667a 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -100,6 +100,30 @@ class HostBase(object): pass + def get_current_project_name(self): + """ + Returns: + Union[str, None]: Current project name. + """ + + return os.environ.get("AVALON_PROJECT") + + def get_current_asset_name(self): + """ + Returns: + Union[str, None]: Current asset name. + """ + + return os.environ.get("AVALON_ASSET") + + def get_current_task_name(self): + """ + Returns: + Union[str, None]: Current task name. + """ + + return os.environ.get("AVALON_ASSET") + def get_current_context(self): """Get current context information. @@ -111,19 +135,14 @@ class HostBase(object): Default implementation returns values from 'legacy_io.Session'. Returns: - dict: Context with 3 keys 'project_name', 'asset_name' and - 'task_name'. All of them can be 'None'. + Dict[str, Union[str, None]]: Context with 3 keys 'project_name', + 'asset_name' and 'task_name'. All of them can be 'None'. """ - from openpype.pipeline import legacy_io - - if legacy_io.is_installed(): - legacy_io.install() - return { - "project_name": legacy_io.Session["AVALON_PROJECT"], - "asset_name": legacy_io.Session["AVALON_ASSET"], - "task_name": legacy_io.Session["AVALON_TASK"] + "project_name": self.get_current_project_name(), + "asset_name": self.get_current_asset_name(), + "task_name": self.get_current_task_name() } def get_context_title(self): From 2c35bda38146d35f84c3468abf64710c5fb02ba4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Jan 2023 21:45:09 +0100 Subject: [PATCH 039/356] added functions for global access to current context --- openpype/pipeline/context_tools.py | 44 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index da0ce8ecf4..50ce827a39 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -306,6 +306,42 @@ def debug_host(): return host +def get_global_context(): + return { + "project_name": os.environ.get("AVALON_PROJECT"), + "asset_name": os.environ.get("AVALON_ASSET"), + "task_name": os.environ.get("AVALON_TASK"), + } + + +def get_current_context(): + host = registered_host() + if host is not None and hasattr(host, "get_current_context"): + return host.get_current_context() + return get_global_context() + + +def get_current_project_name(): + host = registered_host() + if host is not None and hasattr(host, "get_current_project_name"): + return host.get_current_project_name() + return get_global_context()["project_name"] + + +def get_current_asset_name(): + host = registered_host() + if host is not None and hasattr(host, "get_current_asset_name"): + return host.get_current_asset_name() + return get_global_context()["asset_name"] + + +def get_current_task_name(): + host = registered_host() + if host is not None and hasattr(host, "get_current_task_name"): + return host.get_current_task_name() + return get_global_context()["task_name"] + + def get_current_project(fields=None): """Helper function to get project document based on global Session. @@ -316,7 +352,7 @@ def get_current_project(fields=None): None: Project is not set. """ - project_name = legacy_io.active_project() + project_name = get_current_project_name() return get_project(project_name, fields=fields) @@ -341,12 +377,12 @@ def get_current_project_asset(asset_name=None, asset_id=None, fields=None): None: Asset is not set or not exist. """ - project_name = legacy_io.active_project() + project_name = get_current_project_name() if asset_id: return get_asset_by_id(project_name, asset_id, fields=fields) if not asset_name: - asset_name = legacy_io.Session.get("AVALON_ASSET") + asset_name = get_current_asset_name() # Skip if is not set even on context if not asset_name: return None @@ -363,7 +399,7 @@ def is_representation_from_latest(representation): bool: Whether the representation is of latest version. """ - project_name = legacy_io.active_project() + project_name = get_current_project_name() return version_is_latest(project_name, representation["parent"]) From b75768249c422665fdd380383424326829e00748 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Jan 2023 21:45:17 +0100 Subject: [PATCH 040/356] added function to get current host name --- openpype/pipeline/context_tools.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 50ce827a39..06538dd91f 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -306,6 +306,22 @@ def debug_host(): return host +def get_current_host_name(): + """Current host name. + + Function is based on currently registered host integration or environment + variant 'AVALON_APP'. + + Returns: + Union[str, None]: Name of host integration in current process or None. + """ + + host = registered_host() + if host is not None and hasattr(host, "name"): + return host.name + return os.environ.get("AVALON_APP") + + def get_global_context(): return { "project_name": os.environ.get("AVALON_PROJECT"), From 48937de52ce9092d5718e9cb549764887be6c5c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 16 Jan 2023 22:07:25 +0100 Subject: [PATCH 041/356] added missing import --- openpype/host/host.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/host/host.py b/openpype/host/host.py index 56d01c667a..28d0a21b34 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -1,3 +1,4 @@ +import os import logging import contextlib from abc import ABCMeta, abstractproperty From 9f749edd8a13be373d8d76e15ac8c09729adaa60 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 09:58:23 +0000 Subject: [PATCH 042/356] Fix When there is no xgenAttributes --- openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py index 9397d479d2..0f763613c9 100644 --- a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -12,7 +12,7 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): families = ["workfile"] def process(self, instance): - for palette, data in instance.data.get("xgenAttributes", []).items(): + for palette, data in instance.data.get("xgenAttributes", {}).items(): for attr, value in data.items(): node_attr = "{}.{}".format(palette, attr) self.log.info( From ff6fe13e2a6049e61b829905f2e3bb9f1b0b4cc5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 16:05:58 +0000 Subject: [PATCH 043/356] Code cosmetics --- openpype/hooks/pre_add_last_workfile_arg.py | 6 ++---- openpype/hosts/maya/startup/userSetup.py | 9 +++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index ffb116d2e4..ce59c0b706 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -43,11 +43,9 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): return # Determine whether to open workfile post initialization. - if self.data["app"].host_name == "maya": - project_name = self.data["project_name"] - settings = get_project_settings(project_name) + if self.host_name == "maya": key = "open_workfile_post_initialization" - if settings["maya"][key]: + if self.data["project_settings"]["maya"][key]: self.log.debug("Opening workfile post initialization.") self.data["env"]["OPENPYPE_" + key.upper()] = "1" return diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 5cd27500dc..87025637b6 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,5 +1,6 @@ import os import shutil +from functools import partial from openpype.settings import get_project_settings from openpype.pipeline import install_host @@ -18,8 +19,12 @@ print("Starting OpenPype usersetup...") key = "OPENPYPE_OPEN_WORKFILE_POST_INITIALIZATION" if bool(int(os.environ.get(key, "0"))): cmds.evalDeferred( - "cmds.file(os.environ[\"AVALON_LAST_WORKFILE\"], open=True," - " force=True)", + partial( + cmds.file, + os.environ["AVALON_LAST_WORKFILE"], + open=True, + force=True + ), lowestPriority=True ) From 40f8bc25f5adcfb2b70ce219f555b2f09a74e079 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 16:06:16 +0000 Subject: [PATCH 044/356] Remove Xgen incremental save feature --- openpype/hosts/maya/startup/userSetup.py | 37 ------------------------ website/docs/artist_hosts_maya_xgen.md | 4 --- 2 files changed, 41 deletions(-) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 87025637b6..a2f6405980 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -29,43 +29,6 @@ if bool(int(os.environ.get(key, "0"))): ) -# Setup Xgen save callback. -def xgen_on_save(): - """Increments the xgen side car files .xgen and .xgd - - Only works when incrementing to the same directory. - """ - - file_path = current_file() - current_dir = os.path.dirname(file_path) - basename = os.path.basename(file_path).split(".")[0] - attrs = ["xgFileName", "xgBaseFile"] - for palette in cmds.ls(type="xgmPalette"): - for attr in attrs: - source = os.path.join( - current_dir, cmds.getAttr(palette + "." + attr) - ) - if not os.path.exists(source): - continue - - destination_basename = "{}__{}{}".format( - basename, - palette.replace(":", "_"), - os.path.splitext(source)[1] - ) - destination = os.path.join(current_dir, destination_basename) - - if source == destination: - continue - - shutil.copy(source, destination) - cmds.setAttr( - palette + "." + attr, destination_basename, type="string" - ) - - -register_event_callback("save", xgen_on_save) - # Build a shelf. settings = get_project_settings(os.environ['AVALON_PROJECT']) shelf_preset = settings['maya'].get('project_shelf') diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index 3f47428abe..fc75959f2b 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -27,10 +27,6 @@ Importing XGen Collections... # Error: XGen: Failed to import collection from file P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_ball_xgenMain_v035__ball_rigMain_01___collection.xgen # ``` -### Workfile Incremental Save - -When you increment the Maya workfile to a new version, all `.xgen` and `.xgd` files referenced by the Xgen collection in the workspace is incremented as well. - ## Create Create an Xgen instance to publish. This needs to contain only **one Xgen collection**. From 59b1caacd2f4e662c44d57c23a8deca2d3fe07fb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 16:06:39 +0000 Subject: [PATCH 045/356] Account for not using the published workfile. --- .../hosts/maya/plugins/publish/extract_workfile_xgen.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index e12a870eaf..13a04615eb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -37,6 +37,13 @@ class ExtractWorkfileXgen(publish.Extractor): ) return + publish_settings = instance.context["deadline"]["publish"] + if not publish_settings["MayaSubmitDeadline"]["use_published"]: + self.log.debug( + "Not using the published workfile. Abort Xgen extraction." + ) + return + # Collect Xgen and Delta files. xgen_files = [] sources = [] From 1c1a41ba5efd0e883d8985832dd7371554597384 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 16:10:39 +0000 Subject: [PATCH 046/356] Hound --- openpype/hooks/pre_add_last_workfile_arg.py | 1 - openpype/hosts/maya/startup/userSetup.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index ce59c0b706..1c8746c559 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -1,7 +1,6 @@ import os from openpype.lib import PreLaunchHook -from openpype.settings import get_project_settings class AddLastWorkfileToLaunchArgs(PreLaunchHook): diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index a2f6405980..bfa5e6e60d 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,11 +1,9 @@ import os -import shutil from functools import partial from openpype.settings import get_project_settings from openpype.pipeline import install_host -from openpype.hosts.maya.api import MayaHost, current_file -from openpype.lib import register_event_callback +from openpype.hosts.maya.api import MayaHost from maya import cmds From 5f709b08929e81f5e7c48a077320fdabee08f946 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 17 Jan 2023 16:22:12 +0000 Subject: [PATCH 047/356] Code cosmetics --- openpype/hosts/maya/api/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index f11757b2ab..b7adf6edfc 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -302,7 +302,6 @@ class ReferenceLoader(Loader): # Update any xgen containers. compound_name = "xgenContainers" - object = "SplinePrimitive" if cmds.objExists("{}.{}".format(node, compound_name)): import xgenm container_amount = cmds.getAttr( @@ -324,7 +323,7 @@ class ReferenceLoader(Loader): path.replace("\\", "/"), palette, description, - object + "SplinePrimitive" ) # Refresh UI and viewport. From cdc0a80846f465d0f12ee6bfa83d28313676d327 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 11:21:43 +0000 Subject: [PATCH 048/356] Fix When there is no Deadline setting avaliable. --- .../maya/plugins/publish/extract_workfile_xgen.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index 13a04615eb..d37a03d1f6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -37,12 +37,14 @@ class ExtractWorkfileXgen(publish.Extractor): ) return - publish_settings = instance.context["deadline"]["publish"] - if not publish_settings["MayaSubmitDeadline"]["use_published"]: - self.log.debug( - "Not using the published workfile. Abort Xgen extraction." - ) - return + deadline_settings = instance.context.get("deadline") + if deadline_settings: + publish_settings = deadline_settings["publish"] + if not publish_settings["MayaSubmitDeadline"]["use_published"]: + self.log.debug( + "Not using the published workfile. Abort Xgen extraction." + ) + return # Collect Xgen and Delta files. xgen_files = [] From ff2516e219b5eff574e2ba12c13aff1d9033814c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 18 Jan 2023 16:47:53 +0100 Subject: [PATCH 049/356] Feature: Keep synced hero representations up-to-date. Fix #4331 --- .../plugins/publish/integrate_hero_version.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 5f4d284740..c162c83976 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -386,6 +386,25 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): repre["_id"] = old_repre["_id"] update_data = prepare_representation_update_data( old_repre, repre) + + # Keep previously synchronized sites up-to-date + # by comparing old and new sites and adding old sites + # if missing in new ones + old_repre_files_sites = [ + f.get("sites", []) for f in old_repre.get("files", []) + ] + for i, file in enumerate(repre.get("files", [])): + repre_sites_names = { + s["name"] for s in file.get("sites", []) + } + for site in old_repre_files_sites[i]: + if site["name"] not in repre_sites_names: + # Pop the date to tag for sync + site.pop("created_dt") + file["sites"].append(site) + + update_data["files"][i] = file + op_session.update_entity( project_name, old_repre["type"], From 5476217f6ed17b44cf4fdd02849429ef10774c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 18 Jan 2023 16:59:53 +0100 Subject: [PATCH 050/356] linting --- openpype/plugins/publish/integrate_hero_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index c162c83976..427256c137 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -404,7 +404,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): file["sites"].append(site) update_data["files"][i] = file - + op_session.update_entity( project_name, old_repre["type"], From cf4df006bdf67fc935db649bbb27db8beef4f6af Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 09:12:33 +0000 Subject: [PATCH 051/356] Clean up redundant lib code. --- openpype/hosts/maya/api/lib.py | 31 ------------------- .../plugins/publish/submit_maya_muster.py | 7 ++++- .../plugins/publish/validate_maya_units.py | 7 ++--- 3 files changed, 8 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index dd5da275e8..71d890f46b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -254,11 +254,6 @@ def read(node): return data -def _get_mel_global(name): - """Return the value of a mel global variable""" - return mel.eval("$%s = $%s;" % (name, name)) - - def matrix_equals(a, b, tolerance=1e-10): """ Compares two matrices with an imperfection tolerance @@ -691,11 +686,6 @@ class delete_after(object): cmds.delete(self._nodes) -def get_renderer(layer): - with renderlayer(layer): - return cmds.getAttr("defaultRenderGlobals.currentRenderer") - - def get_current_renderlayer(): return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) @@ -1440,27 +1430,6 @@ def set_id(node, unique_id, overwrite=False): cmds.setAttr(attr, unique_id, type="string") -# endregion ID -def get_reference_node(path): - """ - Get the reference node when the path is found being used in a reference - Args: - path (str): the file path to check - - Returns: - node (str): name of the reference node in question - """ - try: - node = cmds.file(path, query=True, referenceNode=True) - except RuntimeError: - log.debug('File is not referenced : "{}"'.format(path)) - return - - reference_path = cmds.referenceQuery(path, filename=True) - if os.path.normpath(path) == os.path.normpath(reference_path): - return node - - def set_attribute(attribute, value, node): """Adjust attributes based on the value from the attribute data diff --git a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py index 1a6463fb9d..8ae3e5124b 100644 --- a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py +++ b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py @@ -52,6 +52,11 @@ def _get_script(): return module_path +def get_renderer(layer): + with lib.renderlayer(layer): + return cmds.getAttr("defaultRenderGlobals.currentRenderer") + + def get_renderer_variables(renderlayer=None): """Retrieve the extension which has been set in the VRay settings @@ -66,7 +71,7 @@ def get_renderer_variables(renderlayer=None): dict """ - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) + renderer = get_renderer(renderlayer or lib.get_current_renderlayer()) render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) padding = cmds.getAttr("{}.{}".format(render_attrs["node"], diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index 5698d795ff..e6fabb1712 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -11,10 +11,6 @@ from openpype.pipeline.publish import ( ) -def float_round(num, places=0, direction=ceil): - return direction(num * (10**places)) / float(10**places) - - class ValidateMayaUnits(pyblish.api.ContextPlugin): """Check if the Maya units are set correct""" @@ -36,6 +32,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): # Collected units linearunits = context.data.get('linearUnits') angularunits = context.data.get('angularUnits') + # TODO(antirotor): This is hack as for framerates having multiple # decimal places. FTrack is ceiling decimal values on # fps to two decimal places but Maya 2019+ is reporting those fps @@ -43,7 +40,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): # rounding, we have to round those numbers coming from Maya. # NOTE: this must be revisited yet again as it seems that Ftrack is # now flooring the value? - fps = float_round(context.data.get('fps'), 2, ceil) + fps = mayalib.float_round(context.data.get('fps'), 2, ceil) # TODO repace query with using 'context.data["assetEntity"]' asset_doc = get_current_project_asset() From 8c626920ccc0ff81145cab1f994478a2c3ef4a44 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 11:01:47 +0000 Subject: [PATCH 052/356] Fix Updating --- openpype/hosts/maya/plugins/load/load_xgen.py | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 565d1306b4..8249c9092e 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -32,7 +32,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # Copy the xgen palette file from published version. _, maya_extension = os.path.splitext(maya_filepath) source = maya_filepath.replace(maya_extension, ".xgen") - destination = os.path.join( + xgen_file = os.path.join( project_path, "{basename}__{namespace}__{name}.xgen".format( basename=os.path.splitext(os.path.basename(current_file()))[0], @@ -40,15 +40,15 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): name=name ) ).replace("\\", "/") - self.log.info("Copying {} to {}".format(source, destination)) - shutil.copy(source, destination) + self.log.info("Copying {} to {}".format(source, xgen_file)) + shutil.copy(source, xgen_file) # Modify xgDataPath and xgProjectPath to have current workspace first # and published version directory second. This ensure that any newly # created xgen files are created in the current workspace. resources_path = os.path.join(os.path.dirname(source), "resources") lines = [] - with open(destination, "r") as f: + with open(xgen_file, "r") as f: for line in [line.rstrip() for line in f]: if line.startswith("\txgDataPath"): data_path = line.split("\t")[-1] @@ -67,10 +67,12 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): lines.append(line) - with open(destination, "w") as f: + with open(xgen_file, "w") as f: f.write("\n".join(lines)) - return destination + xgd_file = xgen_file.replace(".xgen", ".xgd") + + return xgen_file, xgd_file def process_reference(self, context, name, namespace, options): # Validate workfile has a path. @@ -78,7 +80,8 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): QtWidgets.QMessageBox.warning( None, "", - "Current workfile has not been saved." + "Current workfile has not been saved. Please save the workfile" + " before loading an Xgen." ) return @@ -87,10 +90,9 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): ) name = context["representation"]["data"]["xgenName"] - xgen_file = self.setup_xgen_palette_file( + xgen_file, xgd_file = self.setup_xgen_palette_file( maya_filepath, namespace, name ) - xgd_file = xgen_file.replace(".xgen", ".xgd") # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] @@ -105,17 +107,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): ) xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] - cmds.setAttr( - "{}.xgBaseFile".format(xgen_palette), - os.path.basename(xgen_file), - type="string" - ) - cmds.setAttr( - "{}.xgFileName".format(xgen_palette), - os.path.basename(xgd_file), - type="string" - ) - cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) + self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) # This create an expression attribute of float. If we did not add # any changes to collection, then Xgen does not create an xgd file @@ -133,10 +125,24 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): return new_nodes + def set_palette_attributes(self, xgen_palette, xgen_file, xgd_file): + cmds.setAttr( + "{}.xgBaseFile".format(xgen_palette), + os.path.basename(xgen_file), + type="string" + ) + cmds.setAttr( + "{}.xgFileName".format(xgen_palette), + os.path.basename(xgd_file), + type="string" + ) + cmds.setAttr("{}.xgExportAsDelta".format(xgen_palette), True) + def update(self, container, representation): """Workflow for updating Xgen. - - Copy and overwrite the workspace .xgen file. + - Copy and potentially overwrite the workspace .xgen file. + - Export changes to delta file. - Set collection attributes to not include delta files. - Update xgen maya file reference. - Apply the delta file changes. @@ -144,22 +150,28 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): We have to do this workflow because when using referencing of the xgen collection, Maya implicitly imports the Xgen data from the xgen file so - we dont have any control over added the delta file changes. + we dont have any control over when adding the delta file changes. + + There is an implicit increment of the xgen and delta files, due to + using the workfile basename. """ container_node = container["objectName"] members = get_container_members(container_node) + xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] reference_node = get_reference_node(members, self.log) namespace = cmds.referenceQuery(reference_node, namespace=True)[1:] - xgen_file = self.setup_xgen_palette_file( + xgen_file, xgd_file = self.setup_xgen_palette_file( get_representation_path(representation), namespace, representation["data"]["xgenName"] ) - xgd_file = xgen_file.replace(".xgen", ".xgd") - xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] + # Export current changes to apply later. + xgenm.createDelta(xgen_palette.replace("|", ""), xgd_file) + + self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) attribute_data = { "{}.xgFileName".format(xgen_palette): os.path.basename(xgen_file), From 977d4263cb52e12517d655391f3aec7b6c95b897 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Jan 2023 19:39:52 +0100 Subject: [PATCH 053/356] Fix - addSite loader handles hero version If adding site to representation presence of hero version is checked, if found hero version is marked to be donwloaded too. --- openpype/plugins/load/add_site.py | 57 +++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index ac931e41db..64567b746a 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -2,6 +2,12 @@ from openpype.client import get_linked_representation_id from openpype.modules import ModulesManager from openpype.pipeline import load from openpype.modules.sync_server.utils import SiteAlreadyPresentError +from openpype.client.entities import ( + get_hero_version_by_subset_id, + get_representation_by_id, + get_version_by_id, + get_representation_by_name +) class AddSyncSite(load.LoaderPlugin): @@ -34,15 +40,20 @@ class AddSyncSite(load.LoaderPlugin): return self._sync_server def load(self, context, name=None, namespace=None, data=None): - self.log.info("Adding {} to representation: {}".format( - data["site_name"], data["_id"])) + # self.log wont propagate + print("Adding {} to representation: {}".format( + data["site_name"], data["_id"])) family = context["representation"]["context"]["family"] project_name = data["project_name"] repre_id = data["_id"] site_name = data["site_name"] - self.sync_server.add_site(project_name, repre_id, site_name, - force=True) + representation_ids = self._add_hero_representation_ids(project_name, + repre_id) + + for repre_id in representation_ids: + self.sync_server.add_site(project_name, repre_id, site_name, + force=True) if family == "workfile": links = get_linked_representation_id( @@ -52,9 +63,12 @@ class AddSyncSite(load.LoaderPlugin): ) for link_repre_id in links: try: - self.sync_server.add_site(project_name, link_repre_id, - site_name, - force=False) + representation_ids = self._add_hero_representation_ids( + project_name, link_repre_id) + for repre_id in representation_ids: + self.sync_server.add_site(project_name, repre_id, + site_name, + force=False) except SiteAlreadyPresentError: # do not add/reset working site for references self.log.debug("Site present", exc_info=True) @@ -64,3 +78,32 @@ class AddSyncSite(load.LoaderPlugin): def filepath_from_context(self, context): """No real file loading""" return "" + + def _add_hero_representation_ids(self, project_name, repre_id): + """Find hero version if exists for repre_id. + + Returns: + (list): at least [repre_id] if no hero version found + """ + representation_ids = [repre_id] + + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["_id", "parent", "name"] + ) + + version_doc = get_version_by_id(project_name, repre_doc["parent"]) + if version_doc["type"] != "hero_version": + hero_version = get_hero_version_by_subset_id( + project_name, version_doc["parent"], + fields=["_id", "version_id"] + ) + if (hero_version and + hero_version["version_id"] == version_doc["_id"]): + hero_repre_doc = get_representation_by_name( + project_name, + repre_doc["name"], + hero_version["_id"] + ) + representation_ids.append(hero_repre_doc["_id"]) + + return representation_ids From a91a980b1becaf112ee0b05d4d5c4498ee13611e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 20 Jan 2023 19:56:38 +0100 Subject: [PATCH 054/356] laoder plugin has compatibility method on it's own --- openpype/pipeline/load/plugins.py | 35 ++++++++++++++++++++++++++++++- openpype/pipeline/load/utils.py | 18 +--------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index b5e55834db..4197ddb2ae 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -2,7 +2,10 @@ import os import logging from openpype.settings import get_system_settings, get_project_settings -from openpype.pipeline import legacy_io +from openpype.pipeline import ( + schema, + legacy_io, +) from openpype.pipeline.plugin_discover import ( discover, register_plugin, @@ -79,6 +82,36 @@ class LoaderPlugin(list): print(" - setting `{}`: `{}`".format(option, value)) setattr(cls, option, value) + @classmethod + def is_compatible_loader(cls, context): + """Return whether a loader is compatible with a context. + + This checks the version's families and the representation for the given + Loader. + + Returns: + bool + """ + + maj_version, _ = schema.get_schema_version(context["subset"]["schema"]) + if maj_version < 3: + families = context["version"]["data"].get("families", []) + else: + families = context["subset"]["data"]["families"] + + representation = context["representation"] + has_family = ( + "*" in cls.families or any( + family in cls.families for family in families + ) + ) + representations = cls.get_representations() + has_representation = ( + "*" in representations + or representation["name"] in representations + ) + return has_family and has_representation + @classmethod def get_representations(cls): return cls.representations diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index e2b3675115..e30923f922 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -748,25 +748,9 @@ def is_compatible_loader(Loader, context): Returns: bool - """ - maj_version, _ = schema.get_schema_version(context["subset"]["schema"]) - if maj_version < 3: - families = context["version"]["data"].get("families", []) - else: - families = context["subset"]["data"]["families"] - representation = context["representation"] - has_family = ( - "*" in Loader.families or any( - family in Loader.families for family in families - ) - ) - representations = Loader.get_representations() - has_representation = ( - "*" in representations or representation["name"] in representations - ) - return has_family and has_representation + return Loader.is_compatible_loader(context) def loaders_from_repre_context(loaders, repre_context): From bce5363e4250bc6e9a83c7702889a30790bf909a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 23 Jan 2023 10:23:07 +0100 Subject: [PATCH 055/356] reorganized the order of conditions --- openpype/pipeline/load/plugins.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 4197ddb2ae..9b891a4da3 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -93,24 +93,33 @@ class LoaderPlugin(list): bool """ + plugin_repre_names = cls.get_representations() + plugin_families = cls.families + if not plugin_repre_names or not plugin_families: + return False + + repre_doc = context.get("representation") + if not repre_doc: + return False + + plugin_repre_names = set(plugin_repre_names) + if ( + "*" not in plugin_repre_names + and repre_doc["name"] not in plugin_repre_names + ): + return False + maj_version, _ = schema.get_schema_version(context["subset"]["schema"]) if maj_version < 3: families = context["version"]["data"].get("families", []) else: families = context["subset"]["data"]["families"] - representation = context["representation"] - has_family = ( - "*" in cls.families or any( - family in cls.families for family in families - ) + plugin_families = set(plugin_families) + return ( + "*" in plugin_families + or any(family in plugin_families for family in families) ) - representations = cls.get_representations() - has_representation = ( - "*" in representations - or representation["name"] in representations - ) - return has_family and has_representation @classmethod def get_representations(cls): From e4cd14c2a633f13146920a2c0cb4a70a09154e8e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 23 Jan 2023 17:44:11 +0000 Subject: [PATCH 056/356] Clean up --- .../maya/plugins/publish/extract_xgen.py | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 22260df2c7..80b62275cd 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -10,7 +10,14 @@ from openpype.lib import StringTemplate class ExtractXgenCache(publish.Extractor): - """Extract Xgen""" + """Extract Xgen + + Workflow: + - Duplicate nodes used for patches. + - Export palette and import onto duplicate nodes. + - Export/Publish duplicate nodes and palette. + - Publish all xgen files as resources. + """ label = "Extract Xgen" hosts = ["maya"] @@ -55,29 +62,12 @@ class ExtractXgenCache(publish.Extractor): # Duplicate_transform subd patch geometry. duplicate_transform = cmds.duplicate(transform_name)[0] - duplicate_shape = cmds.listRelatives( - duplicate_transform, - shapes=True, - fullPath=True - )[0] # Discard the children. shapes = cmds.listRelatives(duplicate_transform, shapes=True) children = cmds.listRelatives(duplicate_transform, children=True) cmds.delete(set(children) - set(shapes)) - # Connect attributes. - cmds.connectAttr( - "{}.matrix".format(duplicate_transform), - "{}.transform".format(node), - force=True - ) - cmds.connectAttr( - "{}.worldMesh".format(duplicate_shape), - "{}.geometry".format(node), - force=True - ) - duplicate_transform = cmds.parent( duplicate_transform, world=True )[0] @@ -87,17 +77,17 @@ class ExtractXgenCache(publish.Extractor): # Import xgen onto the duplicate. with maintained_selection(): cmds.select(duplicate_nodes) - collection = xgenm.importPalette(xgen_path, []) + palette = xgenm.importPalette(xgen_path, []) attribute_data = { - "{}.xgFileName".format(collection): xgen_filename + "{}.xgFileName".format(palette): xgen_filename } # Export Maya file. type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" with attribute_values(attribute_data): with maintained_selection(): - cmds.select(duplicate_nodes + [collection]) + cmds.select(duplicate_nodes + [palette]) cmds.file( maya_filepath, force=True, @@ -117,18 +107,11 @@ class ExtractXgenCache(publish.Extractor): "ext": self.scene_type, "files": maya_filename, "stagingDir": staging_dir, - "data": {"xgenName": collection} + "data": {"xgenName": palette} } instance.data["representations"].append(representation) - # Revert to original xgen connections. - for node, connections in instance.data["xgenConnections"].items(): - for attr, src in connections.items(): - cmds.connectAttr( - src, "{}.{}".format(node, attr), force=True - ) - - cmds.delete(duplicate_nodes + [collection]) + cmds.delete(duplicate_nodes + [palette]) # Collect all files under palette root as resources. data_path = xgenm.getAttr( From 6ef2b4be9a5c458364f480325483ead90febdcf9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 24 Jan 2023 16:09:56 +0000 Subject: [PATCH 057/356] Working import --- openpype/hosts/maya/plugins/load/load_xgen.py | 76 +++++++++++++------ .../maya/plugins/publish/extract_xgen.py | 43 ++++------- 2 files changed, 70 insertions(+), 49 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 8249c9092e..fec1b389fa 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -1,5 +1,6 @@ import os import shutil +import tempfile import maya.cmds as cmds import xgenm @@ -25,6 +26,18 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" + def write_xgen_file(self, file_path, data): + lines = [] + with open(file_path, "r") as f: + for key, value in data.items(): + for line in [line.rstrip() for line in f]: + if line.startswith("\t" + key): + line = "\t{}\t\t{}".format(key, value) + lines.append(line) + + with open(file_path, "w") as f: + f.write("\n".join(lines)) + def setup_xgen_palette_file(self, maya_filepath, namespace, name): # Setup xgen palette file. project_path = os.path.dirname(current_file()) @@ -47,28 +60,26 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # and published version directory second. This ensure that any newly # created xgen files are created in the current workspace. resources_path = os.path.join(os.path.dirname(source), "resources") - lines = [] + with open(xgen_file, "r") as f: for line in [line.rstrip() for line in f]: if line.startswith("\txgDataPath"): data_path = line.split("\t")[-1] - line = "\txgDataPath\t\t{}{}{}".format( - data_path, - os.pathsep, - data_path.replace( - "${PROJECT}xgen", resources_path.replace("\\", "/") - ) + + data = { + "xgDataPath": ( + "${{PROJECT}}xgen/collections/{}__ns__{}/{}{}".format( + namespace, + name, + os.pathsep, + data_path.replace( + "${PROJECT}xgen", resources_path.replace("\\", "/") ) - - if line.startswith("\txgProjectPath"): - line = "\txgProjectPath\t\t{}/".format( - project_path.replace("\\", "/") - ) - - lines.append(line) - - with open(xgen_file, "w") as f: - f.write("\n".join(lines)) + ) + ), + "xgProjectPath": project_path.replace("\\", "/") + } + self.write_xgen_file(xgen_file, data) xgd_file = xgen_file.replace(".xgen", ".xgd") @@ -89,14 +100,31 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.fname, context["project"]["name"] ) - name = context["representation"]["data"]["xgenName"] xgen_file, xgd_file = self.setup_xgen_palette_file( - maya_filepath, namespace, name + maya_filepath, namespace, "collection" ) + # Making temporary copy of xgen file from published so we can + # modify the paths. + temp_xgen_file = os.path.join(tempfile.gettempdir(), "temp.xgen") + _, maya_extension = os.path.splitext(maya_filepath) + source = maya_filepath.replace(maya_extension, ".xgen") + shutil.copy(source, temp_xgen_file) + + resources_path = os.path.join(os.path.dirname(source), "resources") + with open(xgen_file, "r") as f: + for line in [line.rstrip() for line in f]: + if line.startswith("\txgDataPath"): + data_path = line.split("\t")[-1] + data = { + "xgDataPath": data_path.replace( + "${PROJECT}xgen", resources_path.replace("\\", "/") + ) + } + self.write_xgen_file(temp_xgen_file, data) + # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] - with maintained_selection(): nodes = cmds.file( maya_filepath, @@ -106,7 +134,11 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True ) - xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] + xgen_palette = xgenm.importPalette( + temp_xgen_file.replace("\\", "/"), [], nameSpace=namespace + ) + os.remove(temp_xgen_file) + self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) # This create an expression attribute of float. If we did not add @@ -119,7 +151,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): shapes = cmds.ls(nodes, shapes=True, long=True) - new_nodes = (list(set(nodes) - set(shapes))) + new_nodes = (list(set(nodes) - set(shapes)) + [xgen_palette]) self[:] = new_nodes diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 80b62275cd..91d4352449 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -5,7 +5,7 @@ from maya import cmds import xgenm from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.hosts.maya.api.lib import maintained_selection from openpype.lib import StringTemplate @@ -74,31 +74,21 @@ class ExtractXgenCache(publish.Extractor): duplicate_nodes.append(duplicate_transform) - # Import xgen onto the duplicate. - with maintained_selection(): - cmds.select(duplicate_nodes) - palette = xgenm.importPalette(xgen_path, []) - - attribute_data = { - "{}.xgFileName".format(palette): xgen_filename - } - # Export Maya file. type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" - with attribute_values(attribute_data): - with maintained_selection(): - cmds.select(duplicate_nodes + [palette]) - cmds.file( - maya_filepath, - force=True, - type=type, - exportSelected=True, - preserveReferences=False, - constructionHistory=True, - shader=True, - constraints=True, - expressions=True - ) + with maintained_selection(): + cmds.select(duplicate_nodes) + cmds.file( + maya_filepath, + force=True, + type=type, + exportSelected=True, + preserveReferences=False, + constructionHistory=True, + shader=True, + constraints=True, + expressions=True + ) self.log.info("Extracted to {}".format(maya_filepath)) @@ -106,12 +96,11 @@ class ExtractXgenCache(publish.Extractor): "name": self.scene_type, "ext": self.scene_type, "files": maya_filename, - "stagingDir": staging_dir, - "data": {"xgenName": palette} + "stagingDir": staging_dir } instance.data["representations"].append(representation) - cmds.delete(duplicate_nodes + [palette]) + cmds.delete(duplicate_nodes) # Collect all files under palette root as resources. data_path = xgenm.getAttr( From 04c1b8905508253456e9bab7008bfe1263d98999 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 25 Jan 2023 09:33:23 +0100 Subject: [PATCH 058/356] fix typo + remove part which creates second reference per placeholder --- .../hosts/maya/api/workfile_template_builder.py | 2 +- .../pipeline/workfile/workfile_template_builder.py | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index ef043ed0f4..8e0f1d0f01 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -240,7 +240,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): cmds.setAttr(node + ".hiddenInOutliner", True) def load_succeed(self, placeholder, container): - self._parent_in_hierarhchy(placeholder, container) + self._parent_in_hierarchy(placeholder, container) def _parent_in_hierarchy(self, placeholder, container): """Parent loaded container to placeholder's parent. diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 582657c735..d262d6a771 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -521,10 +521,6 @@ class AbstractTemplateBuilder(object): if not level_limit: level_limit = 1000 - placeholder_by_scene_id = { - placeholder.scene_identifier: placeholder - for placeholder in placeholders - } all_processed = len(placeholders) == 0 # Counter is checked at the ned of a loop so the loop happens at least # once. @@ -573,16 +569,6 @@ class AbstractTemplateBuilder(object): break all_processed = True - collected_placeholders = self.get_placeholders() - for placeholder in collected_placeholders: - identifier = placeholder.scene_identifier - if identifier in placeholder_by_scene_id: - continue - - all_processed = False - placeholder_by_scene_id[identifier] = placeholder - placeholders.append(placeholder) - self.refresh() def _get_build_profiles(self): From 6a992409f0adac4cf4fe534e5feee5e6f610b363 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 25 Jan 2023 09:51:42 +0100 Subject: [PATCH 059/356] Added more elaborate info on installing/running OP and corrected few typos. --- website/docs/artist_getting_started.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 2f88a9f238..474a39642b 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -18,13 +18,17 @@ If this is not the case, please contact your administrator to consult on how to If you are working from home though, you'll need to install it yourself. You should, however, receive the OpenPype installer files from your studio admin, supervisor or production, because OpenPype versions and executables might not be compatible between studios. -To install OpenPype you just need to unzip it anywhere on the disk +Installing OpenPype is possible by Windows installer or by unzipping it anywhere on the disk from downloaded ZIP archive. -To use it, you have two options +There are two options running OpenPype -**openpype_gui.exe** is the most common for artists. It runs OpenPype GUI in system tray. From there you can run all the available tools. To use any of the features, OpenPype must be running in the tray. +first most common one by using OP icon on the Desktop triggering -**openpype_console.exe** in useful for debugging and error reporting. It opens console window where all the necessary information will appear during user's work. +**openpype_gui.exe** suitable for artists. It runs OpenPype GUI in the OS tray. From there you can run all the available tools. To use any of the features, OpenPype must be running in the tray. + +or alternatively by using + +**openpype_console.exe** located in the OpenPype folder which is suitable for TDs/admins for debugging and error reporting. It opens console window where all the necessary information will appear during user's work. Date: Wed, 25 Jan 2023 09:38:17 +0000 Subject: [PATCH 060/356] Revert "Working import" This reverts commit 6ef2b4be9a5c458364f480325483ead90febdcf9. --- openpype/hosts/maya/plugins/load/load_xgen.py | 76 ++++++------------- .../maya/plugins/publish/extract_xgen.py | 43 +++++++---- 2 files changed, 49 insertions(+), 70 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index fec1b389fa..8249c9092e 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -1,6 +1,5 @@ import os import shutil -import tempfile import maya.cmds as cmds import xgenm @@ -26,18 +25,6 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" - def write_xgen_file(self, file_path, data): - lines = [] - with open(file_path, "r") as f: - for key, value in data.items(): - for line in [line.rstrip() for line in f]: - if line.startswith("\t" + key): - line = "\t{}\t\t{}".format(key, value) - lines.append(line) - - with open(file_path, "w") as f: - f.write("\n".join(lines)) - def setup_xgen_palette_file(self, maya_filepath, namespace, name): # Setup xgen palette file. project_path = os.path.dirname(current_file()) @@ -60,26 +47,28 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # and published version directory second. This ensure that any newly # created xgen files are created in the current workspace. resources_path = os.path.join(os.path.dirname(source), "resources") - + lines = [] with open(xgen_file, "r") as f: for line in [line.rstrip() for line in f]: if line.startswith("\txgDataPath"): data_path = line.split("\t")[-1] - - data = { - "xgDataPath": ( - "${{PROJECT}}xgen/collections/{}__ns__{}/{}{}".format( - namespace, - name, - os.pathsep, - data_path.replace( - "${PROJECT}xgen", resources_path.replace("\\", "/") + line = "\txgDataPath\t\t{}{}{}".format( + data_path, + os.pathsep, + data_path.replace( + "${PROJECT}xgen", resources_path.replace("\\", "/") + ) ) - ) - ), - "xgProjectPath": project_path.replace("\\", "/") - } - self.write_xgen_file(xgen_file, data) + + if line.startswith("\txgProjectPath"): + line = "\txgProjectPath\t\t{}/".format( + project_path.replace("\\", "/") + ) + + lines.append(line) + + with open(xgen_file, "w") as f: + f.write("\n".join(lines)) xgd_file = xgen_file.replace(".xgen", ".xgd") @@ -100,31 +89,14 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.fname, context["project"]["name"] ) + name = context["representation"]["data"]["xgenName"] xgen_file, xgd_file = self.setup_xgen_palette_file( - maya_filepath, namespace, "collection" + maya_filepath, namespace, name ) - # Making temporary copy of xgen file from published so we can - # modify the paths. - temp_xgen_file = os.path.join(tempfile.gettempdir(), "temp.xgen") - _, maya_extension = os.path.splitext(maya_filepath) - source = maya_filepath.replace(maya_extension, ".xgen") - shutil.copy(source, temp_xgen_file) - - resources_path = os.path.join(os.path.dirname(source), "resources") - with open(xgen_file, "r") as f: - for line in [line.rstrip() for line in f]: - if line.startswith("\txgDataPath"): - data_path = line.split("\t")[-1] - data = { - "xgDataPath": data_path.replace( - "${PROJECT}xgen", resources_path.replace("\\", "/") - ) - } - self.write_xgen_file(temp_xgen_file, data) - # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] + with maintained_selection(): nodes = cmds.file( maya_filepath, @@ -134,11 +106,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True ) - xgen_palette = xgenm.importPalette( - temp_xgen_file.replace("\\", "/"), [], nameSpace=namespace - ) - os.remove(temp_xgen_file) - + xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) # This create an expression attribute of float. If we did not add @@ -151,7 +119,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): shapes = cmds.ls(nodes, shapes=True, long=True) - new_nodes = (list(set(nodes) - set(shapes)) + [xgen_palette]) + new_nodes = (list(set(nodes) - set(shapes))) self[:] = new_nodes diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 91d4352449..80b62275cd 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -5,7 +5,7 @@ from maya import cmds import xgenm from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api.lib import maintained_selection, attribute_values from openpype.lib import StringTemplate @@ -74,21 +74,31 @@ class ExtractXgenCache(publish.Extractor): duplicate_nodes.append(duplicate_transform) - # Export Maya file. - type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" + # Import xgen onto the duplicate. with maintained_selection(): cmds.select(duplicate_nodes) - cmds.file( - maya_filepath, - force=True, - type=type, - exportSelected=True, - preserveReferences=False, - constructionHistory=True, - shader=True, - constraints=True, - expressions=True - ) + palette = xgenm.importPalette(xgen_path, []) + + attribute_data = { + "{}.xgFileName".format(palette): xgen_filename + } + + # Export Maya file. + type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" + with attribute_values(attribute_data): + with maintained_selection(): + cmds.select(duplicate_nodes + [palette]) + cmds.file( + maya_filepath, + force=True, + type=type, + exportSelected=True, + preserveReferences=False, + constructionHistory=True, + shader=True, + constraints=True, + expressions=True + ) self.log.info("Extracted to {}".format(maya_filepath)) @@ -96,11 +106,12 @@ class ExtractXgenCache(publish.Extractor): "name": self.scene_type, "ext": self.scene_type, "files": maya_filename, - "stagingDir": staging_dir + "stagingDir": staging_dir, + "data": {"xgenName": palette} } instance.data["representations"].append(representation) - cmds.delete(duplicate_nodes) + cmds.delete(duplicate_nodes + [palette]) # Collect all files under palette root as resources. data_path = xgenm.getAttr( From 8ca483adaf2135ad66f2a126228fc1b71164c16e Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 25 Jan 2023 11:36:07 +0100 Subject: [PATCH 061/356] first rough concept for 3dsmax user doc --- website/docs/artist_hosts_3dsmax.md | 226 ++++++++++++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 227 insertions(+) create mode 100644 website/docs/artist_hosts_3dsmax.md diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md new file mode 100644 index 0000000000..f959f48533 --- /dev/null +++ b/website/docs/artist_hosts_3dsmax.md @@ -0,0 +1,226 @@ +--- +id: artist_hosts_3dsmax +title: 3dsmax +sidebar_label: 3dsmax +--- + +## Still Work In Progress Doc + +- [Set Context](artist_tools_context_manager) +- [Work Files](artist_tools_workfiles) +- [Create](artist_tools_creator) +- [Load](artist_tools_loader) +- [Manage (Inventory)](artist_tools_inventory) +- [Publish](artist_tools_publisher) +- [Library Loader](artist_tools_library_loader) + +## Working with OpenPype in 3dsmax + +OpenPype is here to ease you the burden of working on project with lots of +collaborators, worrying about naming, setting stuff, browsing through endless +directories, loading and exporting and so on. To achieve that, OpenPype is using +concept of being _"data driven"_. This means that what happens when publishing +is influenced by data in scene. This can by slightly confusing so let's get to +it with few examples. + + +## Setting scene data + +Blender settings concerning framerate, resolution and frame range are handled +by OpenPype. If set correctly in Ftrack, Blender will automatically set the +values for you. + + +## Publishing models + +### Intro + +Publishing models in Blender is pretty straightforward. Create your model as you +need. You might need to adhere to specifications of your studio that can be different +between studios and projects but by default your geometry does not need any +other convention. + +![Model example](assets/blender-model_example.jpg) + +### Creating instance + +Now create **Model instance** from it to let OpenPype know what in the scene you want to +publish. Go **OpenPype → Create... → Model**. + +![Model create instance](assets/blender-model_create_instance.jpg) + +`Asset` field is a name of asset you are working on - it should be already filled +with correct name as you've started Blender or switched context to specific asset. You +can edit that field to change it to different asset (but that one must already exists). + +`Subset` field is a name you can decide on. It should describe what kind of data you +have in the model. For example, you can name it `Proxy` to indicate that this is +low resolution stuff. See [Subset](artist_concepts.md#subset). + + + +Read-only field just under it show final subset name, adding subset field to +name of the group you have selected. + +`Use selection` checkbox will use whatever you have selected in Outliner to be +wrapped in Model instance. This is usually what you want. Click on **Create** button. + +You'll notice then after you've created new Model instance, there is a new +collection in Outliner called after your asset and subset, in our case it is +`character1_modelDefault`. The assets selected when creating the Model instance +are linked in the new collection. + +And that's it, you have your first model ready to publish. + +Now save your scene (if you didn't do it already). You will notice that path +in Save dialog is already set to place where scenes related to modeling task on +your asset should reside. As in our case we are working on asset called +**character1** and on task **modeling**, path relative to your project directory will be +`project_XY/assets/character1/work/modeling`. The default name for the file will +be `project_XY_asset_task_version`, so in our case +`simonetest_character1_modeling_v001.blend`. Let's save it. + +![Model create instance](assets/blender-save_modelling_file.jpg) + +### Publishing models + +Now let's publish it. Go **OpenPype → Publish...**. You will be presented with following window: + +![Model publish](assets/blender-model_pre_publish.jpg) + +Note that content of this window can differs by your pipeline configuration. +For more detail see [Publisher](artist_tools_publisher). + +Items in left column are instances you will be publishing. You can disable them +by clicking on square next to them. White filled square indicate they are ready for +publishing, red means something went wrong either during collection phase +or publishing phase. Empty one with gray text is disabled. + +See that in this case we are publishing from the scene file +`simonetest_character1_modeling_v001.blend` the Blender model named +`character1_modelDefault`. + +Right column lists all tasks that are run during collection, validation, +extraction and integration phase. White items are optional and you can disable +them by clicking on them. + +Lets do dry-run on publishing to see if we pass all validators. Click on flask +icon at the bottom. Validators are run. Ideally you will end up with everything +green in validator section. + +### Fixing problems + +For the sake of demonstration, I intentionally kept the model in Edit Mode, to +trigger the validator designed to check just this. + +![Failed Model Validator](assets/blender-model_publish_error.jpg) + +You can see our model is now marked red in left column and in right we have +red box next to `Mesh is in Object Mode` validator. + +You can click on arrow next to it to see more details: + +![Failed Model Validator details](assets/blender-model_error_details.jpg) + +From there you can see in **Records** entry that there is problem with the +object `Suzanne`. Some validators have option to fix problem for you or just +select objects that cause trouble. This is the case with our failed validator. + +In main overview you can notice little A in a circle next to validator +name. Right click on it and you can see menu item `select invalid`. This +will select offending object in Blender. + +Fix is easy. Without closing Publisher window we just turn back the Object Mode. +Then we need to reset it to make it notice changes we've made. Click on arrow +circle button at the bottom and it will reset the Publisher to initial state. Run +validators again (flask icon) to see if everything is ok. + +It should OK be now. Write some comment if you want and click play icon button +when ready. + +Publish process will now take its course. Depending on data you are publishing +it can take a while. You should end up with everything green and message +**Finished successfully ...** You can now close publisher window. + +To check for yourself that model is published, open +[Asset Loader](artist_tools_loader) - **OpenPype → Load...**. +There you should see your model, named `modelDefault`. + +### Loading models + +You can load model with [Loader](artist_tools_loader). Go **OpenPype → Load...**, +select your rig, right click on it and click **Link model (blend)**. + +## Creating Rigs + +Creating and publishing rigs with OpenPype follows similar workflow as with +other data types. Create your rig and mark parts of your hierarchy in sets to +help OpenPype validators and extractors to check it and publish it. + +### Preparing rig for publish + +When creating rigs in Blender, it is important to keep a specific structure for +the bones and the geometry. Let's first create a model and its rig. For +demonstration, I'll create a simple model for a robotic arm made of simple boxes. + +![Blender - Simple model for rigging](assets/blender-rig_model_setup.jpg) + +I have now created the armature `RIG_RobotArm`. While the naming is not important, +you can just adhere to your naming conventions, the hierarchy is. Once the models +are skinned to the armature, the geometry must be organized in a separate Collection. +In this case, I have the armature in the main Collection, and the geometry in +the `Geometry` Collection. + +![Blender - Rig Hierarchy Example](assets/blender-rig_hierarchy_example.jpg) + +When you've prepared your hierarchy, it's time to create *Rig instance* in OpenPype. +Select your whole rig hierarchy and go **OpenPype → Create...**. Select **Rig**. + +![Blender - Rig Hierarchy Example](assets/blender-rig_create.jpg) + +A new collection named after the selected Asset and Subset should have been created. +In our case, it is `character1_rigDefault`. All the selected armature and models +have been linked in this new collection. You should end up with something like +this: + +![Blender - Rig Hierarchy Example](assets/blender-rig_hierarchy_before_publish.jpg) + +### Publishing rigs + +Publishing rig is done in same way as publishing everything else. Save your scene +and go **OpenPype → Publish**. For more detail see [Publisher](artist_tools_publisher). + +### Loading rigs + +You can load rig with [Loader](artist_tools_loader). Go **OpenPype → Load...**, +select your rig, right click on it and click **Link rig (blend)**. + +## Layouts in Blender + +A layout is a set of elements that populate a scene. OpenPype allows to version +and manage those sets. + +### Publishing a layout + +Working with Layout is easy. Just load your assets into scene with +[Loader](artist_tools_loader) (**OpenPype → Load...**). Populate your scene as +you wish, translate each piece to fit your need. When ready, select all imported +stuff and go **OpenPype → Create...** and select **Layout**. When selecting rigs, +you need to select only the armature, the geometry will automatically be included. +This will create set containing your selection and marking it for publishing. + +Now you can publish is with **OpenPype → Publish**. + +### Loading layouts + +You can load a Layout using [Loader](artist_tools_loader) +(**OpenPype → Load...**). Select your layout, right click on it and +select **Link Layout (blend)**. This will populate your scene with all those +models you've put into layout. diff --git a/website/sidebars.js b/website/sidebars.js index a19aade712..6802dd6918 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -49,6 +49,7 @@ module.exports = { ], }, "artist_hosts_blender", + "artist_hosts_3dsmax", "artist_hosts_harmony", "artist_hosts_houdini", "artist_hosts_aftereffects", From 4d5f498b9b512a69febd3e8d36351a6569621e6d Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 25 Jan 2023 12:31:33 +0100 Subject: [PATCH 062/356] more changes to the 3dsmax --- website/docs/artist_hosts_3dsmax.md | 12 +++++++----- website/docs/assets/3dsmax_model_OP.png | Bin 0 -> 85689 bytes 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 website/docs/assets/3dsmax_model_OP.png diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index f959f48533..0ca6f008cf 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -4,7 +4,9 @@ title: 3dsmax sidebar_label: 3dsmax --- -## Still Work In Progress Doc +### *Still Work In Progress Page* + +## OpenPype Global Tools - [Set Context](artist_tools_context_manager) - [Work Files](artist_tools_workfiles) @@ -26,8 +28,8 @@ it with few examples. ## Setting scene data -Blender settings concerning framerate, resolution and frame range are handled -by OpenPype. If set correctly in Ftrack, Blender will automatically set the +3dsmax settings concerning framerate, resolution and frame range are handled +by OpenPype. If set correctly in OP Project Manager/Ftrack, 3dsmax will automatically set the values for you. @@ -35,12 +37,12 @@ values for you. ### Intro -Publishing models in Blender is pretty straightforward. Create your model as you +Publishing models in 3dsmax is pretty straightforward. Create your model as you need. You might need to adhere to specifications of your studio that can be different between studios and projects but by default your geometry does not need any other convention. -![Model example](assets/blender-model_example.jpg) +![Model example](assets/3dsmax_model_OP.png) ### Creating instance diff --git a/website/docs/assets/3dsmax_model_OP.png b/website/docs/assets/3dsmax_model_OP.png new file mode 100644 index 0000000000000000000000000000000000000000..293c06642c721cf0a6b78bd5374da477f142e7ae GIT binary patch literal 85689 zcma&O1zeO}*Ec%SEhVlcPicB&@dp~-8GyG z@8@~rd*APzGe5zZYwcKjt+oFv_8!92ROATpDDXfa5TSy+v<3)-76}5~TgO2Io@CQV zI)XqLl@KjGH$7z~K?^4bHZw~nb1<8ygEK$}frQ08oy{!lz;1NrU~7n@2>o6|3q2jg zQiNWYN0~#}Sqf|ek@t25YkI3_S$Nx72w2jKiQ)-+3IYQ-fZfdKJRR&Ep@Nv&#>2)zFN#Me>}qKxs39%;ug<_H5qcXpH)lb1 zb{GuC2IFFLa*)_mWCwEs1u#5-T(T(A+B<|!Qs0Q%_+v`a~9KeoHK)NFIygXe02UY*SP`UZ} z|C1`^Wbfpv378dFg#Pv4$9J+5RB$k}2J1pB-E97IwErY1fgP>yDE}VmFXfIqd-pr_ zYCA%J^>F^@XnOxi6QLL2W&bY|>Hn{!|ImftU;dPxECKfZS<`=#6r`loT%D{S_5cm4 zAty5T7Z|Am6PqC834rz0(~J?Ueac6Kr2oTPA*oC*Q}g8TCX_;Ie7)SxtKX# z3vzJ$W%X}sU_V%zxtaZcwZF4!I$@xPva+B&1nTDG>h<^0Kc=n;cKPSiKOgNOe{GzO z?ysf=%`EPOBtq}(>SXC|0k-@*IWXKmRH&1c8_diVEMW~OS%hA~$_fIA(~C~+&U)!y zv%ThEJ+?(rQk|qr%vSxgx|)Y~JE3t|sfyX7^P(cR4t8_2I=?v`HZ1X(R;Xx0**v@8$Zswpw zXBqh8Y6d;)gf!ijP=mL?QD!xuB)WTZit)o#?r8dn{n54wOQ-&Nezc@e#lnJE0p(CGCkb)z;iHL zZwyRaaUFWKmL@E2Bh|b~ae8VdzBr@ne-CHdx*?6nUl;HgxX$wWP!Ncy{qE;p0y7aM z2t)@`ke1N$Oxteq@+Fx_IP|47N|p`0qy3&zGJ{-sZkx^xzCWy6;A&AZCJNzmCfw&Qo=#Jtaum6vQ7 zRdhJJS<=zfg(XQV+U8*jn!eX^ztS0N(jGf0fbKS}an>L`DCuy5=y1wkEF2vjA&jvw zBw~&^`1xOio7XFr4a>*9S4BnOWDv*gasusgBEMe{7^PG-%S)uo5?d-%aAyW8R*aLr z{DHs;Vu2*CaydiBFX=xtEQT{%HO8I|`R@-9cHLgx#NsoV>@#2};3n;hc;M@sGg zk!KV;^Y?S~3nw^^NaB=D+2_r(&lMa6_hg)oqnu$?hNh4kiGkZA<*EnwKy)s97Thi? znKy5rbNd#R9R*!zM%7)Ta*bWUrTv+DT75&geRbmfm9DXot2(=tI>ODp1F41hQutFK zVxMR)yMWrOPQHHY{LQVay{%+#F?`iHQl7k$A^KW5>yVn$(1wDiQ-)q|9;y!ZXYwi{ z%9X0e=cZ|Q*}Q(n1lNw8r*Hb0ggmkvYF zKsnhiSEtK|@GtrG$T5`2*o>ds_dzN7u46+tVQY1*^vQ>+NzkYJQeF?lhoDwsD}`!iX^oK-uaP+JLZWmgw+G}ko<1zJm69kJu%_%om51cuwa z)7vXsJP&WPxFYx2i!{`+iG9sN2bxpz^b|1J!Dlo#Z@aVIAf#65Kp!Y|Glin}UTaGL zTw#d8>&;e*$LC;_%hBkC=7));ka53Qn6Rl|Q=T{>YG-h3UU$4=0b0A*#bt&DW! zjK|Min{f5&ZYDxyp4_5B*WXfFGMj>`RR#4eSf_i1(K?Pk+f^K_x-zO)X9llOuX3N{ zrpWYd6d`B+K&JMJ^IXuyw=y;v65Q^iZiH(1BzNYF_Z9u*K*UG%x=R`GOBKzP!X;;) z@n+kb;Qabr2W0117`&pcV_r=kQ&a>Bu4`GjP&wLo^Fv14Saan;{Ta}Ry0lAgZajU{ z+hDGY>!My6TGV1)Z+VFYsBj$Sv`yN6SlN$0r~G)h+$R%mHpAp(VGUP@9l;P;m0^6& zZqXy&?*eW=;l<|^7J9A2RsD+_P}MUgn4sDnT2sG6$YHnX^-LZ)b>HDRNN&^VO049! zl<>`4xmO;7A}?J}R9y;`yeghnynlwp{nXJbUm6y<*4=bA=d(9>qgGKcP6>nSj_o+_ z&3o+uPaEJLE+y$N_K-^xNAScbN3qXNijywd#!`_1LrtG=Z;a$B8RrFTKJzxSQ4ihh zrC;6n8{G3Nm`uwx@U%OV-Spne7+>#sLv0dGd5xql3rn1Mwpy7PS%P^mz8juKkbMmT ztvoJGebVA9r}@1u2n2FIt-JMO7ZLHd5pj*l7z(7amRq?AWoE6lURSCjfT1 zN=C_T6&$ttq$PH0%jL>2MMw$Wm-WOL>0i@D*|Q5nL}sVvKI{|Xy(pQfoT%4tvCJjR zJtyi6ka+TE@uB2Lm?mZ=H;l^HUrfvXkm7_VfRDAsZk&(f-9Qeu(Hm4oC>>(B2~ z?Vc7M$Bo^;IUWmWWGzpfK^^>UbdjA?K3;Zaxt-rp#JM8%IQdbbS4S;{c4m=F%ZM)> z??@v7iqzlh9kJx}ecF=-Md4i4ffihW{q2gK)$3)YB(aq@vN%t%X#$Qhe?ZPpN&;@S zOmFMi4jJe5P@QMAVs8gYS;N$|ve|}En}jf#?-R%5WrO5JA8N{f-E@{@I7Ig(83;df z446oMZ@IPg&}Z6$(~FszP6LHLAglcI_Sc!sDwi?pJsyP+fC>2<-`2`)ExdAib8m9u zAo;#uFCq^kwckH!3pjQb3Ts)(s-zX&oZ#|dW`1WCwOpAmbG4AIroQp4-B5qzs9=xU z-Kj9^`^JWC^!!CyS)+yZwR=VLIMz;Rv~07J&iv1I_U>y_BlCKSPTXkb1+96WK1#CV z5;N%bnV`Z$2U5Fnd*rsj;#Jejm9u7sKl*i8K&F#vxY2MjZs>G#$i4s#ID3}jaV2)+ zd9=Vj@4Z!2GY+E+lO6oh?x}KZuzv>ua$d@AxfsX>{RuGZ(!EdoafkFROX{PU2A@{+5or*}^-mJJuhr05 z0F)NgcyOaC;@@feTwz|d=QnCwSL}oZ)H>c};(Kh0y6)G_#8ky?_pV~ek5|h$r!ZLI zPE-_e3^+?ZAJ8=Bseylv@qmVd%@$Rz0w)bVd(RG2>kA=vU_NhflQ)02Yu9Zb1-P3I zor$@>1BoZY+iO)pplc+)!3w(EoM)vgz5pnJ?=l)yo*q*^nU%r^5%V2wfdh}7tq`*O zmqRmC?d<3QFOGk=~ ze0uL6vsm9WciowtdEjZ`L#}T^`cY)3gf=`+#u}!ob^S(UH)B`j^3={9VqVa=0zHc* zMIE}=XP*|+crMK^?Z%ICYA4_lOPV?VFp@5HYvPHS7e##tgiN zu@I44k}A)pi%5s>hP=8lyHfAnxLXd^b6E$(>+cWjS#`9|b7W_BD&?2+og}jbFqZaCtz;Q?BXV9a&KCTuk^x> z|LD1rt047*%rB$6T*odCd?;|ATAuW3Oddy0m|TU##<#D2+T^;&V133;Gn}mf-%)vF?B?z<5N8J)vXl^@#tvO?1N{^s)1C%m}XpR7E|4%UJCoS^jh) z)zI;}-&)>nhQ!>fi|OW07fD{T;(N>G1CO88yXqsGa7|+{Yiq%&1G>SLu#?YP_G}Hq zMe?~#fsyH7Y||J)Ju4y?m*0w&wQEu}980D#jMJZ=z%c#T?R30t7JCNSZ2Wx-ey-i} zu))g+#6P8F-cH2FB3rI2Ok)O2uNSQjBQl$);%|fG6wHbgemxSZw{VMknL5-&oewp89 zPPVV6VcFV>KRWE@r;RUY8*BzMXx29b(=YkDCT+$0b{kYOL)y1~&tZyjWS-U<2p{$X zY>`eW0+b8^!m^|`b8^t1`d+*jc^`Mr4NF>1i%PsQuD|(L8+y$1+~4bcqRrH4d~5*i z8#LbEDb*R{E>??yw)QArary7 zwF-6i#9Q;UvhrA38U7wWeIGOVHlo4ys534jfG>SyBE`_d-vc_u70RMWW;p%Q>CZ%S zRYP2cN_OYVYWTOEDk)= ziQev~B;B4cf@TlSm-Z(uO1?NOlKD*Mw>p$i2W5^*E~(C#ADQmI$SZXJ^=PKj76_}Z z07Vf(*V19R4FnMI7-_~ePs=~WhGXrjA1qa{6ZVwbd z@A{grXIsV+JK>%F&gV1Q(M<$NeiwDO`13jBpH}`7i+rm$%aV$ss#o4F2QNp*VeqCp zm@LMs?<;(aFnU>;2b0o^T7gZ9yRZ`lsm6)f8TgW zM9&TfE-@2d#V10Zq?TGbduq9(oQQqZo;VPC-?xp!GG6qJYyVPwz~Fst#;9i{2zbD0 z3u+?GV}Dub+kb7yR?lzT_vN2NH&Z`GOlGg7A3M*MF+r)gxAwoA_<;O*n1?j0BEQfIWmh8{#G?>$*+hQ4|H3o$cug}T~< zH;|ALUUx=P{wrTBB@=E{eDpbE*$iO}!jK@wkjTYoSY-h6O8m(De1bL(0(8(ZALrvn zMuiQwwnhdZw~TlPq?6LPp>0O*-IBm%Z6(GHyh!jD)9LBy{sJM7?a2!3A?}N{Ig^Cg z*jV3HtqwhB!Suw9JX`-%_3t2YZEc0Pb!M)<@*ujLSH-_DOa5?w8R}HBbCbY%8prrD z91SEq6p_V`&unI*_>nFmRGbc&4(SMw#^KI`oAq2YZ7}#>THnO04$>PH4>Cjxb0U;8 zJQu_CX`>FuO8nhZRM#}9dzF})nP49!U-Q6oDpVWN> z0&yf}Lk*utZUY@Yy8BA*J|XI%RZlwIrEg}H1g|QY74Ri&!5jJN?R8Fg1Ev;}oi zvNv1z$lN`aVjMaPOstj&Rz|EVUb1);LkwP!{N{GJv`@^fVClOryf5zI!3z@JZ$)=^ zch7n;IMS^$M;+7&yVN(A6p{lLz&-F~>QQpYGdQAW67e~iA{NSRn63g}K+m+j$P!oo ztQv5MNc2C?TP045?KIlQn{(-+29{qwV3i7KNXpOu<~-?thWtrtnkEWo>B}i(fzI%P zaJFZj!#p~Z2SdnAIo2>0R-02kkYY>x#_xq5Dit7P3~5j#jpw63Q6F9npvB1gT&|B~ zQ}Wt>c@y7nEa^ymj;QtKpQb;LuSemc zD;^D*UaRDi@`r3ZGU|*WE~pOE{UqbWPb$`qGo6-JXz!pi)dyX)q%!Kn@2xZaP6M%N z)U>P0%3^7zIjzgJW!O9#t9&SJXF>mD@v9j7KnB}X#>IqteTC50uzcM%F*bIu_0I@= zjj<~vYe;1}qFVU+Pp9uYlF!SMj83XUP5OLZ{f;|%iPi(ptn8mbF`a4`Lb9^5Xm8f# z;^X68>TSGu85K0j%Z*I0=?&F5wt{ejoEO`}?$QTh7ndPNu}Mz9JT>hJwWWE@CM5P< z9l>3@+1u25>2y=}rY-lXfMV-sw-j;}Cl1Hd)bxI7bA3HA-Q&&4fc-qURsF!_CiX;=KZfuCQPy4r8+fV z)Nd6lj&Z@7uY6R4^o46SRFe}Q- zJ$7d=W}!n>)zuU`D=vG4f#UCglayKLeWUfk*8p&h&WWPVmz1TNCBCg-o>It&|X zzGZ=}t9Ex~x5yQUnv1>tq`4+^#l8gOG^5DW#7+~JycvuoFEzVLJkK3H!-TLy19Eetv$ho`~oEpQ#FKcD)7tNrZ)kMJN%qAn<5%l1zDHecf~618KGWlq*7+ zZ(-ZsWnWlKShx|orXt7|@oF*rG4o)i812N=jEz2r7T4=%vE;@Q8EY-)9qiSWl}ZT` z2<|SpSbBePzh259v_H(q*3IJzDYRQ+TX+@-)BMojo_Sw*=9+aN9RvdEBaSwKgN`je ze+wkmjF>;td``6|u5$Hd6Ce8CgaL2^lbTUH3B^A&+d%>!wrHy!|Dpqlt4n&1Jhod{ zkLW5q`{Bg-!+Kq8j`#de{Yfac+5Wm&eNXG3xAvzN$|qf*w|*z1&%$6IDp+P_XB!=n z^#I!56E`)Tk7bZ!vr%dz*x48y9Gox>xGAcc%UZJS{}dV7bo^WK_GbRp59vOEbnRm` zDLD)BQH*=bv3_(BMJ2%b`gNKA%_Sg89=q`z+co^)7vU^@sO9HkuNTt@2noOE-;NfF z(7!bESSr+bh=y5*<2=F5Jq7~p@awc7Dr&yvL9mTa?zM^rJR11p z7u4R?R+f8du~o3;_A+v#UAyvo9Gg-?hC%Mj9sukXBkqe`a~R7X(s7k{#!<~A{kn&> z*K&)BMxB^;45WU*ruk!3d|xHab4yOldpW{%w$w~2pQwyuUpU~WM<99f2-e1q8ApSQ z4wO9IJU}BnBf| zshLBQJHL1)CdH1Dff2u3-jdkPV4?-`pP~1a2a1cw<;aK@yU6Nt#LCflS+ey$?<)t~ z1rt|%PrsYv3{#(LZw*CJ0T%_FE6!@1N0QhwR;mk3NaJ^z=l?Djj!)yyvFJ!6d|Bz3 zO%tISu=(dFX*9)42lg>OY9Y^@qTSv4`rc~ZcRweUr%m$3uw)E#StM@{s;|E^8Gg3J zn@L%!U0t=RkFoq3g+PW9qZR1FF4PO)Y6bT}DMkje4`2daXR3 z8Q?pzn~3Y_`ExWBANyd9A$osnoLbCtzdh_xqDX~RVAhM-MAj%nr%1&i-)k383~C>{ zV=dKZwDiK_!Re$NF-Ef3dTk-j4R&U}m<>vg)-oX`<4ab}Yt@9G!Y0iA+d7MLMF^JU zJdMG6zm&C$=z!~`v-toq5S6g!{*-m*_pm(M%-zjR!Tmv@VmVq&Wqi@I+2`XW*za`j z(^mT}J~9V?A6nybtFNi4sjc-K$6oX@5%p9WoZ1CI~e6Ctdi zwC2e!>c45SejSN7J&xlVR<2Usn)+YRp6$WorG#&FJzD1Y*`1=gF~!C#>7B$KS(NNwLQ8>1La{)xPaTd#5lLC^+z%1D*y+nnlHA4aoS zzGz;RjqaJza@Y%-_d6T@CN+RW7PnmYMNv8(Us#C=2EVED-v4?WDmMIiG`x~g;pf|Z zMbpdQXDzpWk{|Q)y}Y~#y)y4X< z!h~)M9>~_`it0HLpWzrNMlx2#zqwF)$!YhdiJGVja6MD=^S9j$(fw{VLbzDyE$5hS zZv1Q{Usx_&bolAUN)yZ+8E>BtUt$G2XE0a(Syn zNc2j-x>j#%FDWIpp!o_ZKok3>YVqivc)m1+6{l(92yyDKi*mA%vY8zb5s~?eL0I!) zLdoaE#7^*+O>J?Z0RKa4v8Argc1EiV75}s`7FkO(MotWgeingr6F}ylYh{f>+g!F* z2STH$9@N;oh+cDmV%~C`_nG?V)=y`=PZR%59m}@&^lENxpcorD}R?At_ zp5Fq#X)PcfcB55Icu-^{{Zn68)~>{gx5_*(c?Od{&~+RAnVFvcZ5)CLmOXXTzk5yj zogrcSe|`dZK<*-SkB*snD>v0~x*Xi++eHX?<*kDGYbHaz?jrr{>YC8ot(ILNf+{ks zVQLATQihpXsq1?xm;i1{Tifq)1HP2i!*qIbI+@tjW>L7er@e zC3ZC@)5|gq2b}tD^VM$q=|=KkTbavJXL7=05a{smuv+Z!K;>xsHJU>t3Cg-hK-630au2h$PA@nee^yT9Zlsokw@UCIGm)3 zvziv;*-l;=?0;XGnXbxRk9Cb)Iv(q?T)SAy#T|hMQUK>VZN#eZTZ`N?BU1)@+bf-& znTX2v1D#?T5N(qU*fuKw7Bux5!7Se;t$YHB8@NuqQnJe55C9}8p-7Bj=Gd@P?UwYE zP{|^4fF4bYip#qi3oTH9Cdz!Og+fCeNJ%iI)JgOc;nC1I0#(tAobM6>4W5>(6|t_k zX&uCc?bF|@-)p9ew7)esH)k8UiJ2OinGxdV267XK^u4Gh=f*|uuOie#Xoeh`8+Q5f zNfL?uzWSu?oFo%Q!t_VpyjHe?hU07>o~5bNaajfeb?JXn6_xn7ez|T9w58am5ufuc zSAW+A9kFJ;_zh_V;-a3f#kRRKyG191s&z3D>A+HtFQRr35or+1)rYk~FMcPn0B1P` ze_v;bYVE-#whzT|4304}Sz z>~5Y8+4aQA5SW&o8rAm!xosSciJ#DSDS&18=e3;uG2JUr{$@&w2XMHRj(o{Hx!7jN zpONsOcZkpmjFZ*+r?wCx?gPwjIrmYo9|*N}Csi9$U(FWDnH!`vRk>H0fPkZq!5jY? z>knf@O;tXTgUgz%3`tL}TbhS6|2dlt)h<}&Y8AI$+P(3i1f~L=8ydQIRW;;(%fos{ zF)zA5o^lA23f^d|NPV?h<_uZwZdfwH;Le4IzI^rS4U8N}+kdy^YZq|PO#Myte`3>q zRe6qk+4KN_PRoNJ!N|Ll;t0}F$X$xB-}qJKF2nC${3|sKi6`vB9+gtSAWz7c65U9x z)r0TU02~XO+|?ZZ|BY~Wj0~Jbd;UDQdn51NdVuJ8cQ=guE5H;TY;E%kI3M20@pRl9 zp-aE~8}yIHfR!R=+`y;fj?k-{4c7av3a#)45HLg%0aDIjc81rBQb?#^x8$H^8wQ-n z0|#ZiqqRVQ-V(5OD*FT3fNyZk!}BjA^27BKwBSV`vR=jtcR&1R0ea4rm3QbkF9Jwz|dE>zK>I7h(@ciukzwZLvncj!N>WlS;C_+8KNh z>SpK)1o~PqD`dTI2la!^)UNuO6*uaf;nW2~68HI5_+(4GibvQ#7h;dW}DbFraDN<}ft@G2i zNc36y=n{X`?->{Xv!Le7^o%CL2VH#A!hL4l;zpc|Ky7#B1i3|} zt>>$GTJfo!R}{Xzs2g0Jj%9@bBdCU+wPK5(s){fPrS_3NV)^JT|KThvELOBXl<| z6d@aG##47S=tz0~Hp!z?iBta;OA>gu6*%t$(wm=~Oa|ylApVkP08_$$fG+qRdsv{M z8=ur(0J-K{pgs(v0E)7rQc@1bMaE8y|EWJDW{U1j&HS0tHQqyi_xB)MNc|#-o^0)o zQ&E?O2AIwSEU3oAsjYZ=6$#p<7;4_!^ag=!W=)VdAP^wLGhH50+N+J`@!Z~5`xjsH z%NyNI{jBRZIilaD#p4J41A>cxHM?%_crfAl<{RbpRpyzD=!q<$?LN%sp1J4=H3X>+ z9Gw4PaT3vJ`Pw1-gz)c5A;12W@48#Scb}`i!*hT9Rf*lKl$IP=rQ{!^)`FwRVuWI( z-ppNESWx6Uv`idDp~yxaF0@Bac22xM@!y&a``43t~$Y92PI z!A?IE(`8;u)tFP=(BSHm08F*la*2$eW*jbvLg{c^H@uF$qG6fy`09s@*$$!;ncxJ3YAK6dxuP4Ac@qzMm_e1oZ{}ZSv_W_1tK7@pc zCn1c}WC*AK1vmf0a-5Bx044+y_xJa&k0{kz-`P=yJz!({Ylk2Z7B)7*7z3jWbTju4 zT>X#!{{!(pQHBF1nRBmGy1uXchr}Jy|A*;rbQ(6moQhzmLk3c=zxro<3?LM69b<-4 zXv0-Xrcb3_LwiEYE2n;rufY#MuI#NM9}f?w8O#P4Jg2?;-#dSO`?P^s@t)v4z;m?T zr}67pI)%XoylYT5^|NyO4%qwSgJq@NG8lgmT_b2uzF=kv6ZGQb{A}lll6m;|EbiT< z7Uj?NuZI05s@!9>-vC!A5S27L{LwJCOmACBG}n>8J5=6weKW)%wJ|dNPvEYVX-&Z1 z-IWOlxb9Y71OPeUs)Fi?2kl>!)xOBUcAoG&)z+Or_q40I_pZick7!b}Nu*P1w-{oR zA68hoJ2mBSS(sn^6M)ZYxf-PQOe3Yo26kY6y`b$71F6tITkl&%9p)}OW(gdvp7L7( zcKYi8u_(Idgfo z2g#dM*Ih|f53Oh)-?F7W$=CxikIAt~0y?&I`N>)M;-iX>Y%UiUA|W{;HhrVlZ91K( zL5gGtjoq20ubkzn_{Ut^d`3m!D1r=634@6#INl;YJ9~*C{wJDXSLz3n8vP<*`Y zk!YR+l913FS{-mZxRVZ=s~g8r5K@9bAa^m6B2l6lJ~VtBNW254LK1cpE(9=DLgUvk zqgM1nz$WBkz7DK~U_rRq_gzRvGoe>J+k+}${~ zmu1!w{guM30hN@`Idge%{fb*$>k zr6u0#M}f&gz~muj{ke0~Rr82?_MYXQ>EvEHmN#C>8Jyf!6G*>zy$WkvRcz#;0f z71p8=lF6l7NvZw#DL%{4fl5Pmt#e1@ggq5C?Zy6R8M;&B>3C>S9R2sgUjR(t03lYg zsa|43x&2KMrBjRVSOR%5K_DrAl(q69m4iEP542HUlZ;uxiVpX_Cu0FBbJ#s<3GXO8 zU(`g;-(AM%Y-9ccRj`6=Tl#q*phGSZLvk0r9m%WjmJ8L)LU$4-tgWpyKdcHweS78b z;&_y|a^Zsai#b;71`}50Ll^?@9$Nc~n{ky#TYb5E=HdL6DK@I@Y<%m{k8zxGe&DPq z=3-GviGGytIHd(wQTU?5#Lo ziT-$VwS9U`j=fz6Yb3kq(QXgS%E%gU5rlxp?#Bl+>o{F4-#qIRCTa){=k9a0ujGPh zWt?CZ_#+bwyt5Ks8J@65Y%tJdlBK3{fW)^8Z!C0;3=NE?&u6Sn1<$w0M}d`k-9>B~ zswP$_V?(hjmVo3J;Z$8vmI4?VS48DE1nyD^oqGNwEw6AQOP7561-?*>2Yl9a81J64 zw5_f^@%?rwArpJsa3N;pkH*I2?Ninu1mHEWV3M}7*+^X{BNM&;BeXt^c@M3ed#%Kf zcnzsW1;0QdODlz%HyHSYQIBt>nUT(Qk0jrjeM#$2W7iU!6dS!AKNu&^{1EZ}u8IwO zO#s2IoCwy|#(lc+YD|NU$^Jekt8fMrh61Yqc^z~3EFjC{J{i!vbzZJVJM2&9gZo`$ zjz8~i;VZZX4Rd`0J8mH#puGo@_)Nq7VZZxl*NVEg&h!0)zdCRUYk#^>wQ>AF9O1Qb z*kgSDwsGfdG?YRZ;+H(N)p4GRi!(?wyl238XC_W+#{ktTQs%8f;VR|zl2Ux(`TnZw zWhFS%6^;18%R~kv1(yw0zB$zOUPfw1UM-m!S;(4~GUB&!j3TYpI^$odJi-fQ)W=i! zR?31K)cpupSmYR4*6a88rXinDkvsfP>@*DBUn#1s=MC6eh2QGaRLR)n?zZqaokev%cv4C79H!2OB0r2&?1FW8?CnL%-=hRUe=%JVxpKXq~t zNRcHHBXNmdr#D$FrIJ}y9gsw*+kbTbVA$jA>l(Cg$g8f&Cjyuu`g>wDMn2{VHo+i8 zi&IvT`<7*Qaa-PdvoB?>>l?+0I)Mw`pPepg55P)$Lb-O&rS!~qrL9aXSu9h+V&@^B ztokC~zVhPTi|iEs=20PflbD~EpAX{T{aG#T2A~Ng;Fsde`7?aZ+F*y!L(C}nO&rcs ziV^$w@5JfHIp1Nil)isP?4sB8v63yf%y^(xG9^BKd1F)r?dc`EB#t_dvif8F*x+x& z-RB=aeQSBZs0_Fw`S1ra2lgem0dsaM{3>JI+S=O6*#X*POnCWpAK&j&VUPComltBS zqTRzHdzfGmZ|pD*i5Opz)~?oqD*BDjq~Sho1=+l}Ny&YgpZ}tqF*Nw+pY?NjIhYR} zRa(uX5V~%+zq2fO%`G_ljvhP($(NHPN`BDL>+S7v?k!&|o!QRH$7!V7N9#HgmgE0b z#)8jMN`de8yRf^Mo~g>rvWuu9>c+ys!*FU&@I5hT8cX@zsRu#}z-AepK<)tOv>n zoTt*+Z^)5zv#BgZ6keadKN!>tiDW5b>5~b`c8$H2#p~ltdw@r%^jn2HQq3@Pdbpha z!*^#6lC&>rfTI!+<95!v$(Guq`ux~yL6sqh?hclZ?sd78vna-KbBg}v8-C7Qo(l=j zSJTwieypv{bN3ISB~=%OviQB-z0XQ9#6m$9g!j73m8wtuynC-3Z3z0n?dSN?x7Tm= zhQjlIgiFqRd#0ef-qX5#zC77IxqtC3kPF2%;B;{rI`sX-jlMhXE+<*VR3||X1j?L@ zAFgjuBV`X162bb3mI`r}3bB^@iH~K}No2IOl)o(&ZCcfQo2|Ec6td3c>HO(iS7YlD z8v92y_Cv|z&?T8DQXtgj53~@7G9%waj_+8>Bs|vFu~bN8)Q`(?*2BYW`zHQ^gZv9w zCKYWu88rnnvs#aYyP(H>RWTPIz4|F%KF~Bc z^RJL~25Jw<#H_y~m9&+0B`?9tG}3;lJ+)@uP#PTt>q&b!&C zOI<=@YGL8QVGkH10o&{LmB1mVp`oFq#o72veOHBaqb7I6qHbj(X|=ta^^IsG)-)y%zVWdiprGwkA)42mge2Dqa;Z ze^%*;jcaBw#TbYuK3 z$?jaVS3A=rS%~&ycS}#px3vK&Y4q%?mMc$|pRBGd1Mc6NpDT?hsm-w_OsllxhXItT zPopX-!Ioi^JPhM;#3!_fFC&$9HCfVvu^-wN-oD`Y8xU^LZl7gtk(2EX^$zy-_DaUe z#>k-Ed2fOcGy9-)vsO02G!J-RY;1gT>PByWPwzl)cdxTmeHvRRa-mcRurZ`n4FEB8 zQJ*4xuS}c0N>5JkcQe<{m8^ACbR1Q5JaU=)R@|l>01IHl;{~)Yk>j}k@qU_MD|yK4 z69)9*=fiXI{*^!VbBV>kQ?|T3KHHD$mfJg$C?L z7GlrXwORr>t3+m@=gc)6HKlYVrF6jW8oKAqe1Pd!3Q^sezFxgBHN1bL?Xx=)V>mec zfh5Xvd#6P7bmdI+(jpH0mQ40fJAt)q2s2k(@`7tU(8stkEKB*mb>{}MnE~0PPw~WP z0z#J}ixYNwKK|GMFBI6I92_)cwA7U3zBM#YC%XRWPG%?j0++qhF>|5k8>+)C)|3~oEZAFOnFuNJ&^%G zXbq#`Eu2@72Z6c78n{A$%|GCDOqb2(^y>FFXAWXKK#V~P@3LNXekohI=m2h+%-2;N zdL)9wWS=j@JX636L0fvX#IV#B%peuqh7N2lzu>jY!LulpPbwJCf4RQUCPkBo$pcI; zk(jElZkya|>LO$1`}YoJ#c&aTe|%Sfe<)`fd6vy1OhGg5E>42T8F1x+EQRM|zvD6* zH6_|Tl8qnVA@%TNwM?#^?(cFePo)u~eDeX&A;df-1!bC#`*9s7`6g=BD-j+!URjhv zlr@e-=4aW4(|jjc3{EV3;kI}Ed)~g@zk&{jdz75?rbzDDcg^?1dN1SXv88^F9E^O6 zJz5*g6q;ERo7oeCHN$M&Y=k*N7J>V4^1-6A3oPW1x4Spad7%Y^Ltr z4ym5qSkm)n>~dj%N`+g_7p`choG-?x`s5@7D_FWV)wR~=yE(hcj4VCBnEgb5mkTMU z50(5GTNVpl^YIyU&pd#6WNvLz3iCZ7!&m$c?RMQYa`xN-)!6z2p8x_jjf#z53Jdcxv zN4&N;PGBO&`}B!0lXCqea|^O3;CHAr)9O4g?M&ZPB^wigKMGl1>|~n6gC-T76gM<( zhyUeI@v!2Enh5>^houh<^i+Cm-R+UOwhl=$)-tCe=LuNu7p&t)IVyszAQy+e-Pz8L ziNWBEqw7uO0k)-#=%vRIZB%ny=kQeDVdA{|7F>si9rwh!!)f`hzQQ2RibjX2pcQbA$N}ShtAP5ZHC}%nXVoGOzk8*FMvbY9@kZbV&5aS*=8h-5HsZ z9!BPoTf8=Ht#H7pxCn3ydGm0?xk!~lnJ4CZb?$4B0iv?a>C)sJ*#cjoTD4k58hH7b z`NzIKO$pQb3Gp%g|T;@B%Jtm91eZ_$`NR(4hTYl!^-2!;z9py5NAw!Ggz} zT=RZe>roJ0_^}*`%ug#)hf2!oR{T{^W~UmfpN9gLtZZn@QFegf@#;@Z(PZ6XlYj}< zOmlr+5+ckt3H?Npo5wQXEbjs7+~1#Aq<6OExp)gg$=2v^KdCL(zeQmB8L+DpOUIG| z4i!Uvw8QZJr#CpbAEeeou?26ayHx!=vzBUGTC9cnJr;WDy1x@`a+iCWkumWx?1hhQ zl{6fNoFo}*Yi#$pf`N3N<5MnVHJQ;;CXPNAI$BrMSt%$-B4{pQ7fUqb;<%$X#0x5A z>2qmBTEQl%iBj9lV3Q6+sXye7a~P`VDU=Lb$w40pqQgz2l|LY?RYXpQ0uT$C1M)>v z>h-=WV%z8A2}#D7pjdUw{KD=)wYQuc0=%yU1w{op1%*WgL`4N?2rwD-v*1Cmt4!p! z?Oy#(WOJE|aX>y}!x3g*4|3?Prkl3srGO4RA-C$}pkwY`KmT!SbXFEm>+5{j{|Crn80it@Jw!rV*t|kvO0YRe0+%Cx#c( zWFged5CP%(@a!;`2m+gw(;3}>o1N#)0@2S$rA>5DcWdq3;w_6k?BI^pj23zKdfz~2 z`RxmNi$p@A%stvoS_*Osa!R6*p!|GUC41iL5`BAgUOV}!x0B-|v=SsTBTUow=F&wn zsROxv)h{OXqO`TZo|Zqd;Wx-=)VcpmL(|$&2;*@igCfr8h9X9YnY2kxUrO%xCWZqR zVlS5}Yln*vN~cM+UDlUed@4fqxV%46V$f&?6Z4modCRTfDRzhja?W{V_R1eTJ zg&B94EDi?&PUE^85V?>ilj|oB5;P#6;?Z}xu(WX!jB$2eQ$GA;f&U>Y?@9a@lCH`2 z$=e&$JdMBI)5iIL6E&pu-X&Gn0d6Mo*5ijas!g}R<*INJ9IOzzP{4~_TzPwYH(X&~ z0re$71DakNnm#VVU9KUNMjTA2tjKYysuCFqVrdBy%v9x^mOQPQZQjtf)zTSiBEL5e zW6h>YNkqkjoy#kBnt=7AdSF)}g9B&eQZbdmf_|?hI{1YctiAjX4ucjO)2`DBsgfAl za8Ui_EY#SAFW$VM!c+XF#t+z-hyS_nt=jx>_eE^jNGS=yd-RRsHzcWV4VUzNmd5S` zW>Rks){%WObX32=qGPa#$<>)AuU04>#V( zI@n13fm}7|!y|e9DaY(nZQw^CGO3hz;k*O`9Idr|SsD%;;@_Y1y=vndBFrFMc;T=N zoVs4p(;Ke)N+7ruq&W*g(H(yvO6SKh~?>k78 zbzk%Q`d^9pt}G`%dO1Z|MA3DK_FU~~)UhHWYp1LPbu*R}K=36;krqa&Z(&m{nw*=T z7f!E=aVI8N`i`LA>M~GjfDfi^3ruvBQ4QhBW(wV97pXuT9OjWaVD(5^y>bKU6v5kI zQ?c7dx|-Uo~2 z4db5UbEONNRdeoj`gK8chu&K9h|g^!?*_exA*E`Y{KqzwDS(|n#8D=SCd%B|5@0(W{be)uR9$j&kXDwVRm9`x{b3G0PpH*=aN zSEi`3LXUw`g0`}>7n4A0fZY8$(vnxAM`918*Hn8ZtN!~Hw|!=?R+1Al7E^gE5Ot(g z;qZBmw6?asdgt>l@2jB-*BFG@CYQ;(%zxnzFIn{iEc#rmTzJ3S}-JG{Xj$zMXmJe z6gDs9A2LrAviF)Y;qfAr+Fl>L_`+{E+^dbx5vgw3K1uhUSQ}xfPmxKr2Uz_G!ar*% z`!qu{ID1W7Sy`cpEX3Hje0BBEkXck`<0YH>BsD=>e#*nd9A|zKyim!7w?IY=RtP_@ zkCe4R_7yR^L+p;fpRDlBGAdrh ze-V4@5b^McT^WID$-Mn~P&33>;S-Y2>BP0I_{u7at>0~&*yPU#`8E!AoJF3bfWpP; z$hVCH9nGPP7G}@;-64gkwzlfpH@pnuKUx@x)2PTc#r#%B<=|r8d)ZYn`m7Z41z1FFCYKOf{-x9>UC_nIBl9NRNRQ}k6Z~5lnR&o6| zx*G96n92x7eWE*zBqIQ%s>Xi@n5B$8<%};~rN4obQwA^Tm<1R0$F!7nmVvBGRG_oG z5U4bt^!6o^vOp)(g`Fk`WY>u}>?*X^wzZP`a4+o(JBIwsq9kR33#y-yVi&t50k=Ub z&3Pj)_k$3-;VRB~hW$VS${>|-ZjNLMS^y4vzcM09 zLL|p7ID>0BH{_Cw`w4VIbgnOf+XVmoT>!)`3C2?v1!HTWdC!{f7dui-!6+>);KYz( zbfe=}cu<_!)HPh!#3P|^VbSaVq3Nrms(PQVFCigFD*UuRvxdvRHx38#evA*T6Gcy?h^$A?UehreS8W|AVmP ztE}|WocpAh|1mJF}Ib9{zNA z4pL~~w43Gep1Lty((lM(>p2zxIa^)vck^+2X8liz+2*HqbbJc76~Q?C64{tlV(Eb( zebqUDMd!dsX{p#TY^fdozMf}_%T9?ThtR+AA?Z%gOCy=TgSg{!~sJNAV(*G1SxtJ870+9GGkM0Q5>~2RJr@7XOisa&8!`V#wSG;Hi^v; zfQ;!nNLDoHdK{6{pzdQf`XaZ-gOQ0E3_BN(7FGaPW$A7YC8bkSqlQcH4e|xtq({lH zZ*MWtK&7_L#Z}$B=}#0pR>}0a?U%`jHe(|pgFl1J^SPAaYL@jkwq%pjcZ9y2HlqKr zxBnwVc{#T)PVn&wkV9c3%U}<;q_qIzRGNF@Zz+?93Oyz#*0=tlKN<4s>+-?Tv2Kriu~{B(o!A%I$F>A`$2pn3 zDSfOJdM>->2LMEOV0N%)ez0e{XKJyxYrY5k4WM_xV`^dEo(VK~V0mzt_j7DF-*XO0 zlfR*!AG{VjbGdpYpyWzsT;DUhTh-Rn)ueyVX-z3X!ORNgp=GxTcL86lm}&>LmM)Jp zx5JXc(|Y%i7*mQ4tJRlAz)0xW63q z6B`ao8ehK{%bSh1kklSTh!5fr98enz5U99Jc@(ivq)>(gZDF`FDBby)lH!A=BFgX> zu}-7d-WE#I3loHAU0)ndVJ((3i=ts03_<%V2xHbCsDEY4=2={%q!L5D^Byv>^2?4* ze{9?4y!9mv0%73PR4Svp+QQOBi9&nZ9xrr@M{qPLD<{Dq)j%~>BQYl-CnX^#BP%5% z>l+6jAKRdjyL8?4YsB3v^iLE?rr+euU<_##%-m5!?Q-XBRi!)b?x)E7=vZ!s8)UR? z1}jeGlJUq7EdE$?%WVQqkjcfrU5b})IBLitL7das8Cj!_nUpgBnQZv-a`FdVb6 zPQiGLJ+CuDBRvfq_5p$y-%D2Kiv`sA{nzS^hRZg2efPB4qirh}<^l@62K$98!h_Ce z3LdseX?aUEDs^(AqiI_sF{y(_cG09`71uA36-+?1u!3|Ipkck}MVqBr8Y)o4yRC}K z{XPqqu(wsXlOT8a@L8E+)~jdkebzsHt{Z$g2}S;6~b>FNU(i_?iqQBjK1QHs+@1x%Nfl(6OUC30rL zb6W_#<9a`=@(SlkB#u3hH;Y)E5xU;GmmSz4s*C|t@0}T5i}nzY8p(!X%D*h1+$agK z6|Z@6au=p896P{THtuo+`8;c;(g=Mbz@S^bGf;Qrf5i@nsEw!vu#ewVL}(DSh+=2W z(!3uv7d{5wi$~ecdI3KS&qiFE?b}Rt8j~d6japvRa98{5>U1RWD>|GGP|5NAUQ%^* z7OEMt4vflROea7bgDMVA#NtQjm7#d-BC$AVJI=~}_8J>t!ZRL&tbdf81)1ndEwd=G zDNgVV=tP=rAe>N+s_;qChk2zDevHfq$HZn{x;MFI9v+>Y^DM6UHkCi=x@$JY$UsvTNk~dL8Ug(T;F~x8`|qRP<9N%cTB^AUB>}5r z&)!Yz?HxZ_&vQoi%?0#_DQ##W7`vf4ZitQs{q`TJtinl zN?S?_4(=!1kMwk72I`5xwbY4A`t=S(vmD#~@2Yy8=d##rk^^4LL#YJ!bIhJl?PY?E zdKFM%&#lQzEbO+&G;!4{x6_hO*2av(qthf9IurO)eCMkCmDM4YiNT7WnOOKkU!iMl zKhDg`*4Wn6)&qE4d|Zs3_-D-Mf$AJUa-ydK4F7B#TpZe85S*&1hM2X+8mPTSlqu@_ zJ`C2?*WENH!C2&^4s~7{01%uJtuU#?)o$KzNwMJiRP%{$Jbm}^S-`_@>QPD-J z)6;1tr|85TjU;}2I5Tm0-JgGrs*GSfDyiAv8v${GHCr&N>7Y^|Ke8L=KqRo%**q zbvOmkoPY2C-tT&la^r*9rAO;3eqY`9-{boq995|#teXw9Oo-u^J=x8OCmj;R-+S8r&gG@ zT1ZSsnuUx)ovr{3DPVJViwphb*u6W|HJAk0PW5{hMK)AmXMx~?cEmvPDMp!yoW)X- zsnC+iUN4|ET+a-3dP{!C3>aDYYk5b04x4TVK42wSQfW4tRWy?T@E_nvMutb;{RFNi zv=0t0=%ufq4>(xt03ZvPG83YPiN(`@bMo+TX{j&iJkV_}l)j$sE_ON7YhG1xv@_FJ z$o_-!%L&3EF&K@)72`1NlK%d}CLf>Z1^eI*;agA4EC*e))w8$MwcRlNyfRlL*}MzV zr%9>#bX?1gzVJRBIC-k{veIH?{sQ7#_}T?}T;zzyKk{9Bsk4bwjrKA9p}}bnp~$a4 zM8oO79Ux2)@jdiR4ds@V0>lo{T1jlyb8boXijxL8lUH&|)uq1rzLwf|UG2J#!WK&j zKZUMwb0MI7ANV%O`8I8_)J!Py2{d=~Zm&#jQMb`NX!Iz5ivbyQrj5g9P&Yk}6n@Ff z6}t2zlQ?uF+@0(nYzju;$dpt4646b94#5&~pxxHTFePq)2NPtX5#nRhQBKPHmG|1y z70~nA^Q))!Qn_VV?@5(+~+>v4OAb z*ac=7bn_TAqt1u3E@Xoz@7M_{R7bTT#+fjo%`2)F)MzS9@b!EWBJ@WN_M|P9x zONN{uR${!?Bi`+chnwcjM?LnM%(;9W^rkJSD!(2(N!l&!nf*bY5522+$|8k>652(gS6aI?m0|N?4Bfkp&#Z z3sjW}J`G{+C`4@6E~C2i>xhAQ{gP&Kfn5l|=$7SA{-yl}_(fgeZ!bBN}&;-a?K8J*0i88An60BH5@f! zyi-3; z(%NeD{)AVnxUO&qs_AM|H<6{u`bbli)KfTRIB&Occs}`gZJ0cssMB`3nb!Doo89+) zyy#H)Mo#=?>WG-v0K7a-KPnFt^TSWE&az`6rR(IDXE+i^eE#PEa3dud1|v+d{O|DJ z6V3X|X8#ifc3*@ywx^@(5kR-B)GV}5=zQPT*Z&@;vDv`qjp&v077Gb-=nM_zsQUv% zGeQZVO>N%}mD4CD4M&Abm&8$1HszrY12}i3*uNiBk-&;_3RCto@s!gRAK*}7_e8Y;kGL9|#F6Eh1KNQyC?-I3&_j?MyuYdQqMu2-t zQP4Lqs=jNwKNPtt^moy>>BRPJ2`UB$m=BP&1pG0jCH#y}awNDi&ow6{7h9hRURB&s zc3#=%1LQvxwaTt3K43CmqmUS@knG6`2#)a$4YIMpSJW@jve$2u>)r#}@r4h*8@N`n zon0Mw^J-@$C4*;UXck%K?oua1RIMnHKS2VAMpUOk#8d%T;z$_Rq(5P)Fh~JGcr7Ja z1gX3GLd;6m`H`c*S$TO|dE08I+G4v2E1M{~5i2<>>tt(lnZ26(fkyMhFc|n5b5?lX zehXpyDppjcUE(ST684s2uuHNqQEh%ovJzix@30DN`|*iZ_QXN`_sN5RM?L4tFk&vx zaoVv_4rQN?yF;h%&3NS*gSaWk=7i2c-xFHm@aEy^Tbe4}%;9)+y7%pXF-O4fe7ANs z?3N_kQjyZ?DvRX#ru*6dZ2)rn2)C|m*BI@n*{pIbe?cSN*k>x99qMAV6+V2$oC@(db9BpUk;ucQ%Y&|I?mRu|2lI6dc zLWk`4UOFqCXDcYp=j+`j$T3kRj4$gB&jsHsn79ZkNs;&~>oceeP}bUD*jGPZp1QeT zj3Dcu_c91*Ivzbc9Sb&?TT%UvRxky9&d+Lad$$ZXUvX+f$`H5mY5d)iU9%$FfECf- z0oMtCec(X*RDQN&FM^$3^=N|C`4rnRHStP*ZK|!8 zY7uUB=lyDH!a*!CKc@iC&eLm##`^un>%34q%i9*5d3)0`gq`2_@p@;XucQ~rD@9Sz z+3?(ua3B~%)@no}=Ge}`){efKaZSI)*ZEa4{@f`~w<}z!WGO2>H9I>qJ3V#Z2L0bh zPXb2@v>n>`v0J#VsvvEI>G{-gH3E^qj0}Ril#8>BB_Te6gQwTtmxpq$^=`kD$I6F{ zyOeW=Y6b^82gYi~jaK(^ZicxO{pTy8d&mWekPCSv`Rrol{w4-0Z0AFhv?Wb;ISt2> z*sR43Y`aQZd*=ozu#ls-tOt+f%4EPRx~gox{n)aIiw8HcZi~u&y7iBh*lsd&J0%mv zn3M6vD&ZQ!?u(KvSJT2q>+S8?!wQ%s5DAGOgFwg=n2z>%nQyFAqbrr1^LZ`B)$e$o z8DLY);&yV{IDIu=ZSlT1@4jDrdq#BVtc&WHS6@*W&&ba9TlKtGO;vn`V?!iR?t>zk z#7*0#q9XRay7_zCnVhZ9^xo)@P@>nQ3&16tAG!J4SlM0PcIt518*~f!dmC99x%!%! zIQu$#moKiI-*|V57|{PD(}>&suvLbu0}0z0!E6FgM~_FZ+}SGvJ3i#bAx0fdU0!De zkW6UR0d!*5`{9RMyWNs$?G1B-;MVa2UJ~f! zlimg@k=7VO>Qa^gN50LQ(#7rmUkt4gA(kZP2ju;nc*hb?_(fkF#ad$3fg?lz+}!y{ zBr;_@l2ktK=`>9}9fg@-(ue{`1O?4xPFMKPyA-g4Q#w^Mu6<=yC95Q$u)-Tq<`>|B z_7+VR%G|z6#k{MxDL>oN9zPObMF;FEDhJ1$KF$6OVYr6t(9YgrRYzNM4OlI`)M&=O zLTt-a&9J83;<K}qP#($adG*UWm){oGe(_z1 z2+sY?njP7D=?QZ^ujOEbseif%`__k_2fBAA?Ya z&}N4u!StzjC!g>tqPkjyCcZszs3q?rlGP_V(D{6oG$H=&Uw50(NT-mbe%aG-0`Wn;n>>vz2$eztDM4t*#A#QY+YMfJ|Asli4SOkbx8*nFU%Unucmh| zS-F=zvS zPsEq+ZLDl0I;q#*f7VWVp%Rwp#9@?b!S37>Z6j@N1ph5Le6_^x>m@AD3}}0R%{WWR z1Vg%f>etTpik~+OXP1q5YMg&D*dRT|G}@h}S1=l{+}E7O4!*E}WB+XrB2(8O$CDzV zjh#C*FMlilhChz+hhqJD_j-UW#k^$y(>a4JdXrkO&P!!GiaX$!IDpnNZ0Us`hq3kwW>50GLGeh?&hFIgzP%w z_RTBSjoaL}{7fW>C?F8HR)kek%b=_C$4SA;;=#ov;E)csl@51J-a%)6-5a=n6yjY{ z3y-djzvBhZVyj?h4ZD>K;N5O?-cFQP10(5*UH&)wNp-7Ol-)O@{&yPwuZw`0`J$O($%d5>7NKkb2X?R2h&jO&d!Bfg>daLt~1NLuA+kj^Qe6op$)WmctrGto=u ztv%grfSA|j!Lk?gR&XPg!OpyY`&6bzEsUwX(&YB>`v>9C*p_g|JE`q5eKqn9_}$_k z8aYU?SfXuN=d0IUNsW6Vth-uh9h?6GC=L{|Nl-)GFE-SNQR82;9#%k8;RKcD<JOJ=f!J1a^jMa(nSY^-Oj8r41 z^SNf%jd0+}0^Qcm!S1f{g@9g|x7WaR?H|cN}QbSzaw*qa36-qr;Xwo4Ptl7S< ztsnYVY2yICM4nS$iK*Pb*4Ioni_QhCSB=T_KuKv`g}L6heD}-^)*0u7MGF-p`Yj>W z&ux2}Blu&#zqMpi$oY2nA;=lAfcAOwR_#Pw{FOe*GPVn_qvM)s6kFm)j}chebEahGx}|vM8dL1_MeB$%<-loYn!Hzlo;i zxxU@&J@#lobwoacFr+sTpDgaEtlFAti-|if5T(!wd3>M8wr5_7I9SGb$K$=D?v^Xh zP2KC(PWz1rC8m!|ESj2{*53{zDQFt!Zw>c_&QX27f_JbndE)2C*0T>D{4nlCa3Ikw zOZKk7c`P(9Ebe)!^qCH&9eev>z(@a{wsLPjJD=;H6St_FJ#WgWG+%w+XK7;7xqK2D z{@?7}lzsD;BANJWug>xDaY`AFTh?@jY9`?E^*HJ4zPst(uv6?e!nqH)cnJ3BGxa!w z%#(CKXb+gmJ(Zw8OBp=Ozpy>+&~7{D@a+GEE#-l6N-_@|9c#jjhYqT-^2Ml0I1@D~ z4s0toIBT}q94bA1n*PuaVS&{m_m-=-~%2CIKX-iNF%4Fh>e&7wt16RzkF}KGW2~K1!K; zr}v;CTsCm8w1VMGTz#xW010_dPmPct6pxHDGM_e*nwq+fo&XggBO{|zpJ|j8)Z%k~ zge|22Q64K2a?m@!Y2NU9i@v$CN=1q{xZh^V67-`+rPx5f8kR{4#LNuTH9t;eBwv!T zbosPWu*4rfl8d_;!*=Fe8;4DkH-7eBi+<8HP(5&PArx>-(ek-SimD3Kwl3m~9XnL7;~Zxjv^D2wN%^Ol%PQ$qi#L zQ)nOPM;YIZPWKfbFy*%KJV64LfwiC5xX&Q*yBi89sGoIqTty1{VQzgZ^pf><>VLoS zHaxTZ!g8gdW48koGo8!`sLJxwr=I!q$xH-ev^zm5^HP5E1Mq%V3MVt{n$1V->naA)~=)<4ec}H9m{V0r`tZie?sjv#fq@OWVu;qMeX(wRWP;2lQB3~W7%~mMffqK zEYujcQ0*thWT=eCF1fY{y2#9D_JJ}QT@DfHC4O>*igIh;;`9aO4^1@_aPq-Hx1iKl z`0)$POKiGf4Kk0p9SMwi!qcoRe<@(IK4f_dcx`KD8Bv*fwKm5UC-GB-`jg5k9{hvzS+JsI&#P`Im-}i*f z;-ZfcTXO0cVT6j@hv0U(+(44v{;m0QnSZh_w=0~_Tt_hhn`t5ZK{7~<|7JWnnG%UL zkYC#ykeJi!eVR6E{=+c_lvvCDt(WvV9DNgSoWIMnU&!&$l#s)RNonpNM5x*{G0Wrm z-|+0`HSwV9Kq`7Id(OX_mZS+V=JUgPOSkcQg?Oa|qD+dB}P#Cs|bdhMR2_kDvrUHIRx z7uBa&lpOSuQc9;gLWN#KHvA5jS4dV{dF>gk%PVJq81u`F-2@e*h5HtE5|^m^S9oJA z$VI_Qz6HzE8j1B+L7d2#1}aDy@15U2!_?5F^HWuKUQ}37Bc z_W7(Ufk}$&KMno$g$?&AxmeXjaf}|ZjV3YwdZ(=i&bSw)8&2a!PtL}UrKNS2`MBmu zKcK0Ky~}t0&Zli&J~qvMb{*3GkowJ@A z1~^p98#vP?hb^^G9L1Ku6xda?vS>sfd_O`>@^EYKB(i8B>r|QL>2)_fV9hR;I9c^B zPxvhBFz3iKXg(isDc}2-tOAOZ3qv5A_Q7Oh>i40gEkP{!ZbD2rRQTtg)v>Mu#MreK zXF#WL*DQQ9*?GMO)H(W|FAfesOX`2LZa7s^8FSz*9e&_Fsqb;VlYaq-zJ>0`*qGWCZ50*d<=*+_a)_l| z$spcHOP4!V@%lwQ9qSROqd<^p4RoI;5Wzfk(Q3%NO18JXE>$yfv)VpcC%`o7S0?m^LS-MQ8Dyp4DK3iLB8wzad zOMzV$u~fom?gV`*JTpb5`>}K$!tkXBj8WrZE9OBcr?d3q-||PhL28)Hi|FvozUAw| zQ>=B%=e(D&Why1#M`N8$5@^PczF<9|N~tbVS|_BYTU^)H);&$!6Ht#Z_}gO&1A(;Qk0oc zTTW!V!dK=?U9>}1(UlwIU*UcQ1MNhEwx4CsKld*0vdpG^NOB=YSqhnAsl3)7xHII} zKKbS1lu>{r`ApWfy}Y`>7%cnUSwfzFlCR}L2}e?HE}6S*;NZvB^?(Hve@Zd3iRyam zp#*TK!2V{YC;gfXDNP-_^#4M&SU;t_WpaeNwU!?SIYjsJ*7~)~yZ#}xdRmKNt3n@$ zJH(d){^m|@%}W%{2~WMu+jQD2!&YB)N)p)RXMpl)#Tj4c$1yP!(LVU?KbU&i?tM>L-W%;8w*K>L3u6wGn-01snoZxs&K-1!9sU48c3J0 zLP}ZRcB?Zo0Yo(fh3R~I!Ma?&IepgKC^(K?p6x3)!+XElv4WqKqV|j_0vkKC{wJa0 zD3_GA0$t{z8Egd=*zU{YF&Cb}7*k~OR+3ifMOw+lR`JD0!v!UNJ1KsnyaEOIMf5T% zEOsrn{cMINT-q9mJG?1&_;v}JJgid4hga=P2hNNWQCsO?;-bPcfrBE#&eAkKrLLr@ zWun5NvsZ$seX2qC@|FSIUYOH0pRmD>+~-4=Qa#oERhVq4Eg?n(Qv6E%KU*(h=;wU2 ze1=z1WaL==+>zbSX*o27jOI4pq~+bh-bUf6<}&frV3th^cF#eFu|ctKhh7qRxUY$+ z>LuP?2l6h|utKX6>*h53`J~(B9>{WVJ9MT{1=H0V0l84nKlkp}yYAZugE^IIYSv0W=hF6Zz!^CY`lDHv_)9JwOeLn5Exw()_?T`C(wBNxRX&1} zN~E2`!`k}V3>Qe7Ji*`4Rbqp$DgDEp8))6`GWx)TTm*(FZ)KGfe*ssTDJSW~_$hi| zoaPN0rSwo5_JIO1e5AcE5s~GV!|2H$e#f`lMgP|8ZgCzk8lryqJypF) zEJg2XE6xwG*&fvN*J13^;^*QTzaG_Ol+~WCMxlD{K__2YIhU6IR8B^Alz9CwHv#y- zLLdgdwlWF)VXLTY0({Mw@K4YdX#xJ|V&ZNH_Xv*!yt^`7L*JZ{pn&W-%-v^4XLDl< z`MGdzHWmVeyb_*HtMls#R@r)b`FeKw1WNf(nfStCp!<8>BzfC%aSx@b>u76e?c~8j zL+JBMr%rTIbuP*Ed51yOg;KF#{;|+m>ujbKeRDfhR#dDMj8KFS<-QZC<95kP7)hhC zu5H^S7dxj<%SG}GI#eL4qR7OUv2MC?XjGl6m_0ttS;}cv zj59swm zJ`7}N)PJDMsoeIz2qaFQt|6wim|`3mD#SFsP;jTUoTusixH}5%8;efUR}1> zfr1$+NVTtJn^v>Iln->9z4VvgcPM4F`UF%@SGJf-Kfg6OKXv(AZfsrF2Zyt*Ezu_0NH_~rBFIBqhpwoP<$fQVa?2qk&Q$xo+PvM6 zJSP-9#eSIG1%PqLf>-qh0qT}V*Ma|S+N7txfQ++U`~O}`1jE+e0WGYf{v*ey{`f`@oKv-4 z-jqf0_cr)3>8GFk95$fn6GWf8>9p8vUDNe=+`o`Xg9d`P@&lIHIpVxhDf>*Nzr4(R zGkA4)8-077`=zod*w$9pR5!nJ3;_9lLNeIrY?CQ(uvBGIo8%f*%NYbSW;;dw(_x~( z#roitO4RetiWYh0SG0a`FE9gg-DlSHVs(0cTNa@sYb=E&A=I>kC21-EauO~-PLn3T z_*`mZ1p;yVANQKNkKigem}!&XfGBtGx3EjlvzEqpfL01PF7vJnfK9ooGELLf`CJ$? zH{h~fDYvD`m&k-4!80g<>o}Q_otb7S4^ALq1W?8+_#)(-^o-;2*+lb1bN1O^S%tc4 zG&1o@>7CE32d_u|H$qvwUXKNQT%66E0KwG>5F1m9^@*}3;7Cbp77S_ZC8ZHm_GZGk zb*em;gC~2W4@ygrm;L^UqRP3wPs>TZQp4y* znegQ*q7%P}T?V@}KcN0;s!I&u_|Sr+Bdn;`wsYr8F__=)ObXnENI?y)pLTUR(FTdr z4m>+Ws<{q}tVWj1Zl)x#hkUfq;1oS+93gnHtfs;+NaSME;%n9oXcNTR+6IJaIg)=* z8`=VzFv2qz2xv|43$!HJn4p%)$^h;l`h86N_B)PjA`D8<=FHOQ+b;Us(c8n@^XNd* zfaEI9Y}TWo19T$Mf0}|+!vuq?Upy+ipk{G>tR(j~Yhy*3sI#X_b*nBk&|UHi?ZF6A z$Zzz2-X@^monWx87spMb*xf7IhjDRYjQMt+)XEzl_? zi8O4vd~(+^wcD3`t)<9Ksfxn!bs$rb^{Ym?y73AjWY1F{M{*E73y(&KEo+H3eD~D+ zI1;HYf;ZuFy1ABQy(%xzl>y;&IG!C?8Nf}5mH>=9aX@q4UP<_!x0c2$qsJ@9&t7lc zx(C%{q+QE46#tCx@TXr`JP7UWrs z_6GX+KeCszeO9zaJ){L!XEOejNy&j!mdy{seWo>yokhS$9^8+Qpp$Yg)XuTZ7)$gT ztIM-g;R$P{xQu@LWP%sVf&%L9c7OR$dJUazf+i8ky|nhAB41K11%J@SOU>OY(-|R? zlCp{-UHWVYxgElfn6Zxn8y(ivHRPne(lR_%k(I&BS-X51MiPlPI(IoZVtyFGgBSVd z-t`Y<(ZTnA-`l~~-VRIDM<_9^|wwOxYMAPN0<1+v^XT&QGg8ZGE8>`0!vR>-#%TMpl z-*dBa6LJY$rcfJFuk5#(StkFx!426uQ?Pe+Wz*_^>4MA{N5Y1{=EX=zW)^Ioh(A%= z5m>EbrEC`W85sk@FY2LD7qMX4=xtZbMi5ea@nB7b4$UVGGdF{s0eIOstU_tlZz*M8 zVoNU&X=1Z^8!o22NjjQaoD9$y#DqaYLW6MPu%IDvvf%^JVnPK*R|wqR*GFVh_7+cN zaM(O%8C1!R59V?w;8rt*5P3h|Qh_rF+V`KU;k%2w>Pny~X(K0nVwMUM)!`my78Ho z-p>c0Se+A2dIXP!a=eqohnA8cOtazU=xOP+y6wDFu5*H5P3Vouf=6bN4lz5GaYPzg zqn-$KI8ub$(!CK*-4!i=unCW@gh!<=;lruN!sjuL2=vmO-UlliA`A{HHL2ZeMj4QB zFbNbSI!V)%pOk;2iwgJH9*QWL&_8aXX^A(#LRDv<7a!M?{27iXbvZf-Eb?`+<7))s ztAz*#eI-4B^(U|o*U{%RluyA(U21=#lywbMu!cAM2~Yb@6ax=y5}HrT>-~H>S@l{- zPcVD7M61d$>$Hrc(@$X{{AHAuYV}4Q`MW49j8zD4qB9>eM9NSZ^84W7KZYnpzr*|O z{AKS+M_`(hXqe0d6@)|)yemLyn!=i_;!@b+EbOI-q!)1@`Z<69*gz}-b`Ud3r z8{Yk0QC@%f_wSvvp^wh)?#ac&hTJWVPi+^7DU(9J$A?FftGh&l;<^$?y;wxKM^;+z zXi_Ew1=iBE)-}Jj&o5ik{RdUD0j(PqKtKA(GRfBa*w$JrCTES@SAX{S^?i9^EKMGR z7HeJd>_Pv>)sWQ&be5hW8{^@-9JaytW#fi6UQ1{g(D?QGuH%nbr3+s_nxo#&ZSq#* zL-{WNpcLRr=;i$sDrRMHfNz> z#7|QprjqMDF=U+-clQ)D*J8LMI}#NOu)qGG(6WmuA~D^!U_$I#8|z%7^VJ zgJmIaVTJ8V-k>Ng7{c$`aoYMwzuLQK+7p;`pT`WIH#0tR8R*z0|L3b>{j@Raiy8<) zK;dXi%E7VE|7##(L7hIUCwWkZ3l<)P$0{0r;R*cLry0CIEH2eq_Eo)kBgr?QP?6bu zmDWObVLv@#h?YEj)c`Hsmm5sX?-RPrrXk$pQ2vd(X9~nv>fs-bo{sI!@?WBRX^~(Z zu|OCgS)^L!4|SKzdbzl?dI(3cJb8_p=*TneRM*7Ho^yU__6UPgM~CJYujxZKYj|e# z1wfbO!{4_I`7Cm^H|bZjlKsy&XO~WuS9k3yrDCxYpG2w@r}4lu$Mdm%*uTrD3e9~o~noq?D55aFULPQI~ zyI?JH>XSdyLD$kjJJdm2Ys|B+1#Cb4eF@rt@>MF7FPSHzC6J9vndPODh%y=0{!{EW zYk$9!{(j&C*Du$gcbb&K#FfDe*17gFpWWQV2TEAelVC1m`1nR$`yR)^n}-uSMq1mN z$+<+@*~xUpl-aK^M_J5`7jauDur_lh#IXT*G~+b27wBGE$qEW`@@~{Tg%Q%hrRQrt z(Qc*rYmnKA_6!&hvnW#0^$lH-fV6e_c%8Q5MX6^tm-Lg(NjcD8$tS2*T*^gnaDeav zKg0ao3Qx;I#)uS-yHuV7sI{!43C5~54NO?3R7-i_wJ{A0EJw>e;87KsQb?;shp#?m zeK8uk_%@1cvNdYzVH1yHfNh`w(@eO@8B)XwgDBGv=DB|rG}Sy zW36mmURX#LT7|J{#-l@G9rUBh{fbT7&uJj}`S@Zd>-%R%a0dRbz^5NptF^$4eicUd zvw9@&QdPa$S1SaNF#ZnPPO%j)Ak{a;3vn!8+NP+M!>=^In}9ikcRe>16-PkDBPZSA zcy~3IHafeOws+IUrDbQLRatU3hvCRg2r=5(T$Ch%$69*{e0p@peB&#jzKk11srv+2tLdr9*x^v*DbmS+ zQ+sohsEqVi@rj2&&?Ke-7*sqFEGa-gaK+Cgp8ziQln~D<>@QThJTAs;fc?vuYL@zP z8WlLfU8*ZkzWl9fwCtw1ID0U@SKEvBN)SPRt@RHkmkNL9ZcNr=)4wSQ?@#l{kb2!q zsKU(cJGmozEatBnND@?bJi@!pfL48$o`5lU08!17Yq|68i@-M9l{5r4kr_>vW^ zo&+_DX0MV1DMGT@wbVri1kQ(vo7uHy+A>t+Fe-0W&@o-K0c)9+gk0FGbAYOc_WAnR zvV}8}g{Ll&#V{Q2(TKu6)CeA+DTX^2um=s4Pt zsESu5ny0@OAH`}vPZnu%#0b=26V#Ho!ZtdqpBO@B#2!;7A3ZHua8{w&pN=17cTNf| zv^b>!O3O}Ex?~;@@dc0!Rr{>RQT{7ePWxO9DQp=S2AT0%6MS~undj2aV=uq2u_=%K zuPe~k!I8K-xTvnGz76wbiPP43W4TBU@f4wS)5?pq$88QKW$yhC{Ni@KzLs-Ot{$GY zlj~RRyYnHNn=eaS$gLp6y}Bab>u`YwjnFCy!{-h#p__o)f&HO)KL>yEbqn<38I73) zvxw5xe?{y%K&(VF0mk7)ScmGsWMuGoq`xKuNXdWPHM{tY7V(d)ZBplr<56+b@$`M1 z_rV`j@+Y~VcmHS%hJHiG&F)82aL4GnO1x4Xamb&WK06LGl;jf9kXnAn-E_Wf-jS7#EXO8^M=^v%u^k)f zDbpGz@E=+Y@Z!93Gl|NOxQNCMar-?63exa+#oG@|{^!&RU;i2oGEfKah|^Ivwbw_A zs2#)xtV+r4jZ94&x)CwRBGdaJeF7BPQXm$rfI9YXPeJ<0X3BW3D|+XDzFX0r**vut zWfuQgEJ)5R#`^oP`eZp*+WHp75@T%|{!A&dMn$!A1aL_IhpBy&L{M6qOfWu{$Wc=YVw$` z7?kSWkmT?^ISy+=yPGxp9fa0Ix^(2%sU*U3;B5S~=K*VPIL#=(C^8usW4Ahy^;ah? zxB!E;$SS=!5su)&lrdkuN4&f-bi^_F@arYwVi}`QEiQm}Ka3*~&s#6N3 zsO_%WEfV$k;o36`I%ERWx(gjw0A2*c9FzYCz2)b`Y<#wrmR{3J0gB#WgJ)#s8b8NX z(7yr-@}2m{QnnPD6_p#=l*VWPyNK=i91YpNdI1EfJ-IoBGU=Co8~D{lqcSm|mfuw0 zr&&Rv!YJnsLuwo(fn$0bT^Bk0UdNkbTcAE>B!R7Kb-3bjky)?4?C+QSD3M&89^%uI z_;9i)dzFW>COEF*E0VoC^g3r+rwUWKEH*6d3 z>4hY9(0-os!h|4L;g*hbTA7zxP#2>xs8%Z9cVl7>n_64}=#$Thl>d>BVdH#p#ktfi z9vGvpu8p6KhmDV8(VDZ5d4H|lgT!|74vFQ_k8gE)q((LMpWwWWjZKCQ!GGM~;ZqmW z_kD^+0pqm8O$Pi@?5y7s)t+P~6}9iQ5oy`klxr&$yO8#qzEYXtVNqruyaVzdx)Pmg zWj~Am*HF0J-%Slwe;(l)9|K!ao&AHJ4Iz;^Z^I1rLc)2HMmc7|ZIcYyW_LSZ;Xk%} z86EBQv@~6gvpWTZaC?!rl_04x)rjVHU|G-0GE8@Pr{2(-Qn(}+0icD@p^9uDsjavp zj%3#b_BLjk9Y{x#z=bi(UG9S0+{gPpN#`qLH6?nO(Q^=2mjOA%rd(dLvV&h!A)z+melSF=Npt19^8UCT2zk;l-5q1ct&d}?MQy78CD10(GOi)8QBqOD1 z3*eC@WaCKlX@My8CD%Xl>>{H4j}5UJXZLVYnlW+#9xE!)L!H zSsz_qib|cuw)toYD8+KJPH!MDBSrD|6(Gs?p@v*i;L@=aSNbJKOv@#)A z!GU0e54|6Q%^$g~4)n?e^PE!2$U)3F`RRmw9;f#`2_&ms+S$@}jq{m)atqdt`xhH} zS_Xp5+{|*JTGS={m=)@&6Z1rO#mdHc=`8gUXaSQ+!8pyzxC#^K<PkYi_crr{>vB6chc5B45>SK~P$ z^4oI~>N9EKghOAw^FtNNw+S@fKToObg&5D&Gq)5{D)94K*xt7n<;5lpnwY{vL|7B~ z8ytI3c>=%n*iub^@CvNb(GrX#zldDF1a!i z)zj2}^)motcFpX~=2Y|O>HPPy{GZ@OgwECs+T2g4E{#~_X4ng~%oP=>zwXGM!kPrL zjvSS|T;J%c!2bkfdo%RDXEz!T?9}_Gxzbm)3KAEE9*BC1L0@G*P@TC&Udcr~jH>2# zP)(TS7o@YLvjpmDVvDb{fiH_9@SM`dkR&LFFj!I+S3kYb18d*^Z!f_8A7@uRtupfW zWR(j|l7glPFen^^qa#Rm>nG=N{G74wFgq-)9B4L`@3oSY+=wa*oychR44cqCx0s;< zN>fUinP8TEKw%YJTLISo$W)wV_TF+TiG3DwPhfug%;^xB_obXF9iNosEtcM&vLl() zX7oRl%jJ#_`((xD98p)tj-!Mmh0M5pt*idW!ERQiO(GBPhZL&bK*+(`m zj_>)xmb7u&VlGQVNBk62=wC=O&0uvhjLgQ&0Er#xPXd`$VraH zg2BN>o^rAbugOE?AILc~XZCD)gbM?KhFg5C8T!Ow=>L!`@H#kq_+M~`y*1Cq9iDK9D|B+^ z^U&j3*(dUHXSt-fhu>yl-1yT-;zPJgb4c&#cvI&CdzE7Y;Yc%&YhJIDeLu`@9VHVK zyUUNUH`Kjf@aD1K4}8S4f>wM>{_;D9=1Z{TI~rsQG^F^8UY-05>twD^{rP;{Nv}?W zU)j<@@@@_+C4tNENHW-B>!Q`8W-3lz2+Z=d`TPs%9lsE8FdTr?t{w_29m5f6)vU2m zaeHI2G)Y_97;Y}hQG3gKgEH7Xyk9lM>*)bqkp*e(p?{wn=Don55D7ASlx?PM9r7T8 zNB;7$3tD#pFcgne9P z0uyiMSfn6nR>{72?+-E?u_-M9VNQ8)vPmDk<&(vLf{bnI!-lL~OZlM-H>Q5BRDX$g<;@?>dDA~xV zT13JI8&p(STNU7#%>TCj?MZv$5WK4G`sfU6Otkp;yut=SJ$~U~eNqXZbW_ULExdBM zW5t8v(D2cylhkBBq0KWkA(b~bHFv*lcVGCm(xi}m1PwmZXY4h!dF_i0Bh^PcsjzVB zY?zZkR|gp3)Tz*tz7;Zd1femeO@#7ohcu>t;-%qUa5Ow{V91FMx?~TXBiqTw+d>?1 zWdNxQ5f_RBJKk4YcJTL==gdxWzVDFcV|L-IlDkJKKcb@jb+Sx=qZut`O>DU}ri4b_ zMfg*y)bHzVdMra~Top-=6$5jheaP9``>9X7Q3 z_8Sf95`OHjd6(C&b;U7DYg$v*dej75sYM-5X1NT0W~o;PNZh(^Rj&R$2g`7yWZyhK zLlXRl(fZGVjktW81zEPC)Knj4l$7slZG+K9NfjENG}pjVkMGI;rhrj)KcnopK*kHd zIQe(+kFr@?T{!Z>XKPpD3R2o6n*r^%dh-&KI@q!jXYmrS^9R# z&FH3Fw01T6|9e28${?R`dFy&b`isI(v)6g~Y3t7qZ2Arjt*(NxhK#uFi=E8aeVyO7 zo4&6`?hRQjp`xbL-=+izszcF3p2rk+J#_e%afI5lp$|cnN5&!x;|Z=SU4|3;mG=&a zdXs&aQ9XJsn;R$soeS&ogdVNb5nPYHX@zcnS0>HJq?Z@EL?}Jhxn2(w_BC;91V)gO zVLKHGD~!JLSCfmy=ZVj#SgLLenO~y?W{A50x(Dy{aHu2$;c;NW>sLYsJe5Ja^UdvZ zm0%PM^FH0_TZA$DmyV~DThqoeaY(@`M7TQAF1!$!_L*U~-OKd-Ggc@>k~x6h24pbC zo!B2yY`oE7Ki%t=T{S!ztt{T5H*P;zC_egUqWPWL!{jb)fSUZb34&ha42ccV0xGK? zQ%~?VaYah%+QSDk;Yyo|8n`F&2xD5~z1a$IfRJv^CDUHb)bcEPy3rda~~ z!vdT$CfmF<0ckvSGp zMeL+Zuy`cw{Bw~~Te!Hu45}!zgq)qMT$<)cK9$)N5j0HVml7+C3_Y1rfBqz2nlz7Q zo8f<)+NPI=BhvpY)=<9lJz00d0h<%4nIGT0^1lA}69g z?de+oT^);Dc3d#&I9%p5vmr{gEWtljry$-+{*%R%u`1)Xo;&%&*`p&p1-@2O$4xJa zS%__ef6EP}oMFVvq&ko_hYQMR{Z+9sTVPy)uTuPSl?&vnptoVhi9QwmtS$66YiXtu zJz^9YuGS!cyK~mOc2R2m7dN;C@DM2~iQ(piEN%Z$H0gByvicsxvLrx-N9ooqcF`M3 z_;%SS;dB0e;r3lU>;l-xnAkhj_c&W`_xo}7PsW;NGg{r6si+k-t40BtDoQjMDT0yA zGnX6A1kK}Y%?ldhfhchaZ=kqf>bBR2#cBKj)ml*Mo8+%!hz&Dse)#h&EL7z;}pObcpdji#OQMP8n z9n=iG3a!Pz6jHV`(h~2rJ{XMHz`3gG%x>qmse87=3Jp7M2Fb=p6gUKy-z&>Y51FoD zG-Tzr(Sa&gPnNt%NNu{o?&q5M;lIy$wkjyfzX;P$d+i#Ne{5h4NRK`&G>WcdO*D@i z5~fwnN2gC`jXfjokOohE8EUMKbKFZYfy~g3|40czoc*X--QPGrjP@{3|6H8@Tob-a znsKbp?6}#rY1~nO8cV+Baq{>auBB6RItODG<)R(C2@C84*jXp1CQgnUgh88}=(O;5 z7tC~g2%D$%lE0`oF$}1_6|&f~)O5Zgb~zLc+O*s1y|W!K9RAXfw2)zt!eEm`uo=8I zDrf&&FWBf5B$xbWg7NXH%N))s(|uh7l@EP!gmH;+}$_g8t)R8wUz9YxnWt3!R94UHaI zWwy*}yXd|q0Z3K(m?*fQkOzOWgXg>ckypPI;@W|mUxjCcSs(%R&Sh)K+cn^=57aAC+dcfuw6yzw1@j2Y3_LL6qB1{1n2+UibI!AGOIe5V@Eg(OR4o(aj^ z@VcpiMeGO;27a!_2I}Y=w$5=mT^mK&f)Def-IZhAWYZ=;MQ%)jHrsi$bKuMaOQRgc zWweIvBkdZ~!l`T0CvCGKZGmCW_-ymbi=Df@BGu1VN}?w8{1*GXWnFX~mfl+~4~aZR zyCu8{r-O3lexGDpxQFB{;F$qBDL&l6%RoLZNF|VQk2ya?pHDa0=umQ9zN6WCakg4`?VWW0LL z_$9W?R*ngVqiIv5gK+QEseZznOENyKh)5Xf>Dmn{JBkCwwzKCxZ6Mlz7V3nLN+9V0 zSGsv0$GSdQX-&sQIJ&h_FbEqvW!#bOW$h%PJV!f~3SOB;S#I)*_)n#11tQ`CS6kFn$C$+vO( z0&-7wUY+ANG6a&*Lf2nSvFl#R7Zu76br`7V)c~YHmn8v&THkapmIq3{&*3V$VlNVm`gx?CvcyCR+MQcRy@ z?8Nt{#NdUE$6B4%Zppru52KT4x#QcbIHL9{H)iM7Fj#vxdC8{bNa?)F@l1X!cx1)w zK_&_5V{ww{wS(ndl<@Di4!5mg?LrpPFZ=IyJ9vTDH_MYEJc*WSFKV8|h69iEW%8uqHq`lTd1M66+6hf`H;@vYGF zp{WxCto_<88Q_TeO`9U-m>b?-lAauq4E^^3CKZ?Zyw4t99Z*T$-e~VAZ*2Sw$Lg~% zs!O^)X`wXg@P>l;>FDn$4M!I4d4H2HlJ=&^@neISs2dYQ>|X?EXn>G4#gg3Db=7V$ z?Hv0j{O=Qo48u6iE-sfZ*c&2#C)e}+ZWSxB+k#33oQiX-$!7osCc5$M3Ktv%TZOkv zpMW#m#-Bxd7bp+quiu!glfbS2uJ_VMwD_hn&&m>2prZL_!`cT+8`0-YzRPLhhm`?1 zq^ZYnRGCd*@WK?qbQ%{~DwUWBFDi{Ie@=ekGCZAb`aDRmI{LnaV5&ZeS9;)p_2p;B9<#Pu zvNA{4zJ$j`Xt&pkNaJxk|9_!~6-Ih#PK6pg9e`g~5Z8ds!~)x#Dj~dMJNW^&8OU8j zX{<4(rp}vC~jy#_J*XW3$lP5}}-r1J~?I2@7AXSSE*mXeJ14@Fa^w6`Azc zk$FB(U-%_H5o+&wq`!d0bzw>12)9gDPL4MeECQ2lfGG*s3P3y=gjAA=?){3XJSUc(vwd8fimz6e(#jN$q_xdV?&ns|VNm-)=1e^hlSR=IT zfJJ5gi!%Xs8haBZfnJwC&v}#=x zy~`Cl%gUn^7Fr)=m#8)wJKZG^D=W`en4|7X~v(9AHrFg_TBqE zB>Tk&w68*AFW8jqc1WCYc+THdh)Q?X8_mRQczgv|d{6uMkYN(X+1vZE6MJ3E@A-rm zP=vKjg*Hd^RYO8~mRG)>w;)HAvFmnDM@L&H15_+`{jO7)uIf5%?2t{9Jdnnzr@hL> zYIiQMO8e2?nlM4iek^Ivrr!&WO6PdDcSeoEC2{O**{YN|PpcmIYS&OnA}3|zH0^9~ zW7SJx1XX9K$xWeE?^S9L@B2P#p$UY_D=iEOE^iAj3Tc! zA%RXmG(%bq4SB*M&k=Jm-+WxwtvSWB-*LA7%^yDCygKErQ)8?P!$*MEPp}_h6D^qd z^c=4;_t{(V3NYY&DBuA)@Pk$T+)HtnRnrB_asnemq_YvHLBZvtg?GmT*J}cU4cW9{ zuSximR$bRvd@l`T?S(m*7 zrxg}1bCtmwq?l7Eiw;0j;1kGUKuXDT;v)4zc zI@Jc9391L6UZ`VDG;-^co~w5OitPj&dp#VkV*npKOSI= zMU0ix5&aClKTX{R*0aq7>t&X&pxAfW{c*4$r5?1g{LxvC!99WJ=yXIu%RmXHaF4DFwzRY%V|m~PY3Qnq|yl~i6T3q2HG+i>1dt7doM zKW-j4%-?>6F%VQszo;ciJx(q(6Q)7|PJnCFPE+ok7!}xt%)sjO2>hTYJ77oF?#7{wJY%lk3N42NtRy9qwVKiA_ul@_Lq{uX;w7A zA*&AqGbVbzDRxX}l=5%vn9|m;#|b0~{aBm(ZuUsM z68i=C@a~K@l(|gb@mPZ927=FLtzx$-Q2)04iYGDwaLg605PJDZLZhOrYWQ&8N$TNL zSzYCH*Mu6RCL zbbNhlNC}w!yCt`XSH=wWE@6;zmYU9c` zp~lpf-Qr!jOZ8(^*+pYpM9RrxMxGQ4>wC92nMPa;5FEwHM-QWyCI+AQ6%_#I+M2fj0B%?plghlDMPbw7SX(P;*n+Hs<-S>pBc{I# z^uuo`IdG1`yGLX_bd;}=j?*whACSeq)nxJ|$zHeiQ)6F{gOec422+YTHG^1aV-7I$ zv!N{iCM)Hko2B{%&4FPf|3mJir?sYk&4Ls!^VAlqs0g3W^WR8vi-soE{Qn+x;;JKZ zF=?53myam0X~9c?-MtDme#YoC7@4TM6q<)>x1tt;ww!19wnMa=di`ysgRabC~%_A7*?ODyUGGRNrsyLOx3?L6x94z0~D1Qm~n)0;y-NKEiDrr zUN;drm}D~DILf%JS`H|;Q}iTSFHk}~U(bSTWp#dQe?_S>V-3i8m`Z8yGA6o0v3n|n z!TcE3z^AvpqrI@$p=;39-P*ZAdj0sae&>I`=IfLwJ=TCf2RG}JeQbNUvnb*dB3+F* z*Gkzzq<3y9!uyl`BVBKPJ%fH5L?e3B&Yh;-avd?3an9 zHkecAE%lm^7F?E+h8{P!jJ=vD_vMTzphrf5b&R_uZhS_&{Tgo{Se-{zpe|Nlj6; z;*G$9Jc?xEGnzL(c?B(w{eh9(BanMHG6_}lkBFB!*j)Bf_rzUHd02N_gwjCIk zc)X84n)2`HoLBD;(J+t(VBS3L0mNAGH6w@f7Zr-s{dKGLkYa0-YVpEVn7^olo!!wW z$U~J(Y$Zk?&~+#KSHJj6JMYXw&5AVUZyBBgb)Dn;iO+*JStQYbi6He(l zqrqzvS8|*Z9qHnE1Var6JK=SG5GjD!S(7nXsjVNPc zSi#EQE~w4CB4u*2CjAM&8Jh_z+IzgplUOfzw@?jLt%3cax%ny3$KU@o+a5rfCPEdi zZ0`I~5!7yHRA8SUjC*A!XVBQvztFSiLhYY4|~!In2r}m^=h}z+a&Ch?@SH_ zoq}F#?^*!IBQW@3KvvA}r3jL)tdPp-2~rm>ww3pKft(H0ogu&G!0U{FKS1P=lGy&L zPb{;f&J(?Vxc+sT`|LDO*7SHLU9vfPu!j-;HH_E_cC$3Tah`C$#Y4X|v^nEam1>V9 zK^pKvFiev;4A?ck<3Q`wuSO>4OvXpzObg@w zh=#?$7b&qGhvCCWnc^4e8#cUf4Fje`FvDc!x%J=8O@G>2eBbt-C+sN=U(RnAiW-Zm zUiZwqAVaVLv;_Yk5eC=$V95z7dXZmcIqT)^X?qVS@z*HI(BNKWaThRuS&ni>zB8~( zBWts;pG*N;Qw%Sk&)i^}64UY`{PuT#86ejn;0^nN~UR zzX15%;Jcym;f;@N1_FZA)|3B#q7mo!QaC75L?}}K!h@hsInW1x+9-Wc9}Fhubyh~m zodLVpZ>nxWpFC>TI&RNU0mj`|n@5AAoaCbG;k9?LLd@J;H=}X88j9+I#VxbYqB`*Z zSb$I9VV0F>Qo7$ao8RF+%u3rU;yMB$i%+0^ohJQEM=9Ojd;}lNU})GlyDP@4Sr%&R zWFPL-_O_e#zpBgciiTBh9)r9HgK$^h`++gYkaRKAn_#*8QZ{Wu#>wmr$#?_7n|b4o zN9EB2tgG`M@8RRlL|#MpyWO6rBem<Wx!q7AzSjW+>Ap@jcKjY9pA&W1tB?~)%jIn);=-Z4jy`1B?XQ*AUMUhy`&dJW| z^gT;AemWBHemQ+oysPoMe%066=y2KYEV>!^L@u6E@@MFO%fSnSqdh$IlJ3#WMWCch z&CT6j(?@lLwJiF$?=u|?RV7j=FmRV>$zRqblJP~(x-t{r|48ewCk?V`zwk;`2YZ2? zX3h2{iL!^whn&SoIHBZcxV2lv2XbF@xI-;xIN6YR1y+73&F5l|lV@e8wKun&+dz9P z@v26q#9juXOLFb_AZj=$U6)6ZW+5;EfE0@nm~LLcYrspry}-uOk}#b-f`pBLhz8yq zjQb5E&N#J0oEkGh+B{DDTe9P8d!}SC4!vyNq=N+7^WQtfjjKK?Vc`8eOolDs)zuhA z0mV?ff*SceF+iUWz*QBbkYk^>@3-R}t*xy?+2LSW(xUnBTFBX;8X1z*KV%AlUY{f{ zq<1dK6YfnmoqIiApPN>vLK;Gw&{6g%hQCP;(+tIyJGtY=k>cQacF)EQK4HT z7+2~MQ4-sw)7EgGYGYdN7vWaQ;9Rd_g4-F)jYL2VS3|Liw|F}z>W2F&z*%EOu+(3a z`%ca1Ca=U$PmAxambseWQUnoh&ue|n$HR&Jr0iJayl<=|=ix}vSj7K8hSmuU)m_}y z*{I>%*t28-=3Ni4wY06{;p5>FS;;=)6>%m)3^{#WoIToFd-6!pCT#YByhwFR9NeJ3 zyw*yoccJ^E=&+m-6A`5Mwh2_P|r)fbkZr4f7)|ykj zjvr_EmHkZOc3p|HM%JonfTuiNp)$h-TCZVr&AFDVp)n#$39q|wY_8OgVG-e0vAz$X zx!pgG%-UDp_5Qu{m9G3nFY_|JtGlx^qmepkqzB9KTIz<0EcN!7iijt?L)MQ56%vY* z)JA2n&w0mYStsgX1tTx4{QWx1`!$hQaZbj8!8YOYFYoRz9tHS3Vu3gc!CRP0pYKCa zjkxP=tDud8gQI_fckV*gtLJiUE{w~c;hZQE>^lX68`BzzOGeuqTSYoA$ZY@Ul59k8 zq{fVp*-YKZ!_p-D)c8JCkRLJ2rM0xt@Fzc$mPLjJnSP$$2Vfe(%0D-{^odT??FPIN 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 e(eB#~mH!W3?Sd>^Y}}3j0000 Date: Wed, 25 Jan 2023 13:09:34 +0100 Subject: [PATCH 063/356] fix pop created_dt --- openpype/plugins/publish/integrate_hero_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 427256c137..1a4f4bacfd 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -400,7 +400,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): for site in old_repre_files_sites[i]: if site["name"] not in repre_sites_names: # Pop the date to tag for sync - site.pop("created_dt") + site.pop("created_dt", None) file["sites"].append(site) update_data["files"][i] = file From 036509dc4733595e07b000097b6b841b2c76e40e Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 25 Jan 2023 14:17:20 +0100 Subject: [PATCH 064/356] additional changes incorporated --- website/docs/artist_getting_started.md | 54 +++++++++----------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 474a39642b..40961dbc77 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -7,6 +7,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; + ## Working in the studio In studio environment you should have OpenPype already installed and deployed, so you can start using it without much setup. Your admin has probably put OpenPype icon on your desktop or even had your computer set up so OpenPype will start automatically. @@ -15,63 +16,46 @@ If this is not the case, please contact your administrator to consult on how to ## Working from home -If you are working from home though, you'll need to install it yourself. You should, however, receive the OpenPype installer files from your studio +If you are working from **home** though, you'll **need to install** it yourself. You should, however, receive the OpenPype installer files from your studio admin, supervisor or production, because OpenPype versions and executables might not be compatible between studios. Installing OpenPype is possible by Windows installer or by unzipping it anywhere on the disk from downloaded ZIP archive. -There are two options running OpenPype +For more detailed info about installation on different OS please visit [Installation section](artist_install.md). + +There are two ways running OpenPype first most common one by using OP icon on the Desktop triggering -**openpype_gui.exe** suitable for artists. It runs OpenPype GUI in the OS tray. From there you can run all the available tools. To use any of the features, OpenPype must be running in the tray. +**openpype_gui.exe** suitable **for artists**. It runs OpenPype GUI in the OS tray. From there you can run all the available tools. To use any of the features, OpenPype must be running in the tray. or alternatively by using -**openpype_console.exe** located in the OpenPype folder which is suitable for TDs/admins for debugging and error reporting. It opens console window where all the necessary information will appear during user's work. - - - - - - -WIP - Windows instructions once installers are finished - - - - -WIP - Linux instructions once installers are finished - - - - -WIP - Mac instructions once installers are finished - - - +**openpype_console.exe** located in the OpenPype folder which is suitable for **TDs/Admin** for debugging and error reporting. This one runs with opened console window where all the necessary info will appear during user's work session. ## First Launch -When you first start OpenPype, you will be asked to give it some basic information. +When you first start OpenPype, you will be asked to fill in some basic informations. + ### MongoDB In most cases that will only be your studio MongoDB Address. +It's a URL that you should have received from your Studio admin and most often will look like this -It is a URL that you should receive from you studio and most often will look like this `mongodb://username:passwword@mongo.mystudiodomain.com:12345` or `mongodb://192.168.100.15:27071`, it really depends on your studio setup. When OpenPype Igniter +`mongodb://username:passwword@mongo.mystudiodomain.com:12345` + + or + + `mongodb://192.168.100.15:27071` + +it really depends on your studio setup. When OpenPype Igniter asks for it, just put it in the corresponding text field and press `install` button. ### OpenPype Version Repository -Sometimes your studio might also ask you to fill in the path to it's version +Sometimes your Studio might also ask you to fill in the path to it's version repository. This is a location where OpenPype will be looking for when checking if it's up to date and where updates are installed from automatically. @@ -80,7 +64,7 @@ This path is usually taken from the database directly, so you shouldn't need it. ## Updates -If you're connected to your studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start, it will go through a quick update installation process, even though you might have just installed it. +If you're connected to your Studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start, it will go through a quick update installation process, even though you might have just installed it. ## Advanced Usage From d0354f5deb37f76f82e366ecab54d4000aaafbbd Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 25 Jan 2023 14:44:29 +0100 Subject: [PATCH 065/356] just added note --- website/docs/artist_hosts_3dsmax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index 0ca6f008cf..af90d93106 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -4,7 +4,7 @@ title: 3dsmax sidebar_label: 3dsmax --- -### *Still Work In Progress Page* +### *Still Work In Progress Docs Page* ## OpenPype Global Tools From 9c1e663cbcffc85f2a85769ca33c66a9eb71e18b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:56:47 +0000 Subject: [PATCH 066/356] Bump ua-parser-js from 0.7.31 to 0.7.33 in /website Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.31 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.31...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 9af21c7500..ad80bf6915 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -7180,9 +7180,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" ua-parser-js@^0.7.30: - version "0.7.31" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" - integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + 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== unherit@^1.0.4: version "1.1.3" From b5ccd03ebd9ece448b3308d827a4ff50db245198 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 25 Jan 2023 18:25:04 +0100 Subject: [PATCH 067/356] Fix - handle inputLinks and hero version Hero version doesn't store inputLinks, but with changes in comparing it should work. --- openpype/client/entity_links.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/client/entity_links.py b/openpype/client/entity_links.py index e42ac58aff..b74b4ce7f6 100644 --- a/openpype/client/entity_links.py +++ b/openpype/client/entity_links.py @@ -164,7 +164,6 @@ def get_linked_representation_id( # Recursive graph lookup for inputs {"$graphLookup": graph_lookup} ] - conn = get_project_connection(project_name) result = conn.aggregate(query_pipeline) referenced_version_ids = _process_referenced_pipeline_result( @@ -213,7 +212,7 @@ def _process_referenced_pipeline_result(result, link_type): for output in sorted(outputs_recursive, key=lambda o: o["depth"]): output_links = output.get("data", {}).get("inputLinks") - if not output_links: + if not output_links and output["type"] != "hero_version": continue # Leaf @@ -232,6 +231,9 @@ def _process_referenced_pipeline_result(result, link_type): def _filter_input_links(input_links, link_type, correctly_linked_ids): + if not input_links: # to handle hero versions + return + for input_link in input_links: if link_type and input_link["type"] != link_type: continue From 60c6a61779b132e90233a7b3ca83068a5e4a56f8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 25 Jan 2023 18:27:08 +0100 Subject: [PATCH 068/356] Fix - added docstring --- openpype/plugins/load/add_site.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 64567b746a..860e0ef15e 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -82,6 +82,9 @@ class AddSyncSite(load.LoaderPlugin): def _add_hero_representation_ids(self, project_name, repre_id): """Find hero version if exists for repre_id. + Args: + project_name (str) + repre_id (ObjectId) Returns: (list): at least [repre_id] if no hero version found """ From a6df39c77b26c12ef236bf36a601c032633680ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 01:55:11 +0100 Subject: [PATCH 069/356] fix 'update_context_data' in tray publisher --- openpype/hosts/traypublisher/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/api/pipeline.py b/openpype/hosts/traypublisher/api/pipeline.py index 0a8ddaa343..3264f52b0f 100644 --- a/openpype/hosts/traypublisher/api/pipeline.py +++ b/openpype/hosts/traypublisher/api/pipeline.py @@ -37,7 +37,7 @@ class TrayPublisherHost(HostBase, IPublishHost): return HostContext.get_context_data() def update_context_data(self, data, changes): - HostContext.save_context_data(data, changes) + HostContext.save_context_data(data) def set_project_name(self, project_name): # TODO Deregister project specific plugins and register new project From fa62e7954c0d061695651052651e55c6d1b59ef2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 01:56:18 +0100 Subject: [PATCH 070/356] removed unused methods --- openpype/pipeline/create/context.py | 78 ----------------------------- 1 file changed, 78 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9c468ae8fc..0d42c663d9 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -315,16 +315,6 @@ class AttributeValues(object): def changes(self): return self.calculate_changes(self._data, self._origin_data) - def apply_changes(self, changes): - for key, item in changes.items(): - old_value, new_value = item - if new_value is None: - if key in self: - self.pop(key) - - elif self.get(key) != new_value: - self[key] = new_value - class CreatorAttributeValues(AttributeValues): """Creator specific attribute values of an instance. @@ -458,21 +448,6 @@ class PublishAttributes: changes[key] = (value, None) return changes - def apply_changes(self, changes): - for key, item in changes.items(): - if isinstance(item, dict): - self._data[key].apply_changes(item) - continue - - old_value, new_value = item - if new_value is not None: - raise ValueError( - "Unexpected type \"{}\" expected None".format( - str(type(new_value)) - ) - ) - self.pop(key) - def set_publish_plugins(self, attr_plugins): """Set publish plugins attribute definitions.""" @@ -909,59 +884,6 @@ class CreatedInstance: return obj - def remote_changes(self): - """Prepare serializable changes on remote side. - - Returns: - Dict[str, Any]: Prepared changes that can be send to client side. - """ - - return { - "changes": self.changes(), - "asset_is_valid": self._asset_is_valid, - "task_is_valid": self._task_is_valid, - } - - def update_from_remote(self, remote_changes): - """Apply changes from remote side on client side. - - Args: - remote_changes (Dict[str, Any]): Changes created on remote side. - """ - - self._asset_is_valid = remote_changes["asset_is_valid"] - self._task_is_valid = remote_changes["task_is_valid"] - - changes = remote_changes["changes"] - creator_attributes = changes.pop("creator_attributes", None) or {} - publish_attributes = changes.pop("publish_attributes", None) or {} - if changes: - self.apply_changes(changes) - - if creator_attributes: - self.creator_attributes.apply_changes(creator_attributes) - - if publish_attributes: - self.publish_attributes.apply_changes(publish_attributes) - - def apply_changes(self, changes): - """Apply changes created via 'changes'. - - Args: - Dict[str, Tuple[Any, Any]]: Instance changes to apply. Same values - are kept untouched. - """ - - for key, item in changes.items(): - old_value, new_value = item - if new_value is None: - if key in self: - self.pop(key) - else: - current_value = self.get(key) - if current_value != new_value: - self[key] = new_value - class ConvertorItem(object): """Item representing convertor plugin. From 1496b74fc2668e8c075be1a771002b7b7c8c7fb5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 01:58:52 +0100 Subject: [PATCH 071/356] added helper object to handle changes --- openpype/pipeline/create/context.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0d42c663d9..94230bda09 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -177,6 +177,99 @@ def prepare_failed_creator_operation_info( } +class ChangedItem(object): + def __init__(self, old_value, new_value): + self._old_value = copy.deepcopy(old_value) + self._new_value = copy.deepcopy(new_value) + self._changed = self._old_value != self._new_value + + old_is_dict = isinstance(old_value, dict) + new_is_dict = isinstance(new_value, dict) + children = {} + changed_keys = set() + available_keys = set() + if old_is_dict and new_is_dict: + old_keys = set(old_value.keys()) + new_keys = set(new_value.keys()) + available_keys = old_keys | new_keys + for key in available_keys: + item = ChangedItem( + old_value.get(key), new_value.get(key) + ) + children[key] = item + if item.changed or key not in old_keys or key not in new_keys: + changed_keys.add(key) + + elif old_is_dict: + available_keys = set(old_value.keys()) + changed_keys = set(available_keys) + for key in available_keys: + children[key] = ChangedItem(old_value.get(key), None) + + elif new_is_dict: + available_keys = set(new_value.keys()) + changed_keys = set(available_keys) + for key in available_keys: + children[key] = ChangedItem(None, new_value.get(key)) + + self._changed_keys = changed_keys + self._available_keys = available_keys + self._children = children + self._old_is_dict = old_is_dict + self._new_is_dict = new_is_dict + + def __getitem__(self, key): + return self._children[key] + + def __bool__(self): + return self._changed + + def __iter__(self): + for key in self._changed_keys: + yield key + + def keys(self): + return set(self._changed_keys) + + @property + def changed(self): + return self._changed + + @property + def changes(self): + if not self._old_is_dict and not self._new_is_dict: + return (self.old_value, self.new_value) + + old_value = self.old_value + new_value = self.new_value + output = {} + for key in self.changed_keys: + _old = None + _new = None + if self._old_is_dict: + _old = old_value.get(key) + if self._new_is_dict: + _new = new_value.get(key) + output[key] = (_old, _new) + return output + + @property + def changed_keys(self): + return set(self._changed_keys) + + @property + def available_keys(self): + return set(self._available_keys) + + @property + def old_value(self): + return copy.deepcopy(self._old_value) + + @property + def new_value(self): + return copy.deepcopy(self._new_value) + + class InstanceMember: """Representation of instance member. From e46042c2d646333ee8723cd98aaecbe06881f878 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 01:59:35 +0100 Subject: [PATCH 072/356] use the 'ChangedItem' object to handle changes --- openpype/pipeline/create/context.py | 71 ++++++----------------------- 1 file changed, 15 insertions(+), 56 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 94230bda09..7db4849300 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -384,6 +384,10 @@ class AttributeValues(object): """Pointer to attribute definitions.""" return self._attr_defs + @property + def origin_data(self): + return copy.deepcopy(self._origin_data) + def data_to_store(self): """Create new dictionary with data to store.""" output = {} @@ -395,19 +399,6 @@ class AttributeValues(object): output[key] = attr_def.default return output - @staticmethod - def calculate_changes(new_data, old_data): - """Calculate changes of 2 dictionary objects.""" - changes = {} - for key, new_value in new_data.items(): - old_value = old_data.get(key) - if old_value != new_value: - changes[key] = (old_value, new_value) - return changes - - def changes(self): - return self.calculate_changes(self._data, self._origin_data) - class CreatorAttributeValues(AttributeValues): """Creator specific attribute values of an instance. @@ -525,21 +516,9 @@ class PublishAttributes: output[key] = attr_value.data_to_store() return output - def changes(self): - """Return changes per each key.""" - - changes = {} - for key, attr_val in self._data.items(): - attr_changes = attr_val.changes() - if attr_changes: - if key not in changes: - changes[key] = {} - changes[key].update(attr_val) - - for key, value in self._origin_data.items(): - if key not in self._data: - changes[key] = (value, None) - return changes + @property + def origin_data(self): + return copy.deepcopy(self._origin_data) def set_publish_plugins(self, attr_plugins): """Set publish plugins attribute definitions.""" @@ -746,6 +725,10 @@ class CreatedInstance: return label return self.creator.get_group_label() + @property + def origin_data(self): + return copy.deepcopy(self._orig_data) + @property def creator_identifier(self): return self.creator.identifier @@ -837,29 +820,7 @@ class CreatedInstance: def changes(self): """Calculate and return changes.""" - changes = {} - new_keys = set() - for key, new_value in self._data.items(): - new_keys.add(key) - if key in ("creator_attributes", "publish_attributes"): - continue - - old_value = self._orig_data.get(key) - if old_value != new_value: - changes[key] = (old_value, new_value) - - creator_attr_changes = self.creator_attributes.changes() - if creator_attr_changes: - changes["creator_attributes"] = creator_attr_changes - - publish_attr_changes = self.publish_attributes.changes() - if publish_attr_changes: - changes["publish_attributes"] = publish_attr_changes - - for key, old_value in self._orig_data.items(): - if key not in new_keys: - changes[key] = (old_value, None) - return changes + return ChangedItem(self.origin_data, self.data_to_store()) def mark_as_stored(self): """Should be called when instance data are stored. @@ -1390,11 +1351,9 @@ class CreateContext: def context_data_changes(self): """Changes of attributes.""" - changes = {} - publish_attribute_changes = self._publish_attributes.changes() - if publish_attribute_changes: - changes["publish_attributes"] = publish_attribute_changes - return changes + + old_value = copy.deepcopy(self._original_context_data) + return ChangedItem(old_value, self.context_data_to_store()) def creator_adds_instance(self, instance): """Creator adds new instance to context. From f9f077722c161048f7f3328ce1ab5a2aab12f7e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 02:06:26 +0100 Subject: [PATCH 073/356] adde items method to extend dictionary approach --- openpype/pipeline/create/context.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 7db4849300..b6a6747f65 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -231,6 +231,14 @@ class ChangedItem(object): def keys(self): return set(self._changed_keys) + def items(self): + changes = self.changes + if isinstance(changes, tuple): + yield None, changes + else: + for item in changes.items(): + yield item + @property def changed(self): return self._changed From c88579591a7fb5f9d079b6ee656f5af2937266b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 02:14:16 +0100 Subject: [PATCH 074/356] keep track of new/old keys --- openpype/pipeline/create/context.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b6a6747f65..485252b4f8 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -188,6 +188,8 @@ class ChangedItem(object): children = {} changed_keys = set() available_keys = set() + new_keys = set() + old_keys = set() if old_is_dict and new_is_dict: old_keys = set(old_value.keys()) new_keys = set(new_value.keys()) @@ -201,13 +203,15 @@ class ChangedItem(object): changed_keys.add(key) elif old_is_dict: - available_keys = set(old_value.keys()) + old_keys = set(old_value.keys()) + available_keys = set(old_keys) changed_keys = set(available_keys) for key in available_keys: children[key] = ChangedItem(old_value.get(key), None) elif new_is_dict: - available_keys = set(new_value.keys()) + new_keys = set(new_value.keys()) + available_keys = set(new_keys) changed_keys = set(available_keys) for key in available_keys: children[key] = ChangedItem(None, new_value.get(key)) @@ -217,6 +221,8 @@ class ChangedItem(object): self._children = children self._old_is_dict = old_is_dict self._new_is_dict = new_is_dict + self._old_keys = old_keys + self._new_keys = new_keys def __getitem__(self, key): return self._children[key] @@ -225,11 +231,14 @@ class ChangedItem(object): return self._changed def __iter__(self): - for key in self._changed_keys: + for key in self.changed_keys: yield key + def get(self, key, default=None): + return self._children.get(key, default) + def keys(self): - return set(self._changed_keys) + return self.changed_keys def items(self): changes = self.changes @@ -269,6 +278,14 @@ class ChangedItem(object): def available_keys(self): return set(self._available_keys) + @property + def old_keys(self): + return set(self._old_keys) + + @property + def new_keys(self): + return set(self._new_keys) + @property def old_value(self): return copy.deepcopy(self._old_value) From 7c1a39d5e4f17d3abf94268d9da6dcf8d5eeea9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Jan 2023 02:17:13 +0100 Subject: [PATCH 075/356] added 'is_dict' property --- openpype/pipeline/create/context.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 485252b4f8..e44e5db851 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -241,20 +241,23 @@ class ChangedItem(object): return self.changed_keys def items(self): - changes = self.changes - if isinstance(changes, tuple): - yield None, changes + if self.is_dict: + yield None, self.changes else: - for item in changes.items(): + for item in self.changes.items(): yield item @property def changed(self): return self._changed + @property + def is_dict(self): + return self._old_is_dict or self._new_is_dict + @property def changes(self): - if not self._old_is_dict and not self._new_is_dict: + if not self.is_dict: return (self.old_value, self.new_value) old_value = self.old_value From a28bae61971f59a45d0678211c23d1b74247bd9a Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Thu, 26 Jan 2023 08:54:29 +0100 Subject: [PATCH 076/356] added some scrn for 3dsmax and new paragraphs --- website/docs/artist_hosts_3dsmax.md | 87 ++++--------------- website/docs/assets/3dsmax_menu_OP.png | Bin 0 -> 69195 bytes website/docs/assets/3dsmax_menu_first_OP.png | Bin 0 -> 8376 bytes 3 files changed, 16 insertions(+), 71 deletions(-) create mode 100644 website/docs/assets/3dsmax_menu_OP.png create mode 100644 website/docs/assets/3dsmax_menu_first_OP.png diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index af90d93106..4379f7e71a 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -4,7 +4,9 @@ title: 3dsmax sidebar_label: 3dsmax --- -### *Still Work In Progress Docs Page* +:::note Work in progress +This part of documentation is still work in progress. +::: ## OpenPype Global Tools @@ -16,14 +18,20 @@ sidebar_label: 3dsmax - [Publish](artist_tools_publisher) - [Library Loader](artist_tools_library_loader) -## Working with OpenPype in 3dsmax -OpenPype is here to ease you the burden of working on project with lots of -collaborators, worrying about naming, setting stuff, browsing through endless -directories, loading and exporting and so on. To achieve that, OpenPype is using -concept of being _"data driven"_. This means that what happens when publishing -is influenced by data in scene. This can by slightly confusing so let's get to -it with few examples. +## First Steps With OpenPype Running + +When **OpenPype** (reffered as **OP** ) properly installed and 3dsmax launched via OP Launcher/Ftrack (or similar) there should be **OpenPype Menu** visible in 3dsmax top header after start. + +![Menu OpenPype](assets/3dsmax_menu_first_OP.png) + +## Working With Scene Files + +Most user actions happens in ```Work Files``` menu item. There user can perform Save/Load actions as he would normally do with ```File Save ``` and/or ```File Open``` in the standard 3dsmax File Menu. ```OP Menu > Work Files...``` basically substitutes all file operations user can perform. + +Here you have an overview of the **Work Files window** with descriptions what each area is used for. + +![Menu OpenPype](assets/3dsmax_menu_OP.png) ## Setting scene data @@ -160,69 +168,6 @@ There you should see your model, named `modelDefault`. You can load model with [Loader](artist_tools_loader). Go **OpenPype → Load...**, select your rig, right click on it and click **Link model (blend)**. -## Creating Rigs -Creating and publishing rigs with OpenPype follows similar workflow as with -other data types. Create your rig and mark parts of your hierarchy in sets to -help OpenPype validators and extractors to check it and publish it. -### Preparing rig for publish -When creating rigs in Blender, it is important to keep a specific structure for -the bones and the geometry. Let's first create a model and its rig. For -demonstration, I'll create a simple model for a robotic arm made of simple boxes. - -![Blender - Simple model for rigging](assets/blender-rig_model_setup.jpg) - -I have now created the armature `RIG_RobotArm`. While the naming is not important, -you can just adhere to your naming conventions, the hierarchy is. Once the models -are skinned to the armature, the geometry must be organized in a separate Collection. -In this case, I have the armature in the main Collection, and the geometry in -the `Geometry` Collection. - -![Blender - Rig Hierarchy Example](assets/blender-rig_hierarchy_example.jpg) - -When you've prepared your hierarchy, it's time to create *Rig instance* in OpenPype. -Select your whole rig hierarchy and go **OpenPype → Create...**. Select **Rig**. - -![Blender - Rig Hierarchy Example](assets/blender-rig_create.jpg) - -A new collection named after the selected Asset and Subset should have been created. -In our case, it is `character1_rigDefault`. All the selected armature and models -have been linked in this new collection. You should end up with something like -this: - -![Blender - Rig Hierarchy Example](assets/blender-rig_hierarchy_before_publish.jpg) - -### Publishing rigs - -Publishing rig is done in same way as publishing everything else. Save your scene -and go **OpenPype → Publish**. For more detail see [Publisher](artist_tools_publisher). - -### Loading rigs - -You can load rig with [Loader](artist_tools_loader). Go **OpenPype → Load...**, -select your rig, right click on it and click **Link rig (blend)**. - -## Layouts in Blender - -A layout is a set of elements that populate a scene. OpenPype allows to version -and manage those sets. - -### Publishing a layout - -Working with Layout is easy. Just load your assets into scene with -[Loader](artist_tools_loader) (**OpenPype → Load...**). Populate your scene as -you wish, translate each piece to fit your need. When ready, select all imported -stuff and go **OpenPype → Create...** and select **Layout**. When selecting rigs, -you need to select only the armature, the geometry will automatically be included. -This will create set containing your selection and marking it for publishing. - -Now you can publish is with **OpenPype → Publish**. - -### Loading layouts - -You can load a Layout using [Loader](artist_tools_loader) -(**OpenPype → Load...**). Select your layout, right click on it and -select **Link Layout (blend)**. This will populate your scene with all those -models you've put into layout. diff --git a/website/docs/assets/3dsmax_menu_OP.png b/website/docs/assets/3dsmax_menu_OP.png new file mode 100644 index 0000000000000000000000000000000000000000..f707a7fd956aeb7367abd3fda3ba520012540e95 GIT binary patch literal 69195 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 XbfJ?s==gs6hXciXs_ Date: Thu, 26 Jan 2023 12:45:08 +0100 Subject: [PATCH 077/356] new scrnshots and text --- website/docs/artist_hosts_3dsmax.md | 58 +++++++++++++++++++++---- website/docs/assets/3dsmax_tray_OP.png | Bin 0 -> 218373 bytes 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 website/docs/assets/3dsmax_tray_OP.png diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index 4379f7e71a..0d57d362c3 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -8,7 +8,7 @@ sidebar_label: 3dsmax This part of documentation is still work in progress. ::: -## OpenPype Global Tools + ## First Steps With OpenPype Running -When **OpenPype** (reffered as **OP** ) properly installed and 3dsmax launched via OP Launcher/Ftrack (or similar) there should be **OpenPype Menu** visible in 3dsmax top header after start. +Locate **OpenPype Icon** in the OS tray (if hidden dive in the tray toolbar). + +*If you cannot locate the OpenPype icon ...it is not probably running so check [Getting Started](artist_getting_started.md) first.* + +By **clicking the OP icon** ```OpenPype Menu``` rolls out. Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window. + +When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task** +and finally **run 3dsmax by its icon** in the tools. + +![Menu OpenPype](assets/3dsmax_tray_OP.png) + +:::note Launcher Content +The list of available projects, assets, tasks and tools will differ according to your Studio and need to be set in advance by supervisor/admin. +::: + +## Running in the 3dsmax + +If 3dsmax has been launched via OP Launcher there should be **OpenPype Menu** visible in 3dsmax **top header** after start. +This is the core functional area for you as a user. Most of your actions will take place here. ![Menu OpenPype](assets/3dsmax_menu_first_OP.png) + +:::note OpenPype Menu +User instead of using classic ```File > Open``` and ```Save As``` actions uses this menu for working with scene files (reffered as **workfiles**) completely discarding native file operations even though still functional and available. User can use ```File > Save``` by constantly hitting ```CTRL+S``` keys for quickly saving changes for example. +::: + ## Working With Scene Files -Most user actions happens in ```Work Files``` menu item. There user can perform Save/Load actions as he would normally do with ```File Save ``` and/or ```File Open``` in the standard 3dsmax File Menu. ```OP Menu > Work Files...``` basically substitutes all file operations user can perform. +First go to ```Work Files``` menu item so **Work Files Window** shows up. Here you can perform Save/Load actions as you would normally do with ```File Save ``` and/or ```File Open``` in the standard 3dsmax File Menu. -Here you have an overview of the **Work Files window** with descriptions what each area is used for. +```OP Menu > Work Files...``` basically substitutes all file operations user can perform. + +You first choose the project in left top window then particular asset present in the project and it's task available to you as an artist. Finally you choose the workfile to open. If not any workfile present you simply hit ```Save As``` button. + + + +Here is an overview of the Work Files window with descriptions what each area is used for and could contain. ![Menu OpenPype](assets/3dsmax_menu_OP.png) +:::note Work Files window +Think of reading from left to right manner mimicking hiearchy of listed items ```Project``` > ```asset``` > ```task``` > ```workfile``` going from parent to children. +::: -## Setting scene data + +--- + +# *...to be edited for 3dsmax* + +## ~~Setting scene data~~ 3dsmax settings concerning framerate, resolution and frame range are handled by OpenPype. If set correctly in OP Project Manager/Ftrack, 3dsmax will automatically set the values for you. -## Publishing models +## ~~Publishing models~~ ### Intro @@ -99,7 +137,7 @@ be `project_XY_asset_task_version`, so in our case ![Model create instance](assets/blender-save_modelling_file.jpg) -### Publishing models +### ~~Publishing models~~ Now let's publish it. Go **OpenPype → Publish...**. You will be presented with following window: @@ -125,7 +163,9 @@ Lets do dry-run on publishing to see if we pass all validators. Click on flask icon at the bottom. Validators are run. Ideally you will end up with everything green in validator section. -### Fixing problems +--- + +### ~~Fixing problems~~ For the sake of demonstration, I intentionally kept the model in Edit Mode, to trigger the validator designed to check just this. @@ -163,7 +203,7 @@ To check for yourself that model is published, open [Asset Loader](artist_tools_loader) - **OpenPype → Load...**. There you should see your model, named `modelDefault`. -### Loading models +### ~~Loading models~~ You can load model with [Loader](artist_tools_loader). Go **OpenPype → Load...**, select your rig, right click on it and click **Link model (blend)**. diff --git a/website/docs/assets/3dsmax_tray_OP.png b/website/docs/assets/3dsmax_tray_OP.png new file mode 100644 index 0000000000000000000000000000000000000000..cfd0b07ef6e9377cd75f7a0383d1061d9394c1d1 GIT binary patch literal 218373 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⩔g^4RmBv&M5V}RX%b#SV`%BGupowwv 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 vY3qS@41vdxjwtb@ Date: Thu, 26 Jan 2023 14:34:53 +0100 Subject: [PATCH 078/356] fix 'items' method --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e44e5db851..95e194fda1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -241,7 +241,7 @@ class ChangedItem(object): return self.changed_keys def items(self): - if self.is_dict: + if not self.is_dict: yield None, self.changes else: for item in self.changes.items(): From 83d611a1f64d3a3a840a31117208f28427318a7f Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Thu, 26 Jan 2023 16:59:20 +0100 Subject: [PATCH 079/356] new pics and descriptions, also marked unused topics originating from blender host doc --- website/docs/artist_hosts_3dsmax.md | 39 +++++++++++------- .../assets/3dsmax_SavingFirstFile2_OP.png | Bin 0 -> 32985 bytes .../docs/assets/3dsmax_SavingFirstFile_OP.png | Bin 0 -> 36589 bytes website/docs/assets/3dsmax_context.png | Bin 0 -> 116939 bytes website/docs/assets/3dsmax_menu_OP.png | Bin 69195 -> 63305 bytes 5 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 website/docs/assets/3dsmax_SavingFirstFile2_OP.png create mode 100644 website/docs/assets/3dsmax_SavingFirstFile_OP.png create mode 100644 website/docs/assets/3dsmax_context.png diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index 0d57d362c3..eac89f740b 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -20,13 +20,15 @@ This part of documentation is still work in progress. --> -## First Steps With OpenPype Running +## First Steps With OpenPype Locate **OpenPype Icon** in the OS tray (if hidden dive in the tray toolbar). -*If you cannot locate the OpenPype icon ...it is not probably running so check [Getting Started](artist_getting_started.md) first.* +> If you cannot locate the OpenPype icon ...it is not probably running so check [Getting Started](artist_getting_started.md) first. -By **clicking the OP icon** ```OpenPype Menu``` rolls out. Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window. +By clicking the icon ```OpenPype Menu``` rolls out. + +Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window. When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task** and finally **run 3dsmax by its icon** in the tools. @@ -44,29 +46,36 @@ 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 instead of using classic ```File > Open``` and ```Save As``` actions uses this menu for working with scene files (reffered as **workfiles**) completely discarding native file operations even though still functional and available. User can use ```File > Save``` by constantly hitting ```CTRL+S``` keys for quickly saving changes for example. +User should use this menu for Opening / Saving when dealing with work files not standard 3dsmax ```File Menu``` even though still possible. ::: ## Working With Scene Files -First go to ```Work Files``` menu item so **Work Files Window** shows up. Here you can perform Save/Load actions as you would normally do with ```File Save ``` and/or ```File Open``` in the standard 3dsmax File Menu. +In OpenPype menu first go to ```Work Files``` menu item so **Work Files Window** shows up. -```OP Menu > Work Files...``` basically substitutes all file operations user can perform. + Here you can perform Save / Load actions as you would normally do with ```File Save ``` and ```File Open``` in the standard 3dsmax ```File Menu``` and navigate to different project components like assets, tasks, workfiles etc. -You first choose the project in left top window then particular asset present in the project and it's task available to you as an artist. Finally you choose the workfile to open. If not any workfile present you simply hit ```Save As``` button. - - - -Here is an overview of the Work Files window with descriptions what each area is used for and could contain. ![Menu OpenPype](assets/3dsmax_menu_OP.png) -:::note Work Files window -Think of reading from left to right manner mimicking hiearchy of listed items ```Project``` > ```asset``` > ```task``` > ```workfile``` going from parent to children. -::: +You first choose particular asset and assigned task and corresponding workfile you would like to open. +If not any workfile present simply hit ```Save As``` and keep ```Subversion``` empty and hitting ```Ok```. + +![Save As Dialog](assets/3dsmax_SavingFirstFile_OP.png) + +OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like ```workfileName_v001``` ```workfileName_v002``` etc. + +> There also additional tools for naming like ```Subversion``` in ```Save As``` dialog but we won't dive into it for now. + +![Save As Dialog](assets/3dsmax_SavingFirstFile2_OP.png) + +## Understanding Context + +It is good to be aware that whenever you as a user choose ```asset``` and ```task``` you happen to be in so called **context** meaning that all user actions are in relation with particular ```asset```. This could be quickly seen in host application header and or ```OpenPype Menu``` and its accompanying tools. + +![Workfile Context](assets/3dsmax_context.png) --- diff --git a/website/docs/assets/3dsmax_SavingFirstFile2_OP.png b/website/docs/assets/3dsmax_SavingFirstFile2_OP.png new file mode 100644 index 0000000000000000000000000000000000000000..4066ee0f1acf3e8b3509b7bebb5c39d6a70a6163 GIT binary patch literal 32985 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 literal 0 HcmV?d00001 diff --git a/website/docs/assets/3dsmax_context.png b/website/docs/assets/3dsmax_context.png new file mode 100644 index 0000000000000000000000000000000000000000..9b84cb2587c23af025f81345970a7e91ed59aebb GIT binary patch literal 116939 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

mF6e$k}om*27 zCU~Ia=GN5ID9c6sr7^X1fSwP0Yg}Zcp&S{*@6!e64EssIm*qKiE~%x}*v#xSsr*kM zl!pXk!6nNb@@EK{h}pBCHt+BDdgRv*ZrE#EcRU=CC^oIy`TL!ACG~2CRhu+Bi8tR8 zC}I3pqmH>p}OOo`WSLxgt!*F^mxgwrZ2X#`W42cJt zjbRbG?{hunnb%cCdgXLw6W>^Q_h#pl$W;OS8HF7>;@!>UWEIoCN5}_{Vb5W{^xWIc z=2b~;O$`UH5)lV)LCyEC6}s*qbRRHXWqUCHfiCej?Hg(tPT&9fN1B zo%{a3)q%Ae>tWidYryGc@Ss5s>O4mdBE;oBBFu;?0%L<{Ng>l;l$i>e8XB-_3=x4N z)4v{cvquG4!M@$w+cU1=rKUFS|0X)E{&vsH*VL4TfsVzh$l^i$;lo=zxX!;ag^6kq5$7Eg*u`$w>i=c z(44w?J$3c*i1)!^GiGvyu3M%%}v=x2LwTe~-*(%OTE%~27v&*&ot z9D@;t<+@Od;R3V!LVPe!0yY+x{GAYJgvv}zzCH-X#D5=7ZM-!qE7-jq}mcVs@IAvzG4&^jR!9n&YD^~q9vh$ee zf8o8BwW><<71wqg6HE`VfpVbr#?eD9X!>AKWt)bY|2TBSZebo zc0I%hB;hob20$H_6>cV3**g!FZ%57$XP(c#SI!4u(z>n-B4xygp}f}Lnzk7Bn6_K&1{}T;RmQzkgp>MJ4cOUqsI;a92Q%I#W24X5D1` z2wF=rbaLrd%3^6HwTot*0h}ez)!=l+Z!j!rUcO~^dZ)CiP)RqK zV_nhab0B$FT={P%I!RQl|HFAdSuj0A;Gl4T2)6-ez6KoDN-HZR2|cK>$?3w=8vt6| z$nuU6u8isUp4l@OB_XF8)kbO7`~(%P_vB>rof^P877@u$Pyo&M^oi2BiKmS~mSLu*{`F zkXN?(&3SvhWEVooVp_C+NmA6%&;X1bu!49b8TBSS{N>)<4aT;hqp5pQ{Ud78wFL?w zhdY4fdWm}qF!OcetN-T{v=hwI$<2bcw)Pq8zMZ}n+%r>(OLGp26&l_Ye}7UCNR&)( z_lA0_2s`#<3d|7NSjE5-A=$}wZ172}%*IJ9Xbmo!QHk4(Qg$2pJlK7^H%e(uWcz5P zd=YM4gy?exUz?SH*ND0*jarV{w;AGa&-Bq0e zBDV<&>b(~fPkZx}3RPriSK}z>;SMSv(*Tekn`qbY=WTQxYe2s*CylX-V;rUS5$}NkyodU z9Z3EvC4W7R%m?y9vbNDbu>&egzifZ>CuMj)rVuLMWPS;#(S-X2EjZ69^}5U7tA2yb(zfv4x;ua85I? zYi?_6tEi9%bFh=kuh^a9b;=NUMxSwGb(2K*!j{b+->iFaiaE!$m@Rq{G2djoc|45# zxVWYaMIyGtdw&NcjRy;jikD%+Z&ue}SjO9Z?QRWeorZDz%-Dy*tVEHD?|5(e7m7cO z*i*Bpo>2|7RKqo@M^T{i%JWoVVIfoPQ7~PG>)(Ix!faCI=qfbIl<2qXxj$C@zAdxf zX#ZOo4|XRN^J>5e8V?I?Nx{9o5YqIW491cAnUqwW10)1P;!sy&ph>ZGU{@wZWDfm{ zs#$b2v=LA?0(&3faJoAWy*N-yOBbp@3ZfP$p9aAMw%j{z+XaA;r4!0aO8A_A0y#TZ zi+TU;lqy{!WMT3^lHzsx@dkcWBsT(gmMLeh=@r=j(ct}#;^!j4;!KmHj{kd7_1YIS z>!hKttgREJmlPWovOg8sbo6w^KsQosJxiw!3maj$@uUCh_;CO*nLF=uR|{m!RGuj9 z0bvhVUtiwwO^V;785Y$FSyM|(%ybeU$X${-XoC?@iPAL22{v|i4bt5)iNY`0|KaUF zzeVvjfRg`e?;-mKZQqlek}E68-XIf-;k)Y|&z$$Uc8f3JHW(JLr;PM6qhb&^{cl;wLm4>p-~Az)<&FJM0XJL|ZXpXAc<>gM>V zV6!iw0RycH=m%j5Y>PRGS@kdwS-N%fWN&C>D!~&F2N$;n{J+3Op+?K+Y=0TH1n7%( zsN;EOqF<&0B<}vzv(Jd#k}KVHXGVr5LDSH8+LZHmv+_R*8@ORYu}JaA$%luJKJSxp z7^83+>qE2iiQGG04jvx&uHf5k6&Pqij`*1h8;_}U(_K#aNrhtPZMaFW0y?k20MJW< z1I{Mf_n27$@>Z!o71k#`MhcZ)?|Y?2};j6wKtfk=dJ#$K;m0 zrbLOs3OGGI{q#wsl6>e8yt3O52bn;U>&F^{mM&KWeRZcjt>n+2KZCi;!D3@U68q4uKXbS?{j(29*lIsNxUjF!b$Mv5M;Mu`deAA*5p=8e( z-YtE2i}YrtKr-RNpQ%y;8}{XqAl2gAqAfU~Jmhr22n5COm!pmtrGHVV5`u<4wNkNF@wsP4iC}tWhKPek1%4V9rh9xp5IuzJ zDfe-*9qdMd>UZ3VPmF9+*^Tn6cPiU02TLm-NJn9Lz|1LIG816_0e|r;*i`jQ2Sqr> z&^f0K|Aa2@;-UZ`p*KQG_2oEa3;P2QiUYT9crvNpq7V5KKxD(_N6;YEEZ4%t!vlGE zHZNf+@WK*ez3hfn2%h?!|AM3Vj@L1-y!^|`%4+}`pnbdux)(?yLd2*G z@WV!&$`%9=;Z|o+*d8B@szPiWLeE3M$l&%F39RsgNKAf)?t=rN;BHU~;P#&Y@w))iBRW zRCF{88(VC2G&Tl?R-;{}8e?>Lc(d;xZc!ZiEKei;S2YZa*{~cn4Vt8((5w5cbeOSo z#YiS-HCHY?oq0YYu8g3>!2n{Om>Bc20Ag(J4e`JyBOlZR8lX$@;OGhiwe}o_cJIHn)KImEA)13suh+_-3F??&HLYK>njckZL(V;F9)VQKJ zmX$VuJZLdjLNyNP>*y#k21*Q@HZ#!^Ebx*8pPLJ)SigPywz_H@)ESqR1wyn{pw$3# z89=0V;Mj1P2;p^qBTNXxlIbX6r z!+0Sj=(4GFya66b&3uUjLEVU)qMxh?xRuZJYRwv%=J(E?dS#}ZI?L1TAbI7AE=fr5?>9GZN! zhz&M9l!K5T>a$ZwB76GkTB+|?m|OL?vYtkM;B?o2xH<9G+P0$NaV+_%oGKKHx(>Rz zMEcgMvP=jRRJ0{)OJsj10;JvKb zfxAMfY`SgzlhMW?1+N2we^2YITstB!FF)V#^}iEnlcc1i{JFT0PvbNL0tZ;yZ$Si$ z4{HFf3{dhRzgSBQ(V~t|9X1DPMb{lR#3y^dqx;Qzl^SI{c1v2ix&qK$f1Ks93Rm&J zlbzYoY+>OsK@r~YCpXpExzpalQv`T8ZjSymCr4o2)~qJPlDvh0w>$e-3%e>_oWL@F z*KIvVR22Rz2o3cu{SZQA`(Psr_YQ(fK0no>fQndHxTN9{z#b1nw+5>>`(KX7TUN}& z|6<+3>gHNujWISddUbGXZ|IPYKq6N!>lDyDK)4?I{0ca!gij|^ zHeFO$*z9NkW<6?ZE?}Z%_dWVP50GQVBF#y7+%DnB_UEAbhuk5C%xuMy>+t$SCIJjl zhhl}Vc!9+X#ndTmH4BoT(28%nVITnn&}I;ufSbkZ7+A~}c0-nCP8|4b{2RUqq;C)| zt4d06!=FQ3;s&t42 zhhw6fD-G%Vh_S|c=SOkN-D2WgCfsA$DE{Et1Y^3E#V<&(dWoyq1sNHwktn(O6PC^E z_;E!L?EL!oZ^=#JNVrx+EL*emol%)l0oMyIYXxgyFV`?niJ}HlLS`!?Gw}NQ>~+3c zFF4KS<*lCoUW1bdj)WWKLBXEXc;SHf@IbltW4qwhB_t(wvUOpy)_0sY2BEZsbk)DL zUE8hF#?SBBjgeALc@MP7z@P)CwbI7GzyLCfGVM3F14)Ga1ph0`S@dObf;Gy_NPbo{ zekJo4jnSt_2fd_$%B>p=!8a6W4Agx#-rn9@!@4rJ9~GzwBbr=Wf%&17G+RE;|CUY+ zSEG0$N4W6HFCG`OFO0W`I(-KY7?;FD{_0ur8d#0uYArQ4t*N*evC#OiyAxlnpD&JG zWOqze)yuOHV2&@;%5Cu1`Hm#L4S5V|@w8Zm8>{lyJs5qifa!uShe4H8)+dPNw16zZ za1wZBX=TyKZG=2w$D64db`l)uKrBaNf1U%hRw@ZC*~2ed3p8qAbHL?&k*K3Cf~^|kWs zIVbdGM49EZ-54m)NYdm=n`b{E9uZ(Nmh#WI#0?a*G;pJ(rfyQXms%{Yr76+ByGsrXPuDc@Uvu`uOpBa2IvC+CAHbKiaN2O%$H)^huB|FJ^ zIg@pO9G$hD9V-)42EXez@T212g5oIKu-QqXYSw$XZtssrU($MIY*gnt^Gu|B-MsZB z(!VX8OO#uup|dkHbTl*wf3aLjgc1=ZV(hCmSYzPg0%-2FOJbz2g@+VGw6sVBIBvVHjdh6SvI`2nQ+fLQd2oK+ zqk5CPe4X@+zqI5|Q&#o&&wfA@w0i(_@RoaSSv1}3xZj=HI#CdxL|oKRy4h7qo-@8> z$HFI}zk`*O>-Ne)3uEC;OoWJX-?%a^5vTX}7kW}0ZZBVbF9x$K?Hc0{HLJ84g{s-t zr}Gwr>B+ez*TUXs6tkbxxbI%4zY}Mw#3ug#Tmad^#ZedFH!D({WCbc)O`a?!J;6fX zGyi)b#%5-Ejh!WO6YYj}(&Qvt#QPM9 zaxL^Y73}`ic@gv+(fkKPD2E ze{0OL9?6LIy|}cBHL5nAxRSk^Y^-WK?fdd28&qUW)YO4_?*UF@(yUBMrZ5RDe!g1}#iNBsey?c8fHto6qXty0`N_-@RA{ZlQ5YsDM9zcL1{a=sUi( zGyF}due(NIo4MW7mixOe`SWTOrkIJQy?s>$v7LJ(5n_Q5b5ZA6_UO8kG-j&Y&;GC> zuRCy^aHi3q>aq4|{D^Tfw->ra~yIZDvL-E;z)OtN-|qhFVV0`9i=sqTj1Q{E;? z86wx!cLr?%xx=@2;;%bvYB8vSLkIY7nR2E9TDn|KR zZ9+Yn#nxg?7-g@mNs(lc5M!}_G;}Szkgae1DT&hGdm@sGtwT5KnlOMJo;0F^U8ixc z9p5*laZRkBSZjR9;Qqkr*UW7qs^Bp#7}TwMu=L%jnVE=5#BO#390v3%zmDDC+bcyD zqi5$Us%t;Dmg?nxSaE`YoUK$fD)es zCiuno@3f7F%F`1-C(5LX0Zf@+zIFbKk?Yv+M65mNRHjv2oYblwkU0z=DrSc^eUY%6 zwJiNaAN~*dtZb-@CP^^z=hPjecjsTe24Z8k);6TX%~|I(hd3F-ckkTUd8&Gh zjYU57wVdLs9j;iw7pm(YPN5g!JfG^cphjy^_Tfc5)RawtxT6a*{6geu`i1K~3P}C&FZfgCB^2|7B`s)~vtmKW!G=D;TZg zy0)Nr_N95$FBho8zPfkGQo$_C!lHQZ>l%vac}~;I>i}>Xpy%THpgLLcge21*)dzlq ztlsLwnkdFa)={Ck|Lu2rm6(5TXXA*)%$LX%ze?JZk4O}}nu1M(a^vg=jM}Gi|(zuP*uphh(nk*Q17_x2)% zaD|_%AJDWb&18a>4s%@oxP_VYyP4Ut$@`celu=sGRa40~FzqA;Yw~>V6%!o@4fSo# zP+)cx4A%Dxm-0WP2h!r%&nao`!hrA25Fm4KJxf|^N;uj z=(~1sW(#w zRSHU!8{D;fKSF`40t8<%~=I4g}7P*4DlVP_VfqbjQaxzHgd6 zg^Wl_9y0JOEco%Z-G|m$sxvecq|}>H7E^YE85|4cB8tH7z_eIRyK z;Ht}?u^l^NYAsvbZ;u$s_T^@-Ih_Lyn6<2utV-gLpBDC3dWOs&RO zoK}w{>+xqet+_d-K_}rhns!0Uv0t{(TiT`$Ws{oof7FC3z7XOY)jE{um%{QAO6jXJ z1mWovS-pZi09<;kjm5=%JZ-ZWOo(qz%>Ygp^Mr;*K%33^%dh#NFBu)*Bzva#jmj!ZiWvv<9Br!DZ@b+XTxSh#7BMS^yp zv)q{4aA{E>M2y9l3))mv4kOH-q_w(KxOi(YAWZk^89Y%8{Nz|1Qgpct5G$dz_N9eAf4cD50!?@n zgUaxt=5VR&80!zM9MKa7hOOOsXURv8IXP={b308r=eKE~fu2uxPsooM+>VzECrP93 z5EZmUd8(O+V0bPMKDEvyhRgIR_f(`Io+VpDL2|HHJzd|d}sbT;#HmU?<~z9}q^_<-KKC-i5w5O#Wcdlw?Yup#o479Lqz zlV$F>Z07F1#!)pk>716$z`MVwL{aUz`@V+Yvv4V@^zpW*Bt~?U%J{xhvlcT@eG^fD4~z}cEE`DGWBc$iLs<40Q}BuJ;XD!RKZ8=4 zjwsUG%j@pLXIKcRdb0`WZYKsCwm7}DW4D-*1M7sp8-0LXDwOT-?tY*xJD$#;=NHD8 zNr3tx|6!2pRdM`h^PNqoE;sw2i3EsGVPOd6n`Fu6J)O_yH-oP)#FX7*zb3nmraevc z%TfE_uCO-?}pbW4^8 zW1o9_!Oed1AxiG+g)OJ>$4h%4;0z86>+9`paJK|LT|%P1&xkWAi&(!QQ0!V>U;FXL zn$^rLqdAK^m$Ag6`paro`kjx)(k67Z_#gE6DVSX5 zoML-a z9g>K^0zOR|3mHaBv-x!SYU^|wsjkZGCkb5V@W1DK#(+ z{ifYTd0c20yV*|L#$&BjgPo0Pr#v(1nT4jZeSRzC%VVC;$YZ_{W{|6RJ7nRCwL6#n zlyq-AZTGzMAX)0U^a;Pdg@sfq%>%^3;^LNKz-b-nJ$}7t*iPkJ;$Z2Kl^WzpRE*f4^u5A*u_GS<|7}xq3Ef3U2Yip)|RR^(^(^esFuW<$V`N4f; z2}h6dZ9{$ig+RpZA7F?GDz`Itf`I;Y=>`LngPw=4e>lxiA=`cB?hwj7B@*$+u5==TSm8=-Om#ao8!5UU^+gZ9974Jh4}#g?c`Di6qj2xC9-vG`WUaMy(DulNv2pmL6kVEc2=XQ?Ao( z?6zJ5d{KXCns)0!f|L{ji^nfIcDD8@u6A5jed2RTqa}BZqUdr!#zJgj7G(1Cfq>f= zeMWD2jy5bL(Ts4Fts^kYTZr|96Bu!|$7v(#;&gZS6oVi;7KBbk+{eAaGp zB9`N#^g}6$(n0UwUh6I|Q7kuKk2&%EAFCS6|S}RPff_r(n(E61oEe%c6t|k@&R`X$Rq!>oDGQWPT!tCTk)GbUL zvg}K2A!?E@n;)((uYO#(ulP1XQwvT4Rwytb1oP!V zJtMCrtltjf*B;SH@m1w*;8&KEG*5mV*l+|rC%~T&r8LS4(W~b}E1N-oz1e945C$`I zRQGomtCoWg?vR@3e-%!iS(x}cxgPn*M<0WVVDsi#3PX?({wfV;-L>3t<~9bm(4R3M zokWqj#UrL3d|@kf(h?AXz&~%?&v-1(+=vhm@!fiArm+ic< zlE_f6^EX#PI5wGIW|JY$%zKxC;yLmfJ@}X$jmS)=AY*uDt5c?E!%H?bo+K{`LSuIvh}73qX;IH z^WY#Q;$20k^N;f}3t8kZP1M|Qk$h!CUiZta`LiBcIY)ybQ;tW}AfJ8&JrRBZ^@{`(f?L?M0ZVJ(wIhbn4c><7FE>lx{!xFiC!r|4&l6Y#x> z4tM^gM%52$<5(v8TPTaO===>)K2K9pP2i#G)S%EY&gS3F?{bZ5X#)=rXd{GDm+`Xt zdUd))Y#*j}$FCN^G(@&`M3SHSlBrx!4t{ow=MHiz%{+@T3gEKaY#Ol$P%QBz3Sgvnr?vXQN-Ts6>EkFHI4bcz?20mTl!lX=O4B14r9~;C-1wU-$N8>aYh-_b|Qhd3t9-J5= zMl1|2*`qS>DePs+Jef#LnlAoiC;m#3zm1hyu7;;WI-%~VceJs+UptM3wO8mdf)|Z# zc<#ZSF%1pnI#(}DHFcx{IYroWC=7fVG=vuy>LTcIj%$eW8c6~~$SW>W=(Wv(*VaFy zvEDVow-OjsKipxkheDgM?>BB3-vciI`~Tw;ySl!TZ)-!#51;`62lw;Fle912jDV!_ z^XP)&YZc)^YYXStDS01354HGu=O?yblJcj=1n)~pW*Ck_CqG3O!H0~&6!eK$Q$W8;zE6AO0biInZzTRuT45nY@9%FJV9|LyR=JjlQs) z?d4nZA95YaY8vWERsC}xC8>KTdQC=i+%p<)C#iwYC!mLmD?w1G!1b1?Afn+}}PTF0<6$6bk;oSY?d)FdC>Lk6`iu*WE1>xX#8UZ5!j8+3P*qnNQds6hVVMjl1 zw6a?&ZRTK*b2?a3V~ynz8Pa~DN`fURRa}~1cT^edA*!ULXCvp^Epoe5wCcL%P%lj7 z``&w@5PR|9xa|7n_jeaf_iW0N_Wo64Gq1_kHQLFi>{Te)<$3>Qw6x77Z&imbz}epd{)(MHx6d@^E=DR z*X}N0K`HYkj0S#Ttv}FKM#ycu`&;=|y3u(YN0oiFERL#R7HN$L00)(^rij z^7C&A?zFJe!3??v?vFZuMxZ-+BfC7H@BP?x&M|KWS*`qWv^34tZq)A3hlQ-s$AbO<_QQU9xDN*PXGUT8gYfmHV&3SD7@YexDUsl9h=WBQDjIp3ZhK zwLjM`YuXxnpT+SA4UzNvc!Ti7S5xHjQd&8$$#VPBef>lPA%P_FuSAuAu+b9D^BFgF6mC7AW0{br{gd~t8IxSCDPVlPS*^B63RA|6Z!T=GGS)<#5 zlrptYcGJj!xOZ;jR&KaLzQ(Jm(#7j$31Px;@t{DI#4eiBkrhIoafTjlw34T zAH|B)7zL6dva;;zOD8*CJ_^Q}qisx`r2awdTTm9-nreI zy-QrmKixHlQyQ7)Un)Qwshv%vtC?FwZ`Gx4?#fT@YY_uvr555Ajk3M1Eg)h;u!EtV z4W+xA*pt{OYv+Zzwt~jbcj*vjQ6*>)?r}+kJ=w6C;fpOS zEB9*i8*6iHUW!jLMM7{zyD}ocae!>-76Kxf10EoeZ|IXeh2yovKF&KRefXEg)I2o3xO^8>~;@#$$t2VVJ z6^t95!HGI}F2w%*_Cs|TbP9`f<07J}ItltCm_n(DcF5N3Y_ztE8H9{^WpFW+lg22a zoH|VXLpE8hVZDpEi!#fX!a6Y}(?S9!m!c1P7%LUXDCONR!p^r;o5G#))d>|bKJ(CA80KcH-;VP*l`w(I-Yfrlq32mwDg%;fUL;C72`_`uaL}py$-h2mB#K zM@5CAxI)AF^%DxRpiYM-(%6&|we+|T-m7fcrRa`6a?)$l{DhxFzC;llw&$V24=}hE z8Xdu;o#E=ir>j~CP5;|CeNtf}iBH2(TU$zZ4t8(necz5I9ZD`NIn~rE@@={HiyNra zR~*pct-ET?-h7okQJe5xG91&mv*ph8TSlwpz;BiJ1Fl~Vwj!aSqQa2l8~O)>C=QJO zqbC6C1?VV7t4^3QLR=GNp?1^otez*bg&&rB)yK3v!cGP4j@6w#!iRc_XmROFmjHdDL}NMmZ`B_x)yIW0cw zN1KPy(2<7A=IM-J!)w}^cCkA~DG~2qUtW1zY0o^0EtnY181a+9aX9&tKUG^UmB2DI z;`7`05fT+g13BLFpk2A+M+^a3V;(|ldGsLsoU`G^M|sJe@t>cr9NiPR;~mmDTMQfb z9B2R83$`V5ASs@|-`?9}VPvco!oGL!V1NJAMDUh#dsbA(4R&$vy-v(39CJ5r%-F&t z*yodz;rw=W17u?E97M^bX&8`q0sa#WH7GtPd(|W|n=;0B>Vf*A5&2rMHi{ zdC`!hQ}YcT5vGj&*@Exrmq%*f#C3wH+`^+0<(YNxr*KhEp=c{2Z8Thq?)B$cTnPuP z2AuyUh`qeNe=Zq}36Bq6!^(0n#Qe45P(Da)*tNp+(7`=nPgsAhxP&)|L(K1`j9Cb- z3^+l+qzMXuTH60`9KLH+L*E|Z`PU(>Z^7!$-G?r8 z{kzZeQwHxwTGv(Y`hH@54EmGeg%6nlcit;;$Vi5lan8-h8=gFeqa+_59yWD zJ?Z;-luhjGjDs)$=coSue$+cerlzLnw6W1|-T)Vu{JTPEV;IP5zyHg0NQ>v6GLgCA z?o;%R^q~unh+1)H0N(z9C??~ep|z!@ci5T~A0|^oJzh%b{cL~E+uoJ)DS$W|A^Msd zL~@r+?2+&&Rqw*`1%&rf^6}&pj=LTs<+N-PF8cIbaUKfJ#J)Sy+FWTphn~M5);%gH zZ!oY&fm=vkT0bUicw(PPlpFrYp%vS^ka5R9j~=9oJ2t~GP@b76vgdGcy;q-VmM??+ zq6U5s0#8SioE}GzM1LPL&)XCB*h2cj8|(=!aR3DYzW~)#v)+d6Z3$&?+yckA5in+# zmPR0&^$c`$ynu`NEL)f?g(9WULCy^K=KXs4|8$i*a|>*`C_TgYVs06x;PLmszAD=8 za=UT#HRGAF7DhC3V+pKJCEJ?APZ{<9D~Rrq(%L^{e@3^aXji{wls8N1vU3(?E_w8ZjXgkzNwzXr(|%5Zq_2&7fIJ2=KBEv zhlSSN5KJQzBKET5vqJAo-bb~eHD_<9!=a^vG&_Fj{LaQ|;XJ~kVf<0xBLZA+b$#&b zbd4X}wCA4&&V4lbCYY=s4->eD>S<)$TwQ-({LRUs^a>v^ZGi#9dq-LsVD;0#eh7}# z+!=d6EjtHfDWn6_A1)PFu|E3n3^@XM@L8-Gs`tnHH_t^y;9!IQEWjc}J6qz0oRz+i z!jdK6e#22#XSB|?GPhwF9Aa9)+{d4;{rf=Cc`iW9EvRticFc(W-Zgm^V%tbhMTOHL zsJxtcaczI!E~t~;!5iwwgaRdc6hz`cQd-(pQ+pO;gJnwJ+?78W=YzKlJC`Y%T#^I4uv9p83N(IS#M8Vfm)p;w^m-SCL2B7-e5H(^@r`R`{aTe!hh;x zUfxI1(&m5ox{GxkT?bzc?%P_g{{#v~tb{??AUC=1a%()cF3128^Seq%TIroelo(=^ zIB_(j4<8;>YWw(n+^cLI3518g5R?6NOXhB~jl*FAdO|RQU|RjCJKRdzEc4)vqJ z_sI_xt%_K)$e!2#)$*yig9r}ay7$6^E>Xt0ZdGm3UwyvPV6w5EmrkwT~&w-s1y3 z+jP1XonZUZf5ul?G>WY>L&E=7mli%w)yJCqq#Gv3wX>6bTO!UL{O(8`uU62?%THHl zGR#{(7zjBWZH_;p2U_1Hw?g*+R)GGKmd6l*gJ$i=>qF(S zAHnDR^l@Zl(80f+sgkZpk=rN>wFj)QPF;bXPm1+yK0Xs+7ydv$%Ed7KLO(^!g8LS` z)+ZWzIwc#*=BYkpPVa>u*XdcGu~M1nZcgx6@cO&SOP)G2b`l7MkgOs6V`A>6&}!eC zUvG3;6yu*pv7RPEz=EiXNwkp}?>JLxA%e|i5j1ma2JrOMUAN27n2l9EIXpWS>|oJ0Pw zfZA_G>^oJ1ERj7e9>1yoebUdk45gEfj){@aIr@Ym8{a@dIg=>P>#{pMm(G*-rb6i5 zWP_O1hl<6zVks$;7+{s%u!v+HAIgg%r z%{()XrsRM-!5otWA%_N^#t4y!AQG)c%n?+FR|)KBO7-;8XE3+g{puAp!I*e;0KmWFM+=|od+$VGY%f>^3 zgPVNvIZ2|veKX=;E=;MO--Q?iK~8`N0(ZODNogxzc3xgE3-LW|JsVs}3GLoyw^yyf zG6Un44UR3$DoN|5EO9R`Y;A9?uOMh75(sX~A-Iv4#mLB#p4bois&)nRpb+6}F-Ht@ z){(4Va&Oqi3d#z4f4L@nUsOH95O(<3YhsK&bidSw$QCaZ^?{VEoi96vJSm z?KfiIVO47b1Ju=y&-(D01tS0ZcV%25ZPob=!>0j-^D~{^_ixVi1H`ebsvCLuP=0>+ zpgKcBUQ2qF)i&Fm~0o(}++O?HA`iGr{3C!r|Bjj;lDp8?Jt28nKL&g(%kUOfI%l47|tzUBT;l!t~Q;~(N z3_>=!dLiH3K(Cjd|2)50T{1$jNEOm|+=c}$W>Ofh*XEC{7a|WA6(B?x)O4SVe|_mQ zQmnwTb+`XkgX7n0y^ceUpOG#_mUkv({_$fkTZYD6F1RuTs#d$g*u5=86NGI%#RAe2A9J#OhaD!hB& z;63B=T?tGlCkJdbPr}8M>yH~9h5sT55SI2?f-=eoHBEU>J|zP6D}8JUWg3|TnpdHs zcD>>%Gy^&Q;!0S7Mx69?w-6sB6MLT{MWQle&e2qVa_yf~nW&su3S3u3vh!_y4OoL_ znH}=jsL{WUqLu};n-gcbR6A;p^ohySMkG3l-1d0b5uvAU+2)yg95T*+&aNH2d(8BK ztaV|L)#r4_@Y)NGVx9Rg*m^>b5UADOtKpBqD1jiQeRogel1Zrw7^B_ae_i+lfZrm< zxzDq(?%N;k{6DVVI;^Vaiyl58NQZ!QN;lG-QX(bYAl(hpC?z0*ARr(uUDDkR(j_h3 z-Te-JzQ4Eb;~#pT>%DVk=Ipcgn!VOSL&E7zqeF)we%fg?r(~sndEIT^Tst={2MdPE z&6WURcDryXxo!8I{dxfBuN%emj4@{SD@l|P#8Y6x5wrQ`d{SU*QlV{o{;H=O?xkj`iN;;n&AFoe?j`ZR7!q$YU0A(ciXC=4w&84%$$f{6&7mQD0_1lbpxLijhzFH^ah{=Y1BlqEMaiLDlnTy$!5EAmA zj2>?r$E&)F9`u4Cx4r9Y+V8$eO?(`WMuIuGTgF8_wu%x9s$ASVMNHIqjI>|Rh^YVl zrUY)BD2$pUn_gBG7I^g8OU*j&9X1pkaf{EOM=P40C!D&xIWA5-_-eLP0+9s+c zz|`k3`|U7&3gnn?WgUe>tu9}@cwuvqLQ)0jrU9+Pv$OqXQlZ9cBJ zvwrZe0O;P6-7qQ{j-(ezu=sGThQC9@=Hi56$?@UD91`G+hYhpA(8knPv-Z+eM)G7i z9!g(e_-YAGT;m)w#SkxgZbbOqI?W;s*30NTl!V#ZKifJuO)C0BsS;x*zCd))P|@%Z ztYVN`gS4fnW%l#GCpY}YRao){CuoaOzRNFLuHSzEmg;&m+Q5$jNdAI~=2hNcJg*kXwdKUX z86A(^8QZ-^dE5wwJ1VHZZUd6Mii^SBNj~TLqN+-#*>7gp9=HDS?G|6rqBFCKv12nk zBCB_wAM@){deN?(!6cp`KGUr7Y2b8x#fu*p<^D|Z?&{~=Nfl$r{+B>N@@h+;2SV@m zuE%W$T3f#w{?}d-B)*jUbep0?)f7gwU;^0OLM^~^_)+^?%(`46zt4&@5j2j}&rYP& zF-pfWKgA6p*MvS_eH`XFc%ANkY!G7a%6`_Rbk|R>68DLU(l6mLT#H-C00~JD_kbD> z;>UYD=;?ht_wJho8}279fI~$bM-E?DUo#RR%s{^v4JrqIO!ONq{bwRj#hF zU{U8$zR27A$d!t5=iexW={XSuEFB;qkn;Ez68CtAR2uufnd(Q#67~qPXWj|u(=5*`W>a8Xipu?ZsJI{C8a@;t6 zlQ6;3i6a)~bRJcymHd<>tDApDHmHE&c&m;6I1ykrXwdQU3NMR;;#qxs*mm z;7a{QI$eST-O5l@Y%G^3QCP+s8Wnr{gNNIp2PzsGz@vX?c;u)3P7%6!WNRPPZlr+F z4Kf{oMnY+zmSHWQX2U&je^#znoJiQWh(#!rFD?DwzdcEUq9_J17g9zEtnL@VxxzY+b;_9M<#Qaf04@M13IyQ z&j2(jfq?L%Fbi;H{HQ0svoZr3L;kIB?xsTg6P1Dq;ZJZ8+!}dIVu1siK-Vwz4L=%m z_(~acMql$6KXZs50OBlw(FrJTDZZY-f&97^@P{UWpfccg7wc4iXt% z{IQZ)VTgq{bh_NiyATFcF3>MnQE?hPljELAz~?^AB=mtTpcUGoYKIOdb?iakzi{fo z@E>mVbMR}11I9wX&B**VJT9AexZ}p0@mOb$AByZeE{-%9a&x&JHnI$|Nf~p`26z5t zBcSe={21uL@%I|uqq_MCLA|bGAY)3|VdBS!?x}~{=BxSw{y^gb2D!|TL8(`-{@kxQ z@Ukb^0M{+;B3x+wcj+6GiG_vY5esmWAVHu9{pNg+k(D)vuFtn{7Z9)&X)FFt|LDp) z9Kc(fq&<46%pB4b2hJ5z9!gy&e>m7TYyP*hh7l35)=f=*%VDCZDSJGxalTcErt%5Oni1YR77Et~MKI=e*z9uuXoqKOAk;@uz zy8-FX7x3xOL&j!jtwq%1+Ls@BbVD=BJ{OlE zW4&#IH>}RFy9z?lM}DqVD{IS zVlf2oddTn%W=B-nwDaL`U1#cj5XZmXJz?rh>?*2*ROT# zm+U-ZO=-hYYIsAJJxvt_8zsa(x=Q$`cs?}D9DST{Id%2x{%iVVF%kg+DRSzNi2MGJ zH#C&8(T^cmlPRvIMZdVA4UhO<*d>+y8|aC6vn?X z@NxO$c3)pU1c!74K}+<`%tWK1EQFy)>air8k(EZE-C$Cbbuq4cCf=N+P`ffLBNrGD z@|%>unm2FuIpqMENf<0SA^??A7o-*WC9&Y3IcI~q5Xb-B9#UVaW8gRKs#nVbD)8XW z#trNdpiMiC)8A+qB|D`><*IsK+l(boPRFMz>lZL1e(YGLcF_%;XtHWoT0NqicaHxM zH%xI#TEuub_|Aa=fK7g<_&_duIh}Iw`K-*}X{-61IY)(pH zUHu6Kd)+DYJ)yHBXLG6w3~y(E3#O3oSm@r&XBZLKlYDLr`Ue^g&0;thlm0jDO8!`Z zaxKMU_Fnbxq`Y}`YxG6z?WDR;{Yit`-oD$5o1$koGcoCy($LSXD1HjIi5s*TXOIh4 zgJXHKO6V+h{;xO6;CcSe5&?K;*F8Kq{IO9}x=ctf7N{hPrw_|fM9AVuO1^q!QK`+J zQ~56`0g~^Y%V*+tScs5lhcFdu4KNetGHZ&ihb)iuE)Zn1FWGvK z0;vW-7uUcNJN_HL?&Y{?8}=mG@?M$fpY04ZG4!afor?zDKKRCO}QSkI#?->S}M?rhyUD zg`Xdx%Jhin2Vjrcz_gX+6ng+wY2`bunXI zG63Y{3y#W4+LA=+km1BKSX@I!(ohbEni?CW0`#Y*NBWE|c4(M!LK|aEW-w8Mm8j0w zC@=}rU!Lkq^-#Oxd4Or8OGBk#yd1Fi#U>==_e_7|K+K|1w9kg`a*y7jk_{qsu>yBa z2_AwF)9jA7H<%BL-;H2CdawQ)+c%1zW$h`hXdWL2-X*<}@{@kaNt5}tbGtrgpDqH) zWG2?g=W!A(n3^8{)6$Db?{_2NK{~iPqz#oXTZcx-1C_|z2|ho8Z#Dc}+w{q7^mhvt zX8L{CvKIkGJTc3$9I58%E{9?e90Eq2QM|g&;ZirPFE3Z(2>i#(2*3FRl8K9k<3`&1 zx59sM!>b!eR8D8x>Xgw@LCOL5A41MqW1i{(F!ne-VIhltg0YwspY=Q>z>WEnQ-Vaa z;Cr%Y-`&73nqBw4Ro8-n(7`X~zRXel2M;K?`QDg*RfIpfy0X8eF!g*p5s_Fldq19e za{RuHT=?N@h{pZ9X)MbcIA~_(d8+rhsn!(%B$M#fW$DGBW-w@i!oeceb-Qx8*Ev9) zaoUk%HDRh-M4*7jy=q!)!c)7{9b9gkUxT1;-kr%M9tuW2-8d1< zrKrfJLk{V`PU7{g()7_PhIPE%MU;>~OD^RuM@=2djCo^O6F9Wt!4r#?gj2T@VT*M1 zvWj}tp*Ng$Rrq)~e}8}f)2Bm&jXSaFk*!Y37o55azN{ydF)=S{px{Y$A2|Cr2Gp1t z0{c>aD?iGoM;jzraR)`}9jgm|49-`P%KSMzKRK*k+>61WG||4g0`AUuOu@&;V7&$+ z_%FA++V~$olB9zhf57t4kA1k@OlG&uINOI}Fr$eGrlT51xZ2dQwm_reeZO-I#+~m9 zVum<&@T1ijknTCU@+O8|e@-R4?NV1<%mPkZ`pXUly^{M+c0(yR?;c%O##IzUJKxV@ zkv*gR{RMkkNMlOxYrPd-V$|8_r(S=mlrk?oJq6id;CF`~W5zgoLso zt>!DAQ+Uj=k>t*ziMsZrVfFYVc4)x8ynj6|cOsH$fk$Bo{}`xAG6Am(V5;zhKm=s^ zKEfc9GIzF}?7FfHmD4iG2gR5_I+^U-F?#`}{H0Zby;pM&_XiKc=YNbTN}Z)hUmgdh zSI#Us+s>QDN)VOGz+DRnqcVsJ9Us2l@a=bXjc`SaICs?!ylq1`FbzmqXzueELWKVq z7cgnHy38Cj)L2=XZa_9ef&2$1t8_$}Sso24RgN**cMf{+9jwcsrXg){L@n|`F!t{& zXK|-ryqJxUXT`m*Kjf=-{uD*zExfo$@ma!K*q}&VfayqsZRPuk2kAHJ*yq$tml`9E zd-Vr(HdH?ZB?Q-e#Y~){O#1E*hCbym5nKMW_)hGFs+}*Cv8wgVB~Kcj{7Wi{qK5zY zS^N%v>u7AL6xmmt)`{xLKR#*ek~FkhD7=$h3wYf0r}IJp?gl&2$X|_e>zb%zV<<_0 z2x_{%*11vWj}&Y;HV;3XdJa#{+7$2cwYT>zsXv`ZTyAByHrY~!LFq~H^+cHT!Cqt( z%9*3i*Ku3#U>0ti6eyFs|FM}q>g?v{G z+?&w84`h5W^qU&_>#cqy7#VEzW1=a)(Bb&An9d2c%4IaY#21tZ!q{7Os^4FRXOV>) zdRE`-Tc5=Wc>a~9p{4#Lu*LvE%DO+A){jj?Qo^qVCNwr*C|R@>^6D6~>Rg4V%C*tJ zwA6duSKLK3|A~{MigB~?`l?@fj3@H2TFx5c^NBQhpQqVtz~hQG)rT*7{>3T><$Knh z166~`3)>yRryUc;;@HbIMO^W;w>@yHc;U`vnGB0WKkIi_cRlcYn;olvu;>XlFIDtc z44?6i_D*B89`Fc{_~~c_Xi)gBoD_V}M?4~WsB>l~CT!iAd3!lG*LRUZhX7qAocucB zAsXV%Nsh@hzUeC;7(j!ds9%l)x)yGFvGSi3qo#Ucplw1OLw|2P5TOZwwDP7oQbwCg zYLXIZ7Zf1udG+9dOk>hY2=Z6RnxqXE7n?wW((H&1IjnPDlkjL~rZwN~{|Pg=)y3kL z#p2$YpZCka14s{@KofYMj)Wv#^l$w|SxEip4qC&fPs0M5pCyGk_`J-GxbJ=)3r%rw zpQ&6$P>aRlk>>u^clK)&uAR&Kn&kM?XEvp2IGW@+3b{Mq`^r{3it*>ZDhYSPQ3g|U z8~1t^TT*jQhZl-C+8+xUZ}r4n()Xx!!rIpi0E#*=*Vd++%k<|mJ|h5Q&%4}#3#uSy z;{CHrBNs8?aUyR$Uwbut@bHmZD-g4+MU^{^FQSCVh8vGG5+$Q%x_rgP>+fjNCR(xZ z?TkCyQ~Y-uE|K&C{C>$7A?_Eqp)4)@N^}CIsCpIurx!pC z62;)=E-of#u;G4v|0^+kx)IUd#c`DTgh$SGnXoUwb{$!|j$UvQO0f-=r{{S%xiI<9 zSw?KE$mn6{M~cCq5_!4W1r^vnxXT;#r2?^YkN~4^2Nqy zwB8_b)nJPyF*x-M&^Oq)EFB*RSsxDki70WmX7wBrdTM{V>=ZxXNge6xa6F#C+I&BG z&iQG`;O6i(M8_zy*O^JqSr>e&PdZrK!Y_uy%Ff3}z?LKllwYjMlAg!ae$#cvm)_1+ z3n*wUKpJ3HR`%g@vKcqAAeH)yNHO&vgF!2G;*qOxY|gu)0+;~1RQ)TV?qOQ;(gqjq zs1`qK1WElXFUYC7rSk>vRP(2FLe?ZdwTNn%b|UpQOl}MCL%t?Xflb=Q+tm&=;ORBe zV+=zs%x!i*H%+UU3{#(Uq&lC=XW%hik09Q&6a1i0>ff(-wwcBomTNl708v-v2!7K*KMbtapMQfg9Eh@`Y_I?S4zKo=yF-gY|7zx{o+ zo{VAtnU%HhiGL@`I&5|sqDTCHObkF6u+fj9C}OZaaeuWE3c8}RIoDcmz4`s*dwXR+ z6ugaMPQbjL2+@N_iJAApn&b@>d)zCkfBA9_1Ku2 zR;f(K%9e+_i)V^V$Md6(w71jD#Im{P)4LX{rBnI&@UUXy-oLGz7vG5>d}}Cn>>jM% zGv}6~gU}ut1rZy=^^RV|L>Hdm?dVvqde&bE4>2CN2MQ`_TwX|=ELNb>6$!r2r55O` z%Rg?k&sbo(<$=JCzimv)^Vj=K8R725gcqQZ4C|nR2cW`luduQkjCw7Zv$X4izk`;3 zfzJx>vj7)`T0_UlfRk?P)Ff(eub^H1`EwZFWFyP2buOi%H*#`i%w_h*H)Td|UI6x6?W-IQ7yo zORFTvImL}n8$Ut?Es=hn7r4nY}qQ=-+mNLJFqq9B0p|r zS%zKG>t?$)?KGRi&z`k{^i1)dV0c$NPr!5?atQZ2(^7Hd!hzGwmkW?y}iK!R|q-wdNGd1x>TCNZ(U!)FF0hjRa(Zo zH`bHm12nk=o6^cNC&p)Y+FPWlbO|%Ztm#LOXC%{&1W6Znq(^pKw-vI6(Es0~`}(A? zPXTjKP3yd5lH0yx&izGc(=NVSHm_T28hObOCC*j$$61@Sr$;&66 zs2t&)OGg1zqxhM<77x#)3)GbtSnS_Yv=>@1a-YKHwI2^$@jma3_2WZNBf4X1iFT6>N-769Y%56*R1YZ`TQLpHz zrmZ%Yui><3Un*NbEYT1-UHl=wb#Z0kq-UypPC$1$jL4eAa`N_dU%{5Zn@TC zYbfR&$^l_W13VaAgzF_9OoSd2u61rNMEF#22&U~!b6Q0W87drj%5waH&e^O2T$M%z zQx$+hRswqiFaC;Q!KJ9Nh_&NKncxb+Y zAHqjWr-=Tud$g!$$i!Wiu(Q)*hc8lR?Z{3-MKr)LKr zXSlt6-{g|EpoN84$6ZdWjY>@bkX>5rUPwR<%wa(5V zBL*Ti->Pg@KsV3jo8B2iaaX%!eQf-)SPqK}ed_OA?{`+`qp_Sdi*73$RPLqTtbF0p zx(_V9c({FHFA5^9DI9^VNYPMeHe;>%MZ;Owk@l>*yrf4hnK6I3VEe|(8?=S)jT4gn z`hX1&PTtfFF|D^+ATn-QrV3ItSuYJQiwkodG@~?M9{9?6>*VXz?cLS187vv-fk9vo zJ5JX!qr6qAZg?QPT{~MD^h2@kzc|Q4fIjIoQ_~VAZd=2|+0B+yKTq2#m^ToesjEae zKDP;&uqO_w>!Ohf>hin|4-*W&(~}N3(ds-ABn);`XU?RG`fk+yyf+60BbWrcC#i)4upgJ zFV4}J4(@y|8fq`@9J`4g?oXX(+a5NKA_S_MJ&%^Nq)C0=aiS+$Oigdi+4b*+$r%WI zi$^|3NsiaN5$3Cuop*L%20t=gLgrri$kpZao$$St+V2jPt8T0@72BQ>n%z!*IA@-( zGKFcG7CKLkQmG~utmw=EX!0Ma-~3Cet_1O?umaYtIywu z+anA!j#`d~B4vJyz3$p+vL{f>s4YUZjgIGS#EM+?nxtQ)Y2`dm@1Pv{(bh-i5rZ{T zKV6hi#pY)7+0hkyVm}|Q1~&^miIl8>((oeiI8Ar_&91Dncf{ihKZ|asg-H^>Is9(R z`V{fcJ;DCtg{(|9THHqekJd}1k0G&2va+88y7F3_zHSKlAqFHWu6kRxXN zop*=+1}!$7u4MIR`d+S-IqQr zliVW<*av~(L>SBQ>@Sq1MhTx} z?U9{7zPENXP#y2?%wpC=G+1qNB*sc*`4ixtDQO(=nDJJy0aw$$^DmjYy&}5`8%l#4 z9XIrb3`7p-cz$@;*muX_I!$vI^kSgz(%X5;AS}{b#8djsE>F_376CNwds{GZHVb68 zYC8FAGftu}zguWVxo;|^xb|n)yn;fWi7DtjV#X_)dd4baA2&DQM@FDw7rP4`bj!`r zQ+S}0XMM7{(6Ql0=6845UF&f;BgdC#y+y$%FK;jbqiHVj#qoY8OvzvN|HnvP5eX#} z8vQ)rlY{+}fN$)v)nS|p+P7m5nVDZbcMFQMTyg0PkDp5{^wcQPf|sXI3#se(XR!?>e)W3h zZRMxn)tm0#$e2|sIK;rfjl1pe6v2%v@4=X%(7+8r0O-{W6Lo!5=&X+tEo30;fB##N z6eE9ep?&38<@zih@q-wq` zC`eP6W&a`P;2?m~BnTih|sdG=EH|myB0lCr9`- ze|~WT^YZ(-=YpMmz~I{GbxE+svi2bTdptG<0a@k7YsQ@REeC$zqkeX%43LZh&{4?Sf$xcn?_Hht zx*^y4;gnzY79on6Rp;xF?VeW}@0zNqDWj5thVQJ=;kIbi+`;Zo=gckyyVqq!cRwF= z&~zi35W+gSeN5#U9ZqpE>X}=`<^3juU+| zRPR>^eIiZKgG1vQ@g`Sd@wpf#TqvuyYitn&2Fn;D7SeP~hFn)M&qB$}D)iHaOTL#5WNQ3YAwupI_3#HQh`$`HI|7Ev{yCOy>KS zQ@2tv53)_SwdMyp(Q)geb0_CIWO{Ilt8N&D3*TdqAtC0O>N6CRA>qWBr^C+sJ+t<; z?;22oj?FbnWaZ9CzC3PHY?*@MGAbo~tIxIOf)^GR zf)o3m+TCYYLTq!|BMGnFKgi$NZ+2BzRjr)wEDjHyrSSYJBr7yXx|k_mY&aq-X}%p> zF03{1b$UN5x|lP<_FRmHa?xvP05FVhFFrb4e(h1EL>p}ILx-HWg)bWYV6k?%^dZxRo}GtefXf^47IJ_kQtWm(e^fVztd8t{~y*zZS_|v3svW<@7!_oAHxLR zF@=_{uGDkvyT+B!Ij(R*)|!G0>sC=RA11f;=nRC{_1V?H00RZ3tDKINR#D1 z1zUd(#0}lz-IjPf`-i4g;8V>>7=hb=1gsy5?pyEV;+RIPDpODS&9E;@H>gwgZ$mt~ z(Sc8!^TNkX!U*yhgo6Dd9*7R`pFI~Ik{K0wWt!*?yjX31vP;6SRPRo&Bw1`TppTbr zT=68ra@Eu9%2TX#OE-_P6ezE*7RIp(zQ!K2cPAH$XBjns^H7S;y!459A#U~ptfkE5 zDfhT4_b~h%RGm|uo=x+Y*YJl%$ky)ohovHfuv=;nBx-c0J=|A;_4XQgZEVP8jkdP7 z0;Y~$y_;TgsYTEITGrUZ7IE274Ylk39^;S;+V6T2jwwtN{K9$YB3Z!cwB(oJS>g|B zqEtP(II^mB;i{Lk&pq!OHheVCH5GVVV1Cw1A2waIRdHDkJG*of$wR1#GTw%hWO~Wv z{H_D#1zv*e+iukserE#Z6O}%mtA;O4;6Pvz%f@2S;69oRs%G)cEfUGwI$K<<8SP(w zBe%Q|8}F!VVMWkSr ziTsz0%vQ&bQ`1oTNC7)^-?52k*V$neMOz`C>k80Kciz`$QU>~R$ONyS z)s-P)MU!7;$lLE}orgz&r$4uy&Ix|et<6lP^=RAJtK|X!zwQKbT463|j3y@83vGf@y4%NXu2n!*oH?GN{wC z{`!sRwGceafYr2Hrj8yZt*&PuR;tvVuu5$R8?um0AKDH}T-{Bhoa)o8;leoEgHwI@ zmN7w$P`Rg1p8_wW3|q;Dmuzg9bgiymct7k2GO=j59q#N0p`7@FhAA=;-`nybLZCHV zYgV}lv}}AflHG-`+W0~=`%-vLTsA}!ffw^v8BSw6&rR89Ci&Qc2B(>ecgK|Rne`^% zdEz{EZ^?IUu6buMxE*mE=_l(^1e5q(fUw?Wt1oqG1Rf8gfScr8`Es7s64~sc^eRGr z;l^&_?wD4eI#mURgB%fyglPsVVe;g420M-$m$qn{Cauk+4C80JbaBIRZPB;IUt_Ga zkc?;fWtCwzUuS8f_#kFRjbMJr(3TyK@2kLtV4&|4#r-Ha6IKy@+nzxk&xz4D05u7U z`u-jN$;)=lmL&&ZFaaz`rj->Y66?C-}8?*wiRwZ3;i&Fcn8 z6BpCYZxL@F@TqM4TKxW!$9Dfk?^gKK`uqnk(`Qtuu*Z^D#|DD+X&bu<&HBe0Lhe%T zmznz&i}t!@>(iAv{TLET^590Gl8X8iv|fjd;vXm-rI*Z-H)5h*+>dR+Oz@5(j}dKI z8Tk@!TGjdp{h2ug#YDBnKBvp)%AiqvKZzh`jI9;bkH?1o>Rl2Lo2;K-SbThu+3GAK zqld&qMg8>%-4Rr{EOO?psW4yYOG-ThJ(-2;+jjwrx@NS$WtJCuWwCyf-g=)I7vQn_ zdV=01AjNKFVZl4$WToD>7up;C!e01e2U_c~WWAT+Su_`)9F&%`He~-Wh$^}xI(s@< zGU^AyuC#&A(rC#boYiAkdKoCHOvgT@2G@55*CM(7pqGsrt4|55Jw7h^H1d%7Qu+`c z`Yi4julkmBJQ5xf(`OOm=E@)=%Aum7`aDh`Dg{$~Le)B^QMGlCTGeavL;h3Eg&?I%I95N4t!2r1j9<~+3@3Q^(@81vn=Z|;UvfQSPqI*+r4z6m{hYHlU&i*vu zIe;Y~6Sk^U^0Pt3xTshuv-qiLXl0L*jFQ(h$9uR>uQhT_N0Fh>KBc0~&n>v>8KNr4 z&|k9R=L~3`)DZMNAd8a{@nZ*mN>kaSKojfL{YA4qC!xsQDJy9G&N%M_W&4+Y3Fr49 z_z!aWoky+%pNspOgVdLJH+@|U1d=+bG0g6GNn3U!*7_xPO!>GJkAa)`g%-u}I&6TX z&Wj0T9JQxM6kB$_mBHqf;85%AwZ=S+;^YxHUoUHNDd(0jRZ^$t-}<0sKdzB6N`zc` zj15v}e?Nn1Kv{b>wi* zE4;}e14+7mqo*tHe5`e&FB9~0qX$`Id|!|&dk5SVg!&|Lk)j|nWAeau@*AC`?==l( zNY-m1Ls+raD>itthk~tRgW*lC-2z+Y@ZPXJD!678Pr8sRHB06)EKH6yA}Z^|(IP!! zn6Xa&?475lr(5VevxnPX>To5+kZRU&bJL#Gvv#lWnVh>_Z)&yo{DZ_{=6F!2cF4=q znX{!JzT9w?fFaeVOZ9?Dx94UHg&ITT^lmxTrfF_gplzg|XOeVglQQ93x^`%Om;sT^ zEXLzg{tW}1M{Qv3ms&Q*+P4iu95CteusGGG1Hh&`@zk-{d>*YlawcT~A2UwkeJB#{c1Wrm{om>6< zis=%+G;x0KIS+Tk4z8}t+`KDuoj$j_?2M-U$f4Vxt1*6`M;60na=F`N z)Ia@wXIoM9IG<4tY1-?T?%u9vvD7=U#YOqJwxNmU!<#5{j$3AwkW7d8#~Qj}ZKj#| zGd04gryAwjRzHK;5EDu*4KF8&RNC@w3Ckf$-dpbBJ{-Q2xsH1gNbWA z?Ue~ig+G;EP6`wRy!jVQ<2#1*5oQ#*Wvi2S58whFpCRc znJU#af05&{7E^A7-%fn>I^VRu`iQ?VqTVa~%PUjNq}TP6mdJ=@`dS(APe~r+h_!Q( zEEo^e5V@V777;Z1Y01z;n+=ip8qHOQCRMQ!n^M_r?$sh*0!JW1pN9iCNakvfN^XgO zBlEKRONY>hRD_KP-;nlJvvR<9J;R2?AAY8hMIti9yWZbk`5qJT*iq5adg-I1qXXLt zqaSR`yu@EX?=k_0nG&mGayL@5>r*IcfZONV1;QsLK2^yc{}tx)!|*yc@7HGc!EW9{ zNs-4fy<~sw$%-?HXTNJaoPQMF$F~fXO8W%@$-T2Q`Wd5)YFLzO3SqCSU6lJE2$las ze3W>t(Y&Oh0ZigYQROV7M6Has*k`Z3H$OjS*D^n^fQAFBUgB`m0g&fMQ^CF~I3jmY z1H%bWF0_Jut}eL@g|veVLyKuLH}wh)qmqNRMS^px_ElULO&NJwTp$SlVNYT|DAV>( zf45Dur_LCfo0i0am&9Vm%@lvUIjPEi17Ea-1`lkqSV_zdjL&^tNQ?c?o6gpE6liZR z6XgAD`FQl>d;iw`{iwc@8uuks%f45e}GQs+u}Kb6SS5j}>#=g_H0%2JPQP1AU* zHH@DuNkhmW|4eK-HYvc;Kh^p1ck+9PrW-5jNUfpCP){$w+XuXoFc$43V!yM`|5vnC=45Ie9q3D)v(iyg}A3viQ z%<+>zcH!NM?<21?E96C^Y66m)Et!dnE%BSiq%IR>csp)9X)p`=y4gujVluxR-mg8} z+t<$s2z!{VI0NO}s{pHXcXWJo_iw6cbU5Pi^ly^MRB=5@f$ZK1Eu8GNh&5Bnj= zio?D%njCp0`7zjnKbaId`ZWD6B<`Dxu0_ACj`r2%ndWZgJC{w!z7ABl=NGX^nHs0C zM0>9cR9l@lIIs0hEV(EWPi|!Q#^nT!g5W&`&uTzC%qL3pYDBYuaXc3d9ul1_aHUdF zjnkq%#KK*vKilDa-6$rj#+J=EY)#|U`&e8Q&g0~9819wyPo5m4CZ7#=;7v58 zjwo8;jH&dmmbbPbhuJ2g5pJsE)9;Z>Jh0-lRf$JGb8;g0u`+{3zAmrZGbr#$N}89i znKEAA*ZVjM6APnSPu2NW&2}umffO<0N#Ff^7p@=rKM18(DhToB3BB3h|I-Wbg>rs= z{?tqmyJ@gBeepxfO8YN2+b}eCj+j=W$}^qW-c`7Z@MkCTP%%3;zkgQqW>VOT=W(D4Z#~kcDEX>@EF^iTc7lWU=Sm+^!}!OGRRGH0Xr<*27sBsUoES94V6ljIj0tot)AzUd@bcSJn7wmvUU+!zD-|f@x)FlK#2|=dXTKRIGX3d&D zxR5&I#9DugVo&bYS1+1ZT^LOm1}D+VTZ=%qP|YoDr%tftrdOs zeWGkn3R4D02YC$+1`_4AcGJLT9>>X#2fhXZxpKYmSQGv5a8M7|)7pBbcfSZkGxFbt zAwr|Nir3REnk>)6q-1ww$HJE1d8B$hSXJqw1J{_HK-4#0AwWar*2r#h*~B7bL0G43 z_rq>5olRJTYqUx(U4b>Vr9(L`t@{tFy`Vx~Z5=<2bKr=F`c1X@?EKYq0Qt+`Ve#%o z`cEN68}HhkackNP+d<7|bomn~J0`P~(a(8&WmKP*_8zcy(TRDKBZ&z0ZeO`kt=O@u z3hQk8enoEC?bD7FtiAuV^Zjwk$O?;Ry}l_U%JK9WK5(1HKF=LqHoKo@&^$RULz&e} zm#iM{`)L_8@!Iw3qw(z7g|N^v*C)9_J3T+QIU4f@_|M{9UJ-AE;XGcz`WKd%-WE}* z<_Cej0f8J(iXM)9XCg$jp;%NkU-?5t8=_$;>?-%pw*0SON-Q|BcFHbs7U^T-Z^BmL zEQ?Dxv|#v|Tg{_#b$x9(WLJ?Jzs?P^UW zP~9k^=cg5u^3tj%B|Vmx3h42(t;rrIXV1s<`JY$uSo5WwH|T*lMe>a8V`|)pk8fnG zr{j4$2NCHij3{AqeYa0mIa$i|p^Kg8b2dxurCe3#Yq-*PuV?8FlZ0a`IzFA2Aw5rHR`cM5w-u)(z6&2OU93k<1&M^epUAbk}?_#?; zhF87HPrkhP+##@E#nxmZLLPkH*k~6)2tn71b;JM*_7!XMmo5E@!O_J01@-{Gz0s)2 z=w)a#Ca!@`}c^kqS{*0lMd^=q{P#?%4~?ONpubkIY073 zDNoI$-$!+sXTU=ynDU6-5wajYN(B2>|JgaYlwn8d zuc#M&=0e19f(qpkr9hpYrU+c^;nrU`hW;wcfse{v!R+7L_7$l^j-^|nTnX8fBx2w@ zZkH+lM(5ZsR0d&m-$pEz@YQ6#ucX&(i5U^UUfUcS-75AxXbYxwV&AC~fS^$_?Ub3z zg|F!PFUOei*PGaUp*Bt2&D;WCP0JMm>M7dnUy7*_4d%!?it+CuZ+z_o<2d6u9Qp3J#1(=)6ZDPw(z=w00rqJ zZ)xR6KsQ+koZEM2{uZPkM&Cl9w$v!@KE5#Jm5}ZvlKAN>YhUVsyV*f>)}O}LsdtJt zBm1zOj6y^hg?;#$)onvs3BE>T^BYI8F5^DKbUJ644>HofG~fLNW>CYIDgEwy7X0?H zSx69mvQf46#nD(i4<=cSc&47vB{ zh%7sV&&T3Vl{8xY^6*x0W-Rx zuRR0U7VQiruWS>uDCrrVV;QP5RueH7&lgc9r=$3jcfyObd>A zgdQ=vbec(Yp|jw#n!)zvgFatpSHsyVTS-aDa6Hzh{)-Ry7mwb5V~Q)m^l(V6eWvWe z3e%QlbI&Vj+s@4_{7OK1ZXa#SR!j6D zFOeyLX(nprPs784Os4AFXMfAe;D3Dfu#kYvtAf4v;6!%&a`fbQig@lwW+Ul!>Texj zur;q%LRWBUjZWG#U44%c?q;hs&h<1WNLoNBc8tG>s@qy^IG7vIQ~;_HVr zJy0Jq+8ckEYx@oQzBiMowV?k#Sj_T1(9i6_!mTdYwIpC!MIM>Ody{|j-+tYwy{QiR zltH;_m)D=t}LZRQFSh|l1ojtmVA$?56oXVg20 zo;-Q-0SgqS*pGAlz`OK?>>JvzjV3L%yHPn-7*Q)LD@)5QAbmt>O#ZQhRp|P{!d6v* zY$CEp5tA-I_LWcG8-`fftiEhr*Gh5B*ZU+IS@h%+qXXI~{-UK>kZxan`Y(UP9}N{L zO8HcVBa>X%wI2I<|ImQJAz9KYmQNt*gqxEggPUCY5V&~DHCoOxU%T>kuqFA2uedu7 zI+UxstPnY6C3L)c-6&0At9-pSR4;B7++}WviA34JdH?BDTKZS--KvXmT_pV^W9R;Z zRGbNs%?$KqlB}GiUkgo+OxMlB?{3<^bYBPa^xthSrCS(#X6N46eHXESE$1%mr?`@^ zfF=;85oz_V0|SKDJUbc~#B>=tD#YmBZPaYde2$pFUumj&-|#*`b^I_bngtv&qMue@ zXCZF8m>oc%vM|H)$3zNx@l50r)v(ZkNK`brOQ)STKpKJ~Mf4xeqv$|ESqxyyy_rho zSJjwO#OU3XfXb>{+qUE&DE>cW;4fM1+MCqQA$QYf)jL&hJvwFPqLeGn_@<9JrwaCQ z8`sZfhBB<4!9uE?{p%8T2BR589C$eENENtlP9}x;F6}Q5H3}IH@ zQ&Uj1+ErgXz9O@kiLp>0!P>vBi4Olj3-pk$mXt3C`zrNCaG_CWlORSb_jd^6n=!4F z_^C4pF+lawDFVxX-1VvQ{_YDQC(Kwv)U16DDg(%nGa)vMk;;(swXdq^_X%M?BdtQJ z!?sd_{bOX_mh0YZ5lar$>o%4}ILrLkV>i5a`qb|cGL~&R>V5EW>&lC8ac*|$E883_4aEvV zOT}rcX&!-zr{?6_n=A}LSV;Zu{%)QRD(;dS-&o4^yvWegdGi`GlZR+3jf`mV|AQ$% zkY1`Wbnz=54IW2ShHziYu&D|lD-ajME=X$9Jn`Qrl~H`MX*aq@QM4pXv$^{MNnh%( z0edSX9cznF9P6}CY&a?@FSuCD&HSA{tJy%OVsIWZs`gR$X}5^sF?af)>&c)R%=L(wLB?F8Dr z4*VbTQoa39hKud?Q8r}O-hKre!Cq;ALScqcpu(7sTplKnRgEJV{5;wJqJ&3@COmDH z(%$rgNcC?zXG_%p!hu z#trx*U}Tod^4z5V$O`HdihH+SagP+zt)X7KSxLJG*;npWeBGO|S;W^?^hsS=6;S33 zbnRxmN1r|Lr~6j>OPr&Em#(y79+DrVCMk33Hx)H=pVaIG+rezrZuao-7!W?Tc= z5Wxrv2!Fb{KCdW2XSY|o^hbs~e|}DaW>Cn^M*bMr1uR~j+q83|{f}{8TM#_KvyExzx*`aUmWlJl_|x;59U*E*YO%J|x{C3kwX*7niE zcLNJM_q#Zg4>WdWd9H611&3<$h$yE%83LyVa$#B z%4YXv0(=M1_nwrDEXNmv5+INj6{j*d9RDz?|9b$(@CT{HV(-d{J%{v@=(=m%>Al>E zVZyQFkJ-AkyX^eQrZ~92{E%3?)s%ApW7UJWwg$(Kp zTZrm!X0$Q;g3oxTe`Nbt)u9yI)6?@M9XSLjZgGFL+653UuVgiVcaJDN0qJ%0?PPoq z=(OMUrN%Vc1(Kei#|$CQRKC-0hzP(USX7W()@b~1CyaP8ta;QTuEE#zA zTR(Z(Wa_zT6J%W@+UbmaXo@YjiV@z<+f#1eNQC0nOLsGe&*0u9e*&=BC_M#WYZ*}P z&0VFLZH|Hlg7;WqEd!{S1k0U#P1Rqu1^(`#`9DBT1&XeD5C;NG@r6}_zf)Dd|CkE( zKA2b1c^m2>;|=EgXkh0lYKrQu{vO)TP>Rl+90uTd?EMrLi$%L5$ z3^sg3BI<{&E<=oBS|4X1h--M#S;ohFEWfn>w-Uru~IdTLiS=ROoQk3 zhllgr-@W$_L;%oHO?K_+2|R0|Uw&PQA0B@@yzG6{Yguu*SV{&l&{A?xI-jkG+`Q^N zevJwt8!L9xH)&zYC`cGVZW_iNISs9 z_kYm9#ty#wLW0|slnOWrf!Fi~nWbyinHgc@+&>%6CYgwy$@`hTJsBT6ZmB)|%wGu!^(Kei zkI$zeQPH)5w>X%1({V*K04L~ca|VSd7IT~5Vz5VVa7bU+%tkG*Z0Y7KU?j6`-&I0; zsyQ(u3sBVyDw?@_Ey>($53qnkX`9I`9 zj!;K~tw=ob$N#rQf- zd0hvWU}}*C3`6fq^O1bbo{M}JU7N`OHD8qo#qQ|yvXTzp1Bs)YX`jP)l42ApO11>p zE;;Nn^-oH5ipJXdtR1V~=z zVA5F>^@#Gwzvi!eAG4mCVScvegl1HYG!job!`$!=BUxFUh7K3pWGEp3#uW8YTL$3S zC^f~r(fJjX`D1v^I|V{Q$!2yhgoSGcv4!KK2<3Vf7}uf+uYr()YEWDz_nV>o&o5Fd z9lQC8COi^z$wL#--)I{t2U({FRsS+0TBfS3phhzI%j-o#B~|kAw_j#OY?Qu~#naFM zO}v3icLf}-+)(dqY-;kx*v!PGwhr>Pf{M*oN#pfOr9q2RY6R(9+8#pxIjnI<9Lj_{9vGrck z8E$QV0w3G}qPz(uC9VqU)9DxA%;p*hzoyrzImO?~b+{rRSQ$9yKOA2uwOIIL>J+i* zx$!C;0yID;uZ$y@?0i-Fyi^vO)|18Kv(}N0^DXCHgG6R?*x4}@`pj8t5ORCm>3!a`u_P(BAuTGZ8?KxEty4D7e)r@p%fclH+FSPyW}=%+N`1x@~9>+ z4;=1@!R{O8B@$qd4}>Gr{~XmxdjCmZnZFRXym?6VtCc%2?A2sZhPc7nhjaCa8mca= z#xAw{pvGF}S5`G*`9_uXvQIt1IUi6TS|%?ibKl`VL8Xso)fl}4X)o4)qwCnq@{Gc- ze9NzfDe!2{_=u%;f9kq19T4pHEW`lh*=wmt^_n{LYOgF1-N`GZRX}<63YNWp8SyzY zKF8xq8`E;#DGRPKy4cA>Qd*tdv}ea&&A~!HbdvN(09|pDY58ShoP3t?xJqHN-cbI5r|2Kt8M?um!HIup|^d1fZZE7)XkHemP zmWu={vs4gdY_nBm#yL+uV;!fnGy)Z}STa*IKV<-O>UHC;uB88eYY* z^-FM)`=+#jPlXQpD}7pYsZ9KF^sgGN`RLgObmZc1qrCCzEe1keQ&eg5&CMGqf7-eG z;GhlQZM*&=E&+z@xXiooDf%?;IIX_PQ*kOI453=a=EPtdMU@cB34i{W`@r3f8c{!{ zEkB?RP5uWSglwvzR+&~%6_!Bn6n%D74QC2Jv72CvqaRrS2zNq@Z{>eTP1B%5rOqMc z4VEcmO4MSA-e7zK#StDdQw6^w!rCZ;5iSiCno24<;{0t655^CP7+Q@17pBheEK*`I zXGJOvY$i(f8maK0R|Rk9=uOsl-`ucuE|-IJQp{EM6x+!?W2>vr?nU$|1c=T`Z~c7Z zl{*69@J2FBl%z05${bxYi)>IDMC-E+;=*&!@;;{_YYK zi^(MMI$$yjc!@Ws>y>W?P1}-S<8Z%>I{C%Ljv)*9%YZ3P{yT=`OS-%oAafWEh)@!) zaU}nv1xTuh`8xUOP65I|Rj#!LEkCW|bwQ||I~8EZd?4w{D}2v1dun+$byzIWV>fp~ z3i?CEShSF=PwQCy5n1SAP4{i)+*c8&%&Q+wNZRyo-t|Dz6**~6mGkga7|H^JOUq4= zr~?mjbU9-0-}B5^Zf|ejm$;xfNvT}s^!Pv0A!-FK?q?J|_QmFOMrL?-}|0|M=*yIK!SoM2X}h0jxC6rIe19ftM1 zptR5VI?)oi_DVaB)%z}pU_ZFil`H=-=#KD9&XbcWx^XP6{C+sl&V0kIlOo$TM5CSP zTDaSIb4ls}hk4GF{hs=biQ+GH*Uw+R$%E{6IXzkd+e%ICuRmvPf?xi{j&ZmRh41r7 z0KDFznbEjS@qm_=xterYfBLPyS3?$hI2j=doWyEN-z#p+aPWYNtHz?^%0hV$Q!ro! zjr2fSaTJ8p>Dvs+sZ+Tbeo|2S9G9L7((S zL{XOqvOBe8Uvel>u%_A2GpG;9zQxQi1q|^*x>2mrwqiO}GD#0tN+L>g)$`O<)cns{ zMSda4JwE4z5TpUAI8b?U?&&n-32a%|eUjI0qrh&3o?uU|FZNRU*iwzp=5g#U&-f>oLyy zIy?ElL(Ow+K~-G*1kUCChI|LbcAzL_d&l!tWr8taH>E)Q_!czSc0<7JcDm6O0J2;6 zzOh$qXMReW8CZ<$LH<{P`LCAM%6M8_S>;i)&HYu=juWV|(V6yl)=;D6#&^`xeeNAFD zD4KbjKKBkx%=@(?+S4W4C$V0CfA92t#@Z@G3`g<0Ap9?F#LvK3`pt?3SOiZM;i2cc znTJZbAw8L47nV^UQ(a;){U$PE2_`?o5B$U}g-55b@92Gi^e}qhJB)2B(0J49xyq0f zRGd~ZZY6+;AP|G?*S!gFC(t0E80VrDjWrqsK%aDWb^r)GoeYV9hbvw!8o9GS7Jx z^i#8msPbWffEsi=Byt1V{i&fP+nJy@V|4MA2&`%^5Xs%j)3V~am zZBGW)pa6fq=HMrfdcX02M|N-1TxlGbT7q&==Y%^89X$^xRJb5Y47t>iMiim=2p|yG zZ&-f#d*xY6X?Jbfp`y0RqE%e5Z#Il=Zp@lip}B3DajrdZfJVy8jAzK(i}b6+Tp*08 zgesFHo^bXg?a?g&i=%0sRSOFLuUCdsqH4Dj>7DCumEA3^p)rx!Z~)+T>8_w9ryAIz zeOdl*xc?z!^z*aGyOn$7i8JThzkvT06+WN02DK(^;nyg3fSFm1wrZR~ov3p@Zw)Fy zqHFQW<<;Dcz{!5s3#CvtjEhHR&^EXX=rQeQOELfOLj^jjp1|GV3FTy%b6i1xPbIp?al_GkeRwiBB0^^P$tdN6%w9QHYq@Jw zNutgAY`<)F1GB`MKRZR`!($eTTkm^R%x*Fx5+&FnH7HQC=RT%~t4(yMEGRBt?U)!e zV9E?r*vI^+4a4D6UJl@EWuV~hZ1R7h3p&y6w(sO0vyhY%q`y59n| zn=p$_>`@oSa?X~i;bwF-(lCzJ4oS)%XCBCmeed?yH?FO2sm|Yu`dyC7t-MLre7tGY zj6UMZqf?{fDM5TcBFp*5)?j~Z@5I~-sL{U+V4gTw!T^$${9exJ_CB0Yn3@0c}JUBN2V*wPC z^fMJLDi;pSIVL1Y+v{QZMj{SM0Pq%If{=H1Q4U1CnP+9VysAhlgYZ^d>4ei>o#tT=(Fvcva7Y(Zn!MsYe$}%%!1Rflm<+Lh! zp|+f%yJeRbMf{PQ%l`ED!2x>peW$TZE-dOpQCK+>ot9PbS+!jz3F0xrHoLA6sfns1}>o!85ssFB~V6qS=s|2Q)af2ab+Qe5~8_bj}yO;%^3juo%TuDh3b ziA=3Vo4cn-?=|W-oF7)kAKeFSJ`6IH_ZsH6<4BZFez_b)xQ3s{{k9Z7e=xXNSocLL zzZ#XM`~x360>Wr2GX*6j%u}g2pyrN`ivzN7q3Euye?|4u45I$8Fz!v00d5H3U}X{D zI>WYf4#KU=0f7cQ>Y|)@*jfMMQdNdFvJS(QU4aBc8L`3<=82c1G?5b>CN2XeFyhna z6n?%}JU;AIpiqpy3xi9P{1p)UBe%fK!rXF$2eLugt4s$3RmBjnBq5b(Ph5@(H?a%({0D!m^IdzKxO7C%du$4PBuF)kd3SE? z-V#G)OabkYTfLVGX5bCIXR_d=k0V5f@jEvrTSM#9)X5R2Q&vrkkl26;0!>&kL4F-9 zLxIV1{5rHXi=76)Kw-Mj=QZ~#FoIdfT&YbNL`J=gOuXI&a9Dxi3A zU0q#rFea%P5BiuJRoCsoY-x?zIDwuN1)FTq^vS{r1>nQ_PUWPtbB`Coh1NsR(?XcObh|9`ex8mdDOUuc9TqTQ{aB_7WPhkw;%*sC;8C|c%M4bP8 zDY_g}#jp35ZJI8Ud-oOKoA_iTM-%lQuZm>&sMkFX%Nrm&gAwrR+nRYe4vF?A5HQW| zp>X~hi29p@dgKv7)=GXZHh6}9v0e}QqgGS~hN@?7WqDL17nUxvd?x1LKzBmJuET@{Eh ze(?E(tz-YC%pH83`$;48?>GQ^-VvbW4weQm#gVIK66o*A?QPR@{m>vSPrO~hx_ajU z)qEP+SE=`ZH?dpTKp;dI+uNsNcYaXc{oS$ye`v)iQEsG{jo`nM3B$r;czc=XykP5f zF?a^N0Hu@tz(BJs_d>uZPf1Hn#8~F<^Y4rBmo|SyRcpFS565Z%+7$kAJ}wdIgEg|S zOF6_}Ht*G4;H5_`9Pdzj2Fj4C7ELc6yGRafpAmQ-F={=8DEvhHXJ7?l7IGWLlW2Sh zm(~76bvXdiVUm1&V zU;vv8=r~?b(AFN=J_Bt`ihTsip87n@QhaMzOTH94M!S>+XKJ1q&slOqh&0JKyQ7K^ip(I?X z9Pk)d{u^&L<`1DRLHonMs=5iPG-#J?jOP-5TuN$6pi54#|FCd|5MU11N^_Atxr_dk{gj4np~lp?3O>?r);F zT*!$;$vR9>%n$e=SU-i~ALeB-?xq0f{27P=_mf|($6^128pY1@|0?%YgGXV!9?&^_ z@NcdLF<8&F&Scc-`Ao?JmFwi?W0I3y!7ChEd;3i|!xSUD##44%SGk>_=b>Z^IJKE5 zMrAyBqagH-I$+O;!44)ca^fCUc#fs! z)jq`x62%z(Y?F)&LAncV$5_kr_cbx~XbPnX<6+F0R+l<{PLdP&VP{`k{yrM8{il6d zMkaW5dZRKQ;&Z(w$nd^MztLJk|!pTL9n z2*{Bn0)ar)aOf&@26a-RBt%34WM`2D=m1?J2@u;j@E*Y^uM3<>0x$7sIEkai90;}grbYJ-|xd3dvaI0Z@krIqC0x>rkm zM8)-$Ihd?be9y1heOriN7%if;mHie!SOPxJi$PUB`c1wf!l$bo{JUbrkZyno9Rjq9 zqA06^*!JxB^FV_vpf5*A_t|^gx8$2nzbI+MK*z{g8pNf!&*= zq;8qy+I#lDY%{wJjF&R@YbK&aevt);MS9sv2|{YUM?N))PB;Gh*M)Kwv7F5Fl}$*) zh6o9|#AP5P=$&R3MWAgdlYjnCF0x$>w(s;hav=XFM$D$TxHxt(^Vdj7?L7%du$pKe z!VT8c>Cd(%D3_K1MyiO{gQ_azL81(R&+tWO0{}y&e|D)kAjK3p)Yj+)*61%b1ol73 zjERxd1869tbzzz5^cfPMNp^r%YF=dhf@OBeve}c%fha#U8gNXOHjaWJz0YnFq zogM&@-_JYzufFNp-4N^lsc-7yEN5n7Y};oJuji%E%EFX1S!l0DO%#ye2_Xl_E5M2h zZ0Jqdld$M2xNAh_y_4ID;&v*1tBz_1JI5?Bm zf?M#9F@&KZVRyLUm_`r%bfiJ}h`h6uDI2RgUOydr!=XK#Q$5OXyh9UpZTCEoM? z{7*pOM##v>xJ2GV)L0Ua>16V62u4cK*}8QGQgfd2XZyRvftd@WO^mG^>&MOI22mO` z>m0m&uQN^@-L7yP3NvEX@k4M%&Wu~w7}QlF zUG3;}bQFdO_w}zAvhpq`mzI`>e2@p)o!ayfPEyF+4)o~F3>kVpyBSw4C{%4qmb!1z zhZ7EvZvH;$IFU|c6?*2=Z$XjnKMS}_e>vez8G``L?b>qm(=Ykfrd;fe1%B8A6oVA0 z*LD7ZFZFhxU)bzjNYH`;ve})+X{XmZ@U4YJzg~QkYb?xRTM8y*^oypBu%SZ5xHWk*$qE6aLfFBoW}Qxmhg%recf6SH2&Qro^&$gM z26~JyLrEtiU!IA9*VNw)XOl#`ok29`wG919y5kLIFtgT*Bg4Bfm({rmzs0yX)Db$M1D-%3KJ!9U%U%Fl;mZXjmBXQQ zHpkNu)@hJ$5f|yRUH+((Y(&)(v8*9xWc29RrN)}-=|DmuU>L22=))JgYU}`_U70)Y z5E64@)EkCNF<8uW1d1o@*OA{sGW$)-5YT;Z*ctCPa?p;rI-h5W0VtpHyYaphWXxE( zr_!W4{3qJvfw$}(9F*54udk~Gc^%>|EX@%OOZxH7lM>u4c*FakiR~Q|#lu-$z3xEL zboiE7oafVvX6^bN55;2amm`^DC*(C_SN6~PXRz6r>?bmtGg|6}yr8d8a}#g3SY&8@pqM=;Safmzyv4Lpj z5f{$|*Irjc=R3@(V>s{X-*VkK0q4qf(nx0TpdqGPLc+Id|DZirV0pycmf`&ZqF{%; z6++7AVzx2x<{5aF!rJ=^{Rg8`ouO7>L){}io-O3+SSINTwrK1Z=Zo|EVF-|-89a}3 z@^+T-UOBV69zu0vB^}Fb>j=d)s6IZp6-Z60U&Woc1=*7vGWR8p#%veFI5pN+uszP{ zfO4558O)b?Eg3kD8IDCY1C~b@-WjgE5M0NrWBN!KYhX8hzl(a|M&2H9yUVuKi&Hn5 zg}__Mljf#dOi-iyO{?W{2iNg0wdq4~5O%X?z4viult=^r)MwR`2AV^Rv35k&dAkRb z-=ypWT7MeZ7VYP`8#nb`eYzLdyY3&@E)!kLbl5Mwvm6Cb;W=LTJY5N})SRD@9yxBt zKz4 zBFsH4djkg3yZdw_3%@d3yI!uUo$HuIx4NZv30+;9V%HsW9>Nd3daXH8NoOyL0zcBm z?YX1G){dyTQHFD=9$)sqEBq;vqh)mhJ6p_d*+ih<~}t^a4lr z)_d>tu>ztmXO8)a%_c?dp|@)d_j6eb z89N*z@N7BMjk{pd9*v7-H4p~bQ%o&}{fLJjxNgcUc{5-elEp|Hwsc1h# zXE$rD7%GZ5DPH zS!PlG1u0vW?{RJ&nomVi(Y$0t>mZFi2Kk~6S?`zLD}j=ui^s#aMXTh*=gurIzmqCJ z(uj7+rk>%Gz%9o|Jp^H4e6)Ee`H(-S=^^-?w>aeR}jc55; z>Y^>*B=f{SPT7p~NRvbq8Wp9~iU>^>>8QMx({$5; z3o)viJ1b9ljKp`zMY z`M|xEXDVW1j2h#zGrh;gF>3Z^+8d)qt=jo=? z)y4A^a7ytVl}Muz>rKpS9$u5lKbr45xk7+7F!o@H^m1311@S}f@`)=ijMK6F885Rz zTSrN1@{Frt%F%uA{9Lr;g<+DA8m?{9n=G7n>~?!_tlM*1wUYg@Ymj#w$GwY7*X{Gx zacw_hv^^h4f9EF&%ZPE(FVg`5o3iT6(49qGj?Tu9vf{ZhFj0HhDmPe3)$}G-fR&|9 zR)K`-TP=LGP~@FW=Ve(T>6_^JK-FRuwgCI80tET2~MNv3od!S@jt&;PhVovsyjh_Nsb_mP2pB+^vpU1l!}riY;>nDGzA_t zDb@Vq8Psb8lNdv=e7ZEW=fB5hOR{)vyvaIHKXF$p-mVdY7Z6&|bB_;Q=4m(v0_fW- z?sx4iP{Pc%8qDC<^=QaPHeXzN>EMm| zB*Yv;YLT$UY)jfcPW*$O!_;jw9cvRPyL^ynUBwq@r}eINZpXKp^qw}07yqgIyl zW$nB{FT^y<^DVWs2=jWYyPoRkKAuUUS?jzv=0<}IoylPT@vV)j;Sy4I)kD0SZS9Tv z4J?TKgTimwS!9yCBv1kW5;DKAwgehu+}wi}9NSwI6xXuw(49{o?Y)uf;0`Y368}U1 z)v>6QeP8@T1CQaX)6Hlxui~>E9!eYA$RvL*dGDV52|S@S{Py7>Qw*N~cZKUa$oQJae4W0zk$bI!6Klyrq$!BO>MV7k!os;D9R~9I zmFFkPQ_{%-txoG%Qrt>y_5xu>VWHHGV<&CKT4LeX@kzHoOH^m4vo-qh(xGOR`ZY3F zk4(-&BE)-(*P8x5mWKS(0duwZRc8**CcTLqyX(*tB(UbL>f>z(cw2Eq-I&N@<;nfYc7++KZ7s z*pvHYPlHmm?Od|%B`YkrrJZJgHX>Dhu!y$eiD8`dLr3}P{&QVKb)N{Z=hTh!X$x~J z?dn$dRSD1NEAU<5gjF|E3hOcmQEvNsW>K5RJ+1b>!;)|f4#V@;FbX+PqxkZDU8HkH zRY&O%&3l+QWJ*M9oARaUap?~&-V@ibmzVd0O-YUai~k+3@J@&Qp{Q>zFs+{M?8>7n zeb*_vmHoWyu)3=X$c+RaYUyi6*~bAIqLP zCnk9^#%s8!4H8%^BS*HVuYU+kfl@Vy6Do2vv#PL3QdbH&+NbhtVTdOzza$e~+1Ipe zoShvD5e-kLrQR;hG|-z-a*8o|FjNT*=29PwnvMMg1Uukub56^kouB)p;@URF){HMV zrGn?V6$^^G4Dq42LKrTCYc8>T%9K5$2UI`X;a=!!tiy3>R`zj$1zkIn&NtgBJBJ+W zCdI&06U-&wbzGUtI=TDtn%)-apmg*DwKomD*u_br!@G(asnL;z!ll`H-{5maTjDLg z76YK_Wdz-gm+%Kv6p1Ro9%iuO8!q#k53mg#;VSh50tDy&I68UB8aiAp`WGk|CaTNi zmMkut6+-WRd!V4)&_Aeci@tNMP}Mx3roXCY{gR|Sp2g6CO0t64#R(NBk?3A>D24F# zIb-;t<^&0Rf^VShc{^S?yHd@Zb)L;7=0xY)HTKXj$DQn2ao?E}!L<#bsroHP@wA62 zJDF#~by)Oss0z*qtG3LjILsz~p~yqO-fLYw;;n}f5$9Gv>aDjm{HhP6;uU|s>!8;x z^{(3q+q?es(jDx*eKHR9e6_~+(k}rgtgIrqe*1ve5R8JN(zfP|36dsWp_tzkTlsZ9kE4Or6&cmi33qIaQB5=$z}e9_KciuWbWA?Y6dx2C+Axed zbm(1xAfWg|6fu+Tp>c7h-nL{4Jjf&v7p7kBZET2!Oy!M6@}~F3_GFQ9arhgzo8{Up zLB~l+?TnZv8ez|P;q78sRlvj1Ti098oAq!%ic8~6Q5UtE64ic)!DC{}FUf6Xa!O%8 zG(;k{@8+!~x!v3g@laBII|i%XSX3OQR$s`Jbm*Z22ei$5Og5dqYc#teCK~c-23$OPMM|tXZItE|GIN)2ij2~-wk?5D zb->CoAe6<@#+6X+x&j?u2j{S-72K~ItYNUl z^J=Mf*VS4UItWGK%5Pq`hR+N9^?2KT+&K!Gckj}MzMO+qs4WvhDb?uH^0F*9C;+Q< zJLn{E?}gLDHfTD|J&dbH@B7#nm+!R5&|Z6plI?8Axl_NQJI)mJXhEGlGc10 z=MMA^yT(W?zV%Vkkgf#^X?tWLO~aO6VP=nKW46CnfsWxH#te}ngoWq)zK5%J?Xv)J z@XI(g@GIeNXf_a47`x?@Yg3EKqhh63p!bT^RHyZ#hNtGK`geY5Wa&_s!w?e38G|*M zZ!}n3D`Xcq#SkaiRarGMYA3|3_H)RIRab{fGT%LZ);a#1P=KlMbYW=j7=!-dQ~6~) zk4`UrP^cd4X1i+nbGFgU%3azGe%kPUIxP+wJfi7Gk zT_O7#l?S@>j+Tu}boWAP1^SN)L2=Hm&7E~3q}307IoWdJ7fHzM@i5V%K1 z+ha!++{5we*&ne%U#pC6vb!G3$D>vF^ig58swi(S^;+`e`{N=!U$n4aW-9}q9t>^G zXR~OLQ4}_p=N7KECdY@j-Z%@tH-RAk2%e@gZ>p^2lKQj@W;)n~c66z^%BS;B zE0^raxs-EoNA@H@138)Me8qh?4p`pVHQ|SG;@w)28zQLGf`}Nby(*7+g<=AJf(1TX zsrbhfHn5l-_{8Pc$y}-kx>sE@Pp%S7{X9zp zfe*Kvx#Q1Ulk(arJ@JhGHqUQ~?89ZcwnW4&k4;m1i?|MZsa^Szls~;;SG|f*P`KDG z2barggM?Gr3uXD{Sf<+9&T(yt4CtIg%j$JBcW8Zlb#D&l7{C&7J@)FbW+@U=8|~cw z3IXGskts2qKf@1$+oj$vLxQAnR{AkW6#5)$wA7UR(8<~Q%J!|I>I%<~IR-r~2#C!9B z0L6t4ElZEuySK_qK0nb%IRE}Z1l$%5W_JnJyv`C8-sxMH-Y%OF7M?P+Rb1?dvuvfU z6o!kYcWuW;CD{zyO;iID%ex4uRghnUOGk;zsXJqu(u^=XPkD_p2E-2iUcaBUg@|rA56y)pV65YT|B8b|#&85p>@>E}jt7vPUUpyx4}zb`=B@ z`Mth=^W`4?v?NOFy@^3jf*w5_;>?xKma@41!uNwWo$TcAS|s!kV~nhl(w(*o=--L! zA^z0K71ceV_U7P6Jc+VUcgJ(BQ%GY|N@I?1S>d-PMyZmsES5o@e>;H7a znAEE%RBM<-qR<>8dLM8py*zIGsLdAJ`fzm`*OWgEd&_rGY3=YMor%G@#(Gc44! zQJnfQ&~tA&7;G5$4y9PKjATS3AT>09ESkDyavs1BsJIWV|O2h zwpk*rL#SwTi>Cn<$COywo=Dt?w|bW>n_ZEmv1{GwL>#zrH4*Kjrl3Vsi@X_|cspNN zPC)1m&Z+|AOC>5WN;Uk5`?8Oc5;Z-Qf#C{%PNQiOm_GH&i02!0a~F%ri)Wm(9Tjom zHIH5@^J=7XNjxp_I}ARj-0MJt^rlYZy`K#la_x z=(S|*$LtD}U)N*}1>O{eTLFBDD3e!l;a_DA{cjRfkfBc+jVLG0_kg2In4}WvCwFZ| zow_#cixKUaebpmVc&Ii3XQ!-20KU``EJvr#mG7Q4+xU*GvQ!O^gviSN*eQQZ6a7H} zh7Yiz9;{I%agxq{98>qHQf)NztL+KlQWBe<-MS}Sw8ix;le{`l6}U=$FvDq}RM*_6 z=jQA&=+o8(z6ooBE!S$o`1FlgtbsRdUWJ5Ge5ux4uZe>T7BBd`tklYqVcP7FJwHT* zYadjJUuM9z1ORtK2fRW>t#SL7LFJD5&}%Ejc<0h;=5-xMCu0Z8cYK%4N8tzG=to-S z!qRKcUu5=xAhkcgfLE~x-X-tbOJ_47!;3^y47;3o3d!ir)=*jjwC+?!>PoMNd6wEu zjprV22P=OhB2!0mY|D~(WWGjLS$k%vI5_LB%xBk8>K=DfHWP?>OBn{caDfLbwMl$h zmH7FLI(|&M$!cEE%bQdtv*HH}M69%DiI|>fu?N}7iKG-S(bR@kq4*m%Yo64u2X9*@ zf!q7)8>1s}hn{mU9*qZx#7y*O7SE%SSBq~O_;j7;^(+@L4czT;8aNWTR7l==h!1<2 zr-gGVlw0NO1ZpTgop21JS<+BS-6i@QIRw;pY5^-o7yvmwy_r<$;4vC&6nS(A#V_29 zQbX&PuzV^w#aWpZy?VjNn^_*HXaL~Xsr7SBc-NBPmt*(mQR=BaeeX;RH5z|;Zf%^?L!5kH9HBDqy|#Kz>Hi{6;n4?$>CGf=xxQ=`!q2ckzFQ_p53(v$&9#1N&z;7buf}_Jt*E6rXd)f~^ zXHeh&ycvB*b^Z8w70O1ygD*GJ~sgK7GEuJB91}{9kLS;^)sD>Zo$nM*H5ZW(SEb&d|{8JoZVc>{#7iw$qETPK2f-Yt!vZ=3KQPTtu zr;Q%)Wf2KfBbZ1esK|K@wX>;de*6?9XEuuYY)AIe@A$i8bqIH<reS&~URb!Cx4yWy9WL!fDjyCmi4FQu zfv^Z0uOy>@sow4J8t%E)nV;Ube(`vFC@KXsoPkqKeIlx+^P(wPz2cFss%iY~Er4lX zRhD{X>!sO!RBir@U!6~fl?O>l6%(pnllt-?@@%VC=G`)}6L%hVIsj`P7M-7Netet* zJ1do~Ja8Qb$ePVSE@JCkYN|_4gj=g{4gsO2#CHcZaZZLVT5vQyVg z7pv`>*F|}XH2F8v(7d1I?=+G=fVozvNG0M(i_6Pp^rIqFYE#~xJ*Ar5Xc8+H`FdRN zxFTb_AO39MAo`q5O(0$;&?dt$==Q~9Rbd*Fmo!R!kF>jDGggNzZYA?{$1~wlwZ<#} z(X?bYD9hN&#Du5s?AoPby3`fn9DTPvD@ZXHpR<-h|kagBHq+<)A{ZZDzQ?o)KX_t08gj$wB%u%ioaTiuqHn`hk zf>d_-dbU)2_2RQwska6gVC(4>k@2lCJ;^gRP?iejy`j3+SpD==&UQ!gtIv*k)hk@- zVOS{*t64?iyIEb6s8+reZAXWK?@;z9WUl+mWQ@n@ap(>;Q_e=22ZzsPOmQZ0rT7J9 zzn-Jj+StUPplz*ug|2+05e+yDJ1Q-;TEBxwyKV~)>5*Eh*21V9 z_v`ISl^o>#nAJ{&Fwfp{=NX5SyM4FXT!}3N3~Ahv^a)c9l@K{bf6FWK-qP;TyS=?G zkOvpiArto>n)SNSRL#ID!Mn(I`P`=%SEc20OOOA$1-b2uD zCi6JDt=+4P>W=_7cD6fAr>l=ncASD*dzxnFpGWJl4-+ri{P5Xnv5h92+~g_D7MUu4 zr0uUANX_~PDf9Juig{}!`IGg#G7yOPAxygAu5#V0$aOPSv<8%8fG}Y{Vgc6gy<`Ko z30-B$>s}+#ERdFs37y-!l=unuZV>j#`fWW3(KuQkJmR~nD0174HA-4kU?@y_Ysp%n zH!Ok2J`o^@x6Ke3gv20?3U5-ntJJ&%%@Ul-ebjJU*jyIyBoP$k+duhlphM0hc z@v=kf_At7mDB65}_d&{}+E+-mqZ}vSL`&1_7oH=M{dPYWA z4O5I-Bh$ziz=>6GZJ;cv#6;lf5d@6{4ei+Zlv--O1T|S^OARE1h3}iy7uuXWdoPRi zUV61dt!OZgeLI+UkIQ~5g0=$>8UB1Avag;_DM6Rf3cR(YHMUR8pX)UZ@q9QE2VsTi-^r9=Ej;?khcm@0OYQ^fHo7%@2 z=WL1Kq>{<1%0b)L4B@%S^fB08p7!^$7(RC2N158y`@6k!bIQdtc^5H zUtt?ry6rVlC8J?}aHN{~bkZgCls{c5uSl~VcHB_paXcgn27!*zU3z`K9FolM>M))! zwY$6P;Y98MbJ(1;$Mf|)e;TA$f>Nh^KArukHmtHk{A)^`K}^wdm3Di%8D;Oox%`Fe z6Wfi$P)18MTMs~1FL;#)J&cLALGEdCS*)Au>j=AucVX$E=H<-u_ig+|4GBLfD1OgW zvof*|$6S>qUl-^r)bbjO|CoN%y!Gv5tjPVO@$RN*clXzH3BER;*$p`QyjQ#55Jbo+ zaL|Tnxk!lX!ihO_0o$Z>zuDHh*;1Y;(z@#uyk)jM&0KVT_zAOGh6a2@&dK8Ux>Ozb zZmfKGLK1fs8#hxD>^n!21y@#5QUcr#b1Ww09Q6qwI>s?vOo9KQbuC6dd5OnCMe357#N zJ#pOL$IAMKYYDT7t&_|CcusYV&!bTsN$Y-M<>h{sx5|YY7c1d1WaQ+60&Y)KRq=`^ z?cCxHi*lGb?L{4&U;>w)OfM???iVm~(o@8Tm9ad}H`B#6YTIS_IZ1hv6Dk}Ud+|uP z9si*Au{oja7FR}qY8LyR+kx5=IWx?>^|ziX*jb*Bq%-7*1>HY`oqBtFE35#8x?P`J zM~aWB-vDCl+6c0%KB{I_&MOuP5>FX221q6>+#VYnk^4%tc1e83E=Q<%vs}tdWqQhR zo%&Q&nHS^CJm3|}TEoh-&S{PX);{~g?32xvTXU_A-kfi3dog&0`Q~SwY^+8_S1WcF zQ0QEVUT3CRbPc;!_08!F{KR_I8fx5ciuU$)aO8p{x$qzXn_W@{=`}ypMk$VilU;y; zL4cus7U>_{6t$?b6Oc78M8tWa^&uA#taI$GnobpX&%>g8H)z!GZIW_9}-}$>xgJ zbQ5CJ-+cD{Ha?4)&1OZ&>6;!e7N}Pl;$WI!?#+u~Sr6vrgm%m(TPTX}N^h4*@mz*k z@De&^iaf_QQX{Ow#mS{0TdsIsI~>HTCH>1GF;&XRdA$Qk0JgU{?`4SR+!`6Vi*7LX zV%}Y%v3XFU`qu{3V{c)m%8HQze$6+yxz0^FKCyUfK0xd)(XhC*XnR!eL;V&PGlPD5 zLa*5BK+|a10~P)3oH9?8+`ENLPoaE7g@eO{mYAOH8UEt{s+T#@P9Sw0MB8SWOZtNp zF|R{@VPWA}O1nR@PXKvZT5SmVo-;cmxCUQ`<}zv0+S)|>Qt7tx(1&s?uGZD@QRL)4 zc~X}5%Qc>YLzFO$+%Nn54{2$7Wfl>ahGdWD<`=$P_PrUHJ*`}+-b$XXP%FRozD zPbX+LCum^?n-}ZJS{0^|uRegKtvp+r;U^cd12sPaTZGCx4&^%KYRMA&A$qB)t-+Cg z;BQ!wxf59qi6oiiHi6&uIE6zwXoQlr`RAWWJg7$#hBL{SQ3aYYKTOI9-Ww%>Pt{x% ztb$YQr>B%2w3~YQMe_CiI`y#}7Lia^xpXDkMBn{?#{v)%yj@wL`MUNjxOIczHb0-9 z5o$L%F)TrsVxTJwz8DQ?3a+3wjVh{Hj(UGPlYPm-PoBajkFQM>y~szau4c)$mZ`cu ziLOv-M#WP~RaPr4Vx4|8OAor#q|juo;c+o3jRF(xp#JLQ=SxMVuwCaOLopFJ$}Liz z^`%NRvmBFP@tVeCMp-iWYqW)J0e?8mP4IE}n<5fx=~WhT^OL}0#e^OMHs1T;J}Jdr zcop0blSCO@hOKMFaX@@3#=EOar#k%P}wo9RQf=beKg=#PZH?3`+k~3SHG1N}AXYU@_ zFvWhi_ERj=Nxp`-3-+~q660J41=#A4I>Z}O%GJk67t@4M*Hfa4{&JY`vn-@_391Q7D%Tj}3FGc{|2G}qv2`Zi%3 zsq8tFl20iqOf%Lm675BbR>$%V@wjJq(}?XG^$$pBX&oZ9iQT8WGn^}#l@>D`*_WVK zh^DliunJvV{xND#a562<;(BDzjxI01S+eHOx|X>u+-Cqwe(ls9_@{!VYEx(f^Y7c6 z{**p7Fvz*J(b@g(RWwIXdtO;9^%_&p9Gtz#l1X;oZ)9}opW9%)>uoER!}|M7V8-4@ zC1Wd?=C$EyscL+9lBt!I$H|!T_V%`4S8wk&I1DvkZM}T8->9W<-ucshVG+vLQP|4l z(h~l{mDFA8=SlXGzWW+2w{mAWkR#!jFf{%kCf-V8=I*MWFpA>!Yfyb`f2K;tPtW7e zR}%BdVx9f@dQkSp&(BXwv+8{MmOm;A#=OfK7%Zi}v^keas_Ts|DkbnZS8bFF#~im$`XSx0si zllN(3Sb3x9Vy2JHbIuh$kL)vdAE+)Q7YV6q&Qh1sJcyV!TT z=706l8#&o5>EY4`>Sb%PK!gD&i)wU29C=F79eB=FOI9c5wWL#1QRpEb9aw#gz@Qn)e$S zbfOc@7%`7Ag^dviO8rXpRVQg~C}ny_bZ>O8)vLAL#N_%x52|;NKgfU@(s~PpgI4di!4b6QsyzUu1 z$U#b1l;{nb{_%+nc@r)%l_##g_wgy|MYO<_nE;5HoPkex#)E>4OlVP7&rr8C_#Ii0 za}F{-Dbb5m_NeIR?U_ceB8)mRu%2PGXKsf^BEUC>kLdtgMAW24mJmBFzbjorI*?$) z*?Y&WeL7ndlRQWiHBl;DQTX+1NA@{r8Ea!6xPi!~6S0f6uiq+xxcR%pYx}2Mman#d z0-`b`;XRmS(!b;3q+w+h{NkI<#W@l4jHSsPt1h;do;8K5b<<16Keey^CB6$k1_u6- zT-g(0g&*P3F|jE!q-1)QB1ssk{{oz^b7$`c``@87pD#d#^;u%s2>O}qP2PnWOf5FF z7Q71srkm;M>HBMYzupDpSYuuBrnF_QXUQ7Qb`=Wlq#O{5vDZn-;|sik$lrILY*rc^ z{Az9)@yc(Rt0TrnLfCDA#(e*&Kb61m3eW^TT5`m@ef|tX=jGwy<3~B(e1Incwl|PC zdfdQ?EResWMN#HyN@5JAjKxf))U$7e;N5DtxVU6n8ylap^EG5M+EO9Qk$}JT#LTcu zad1GB4SCh7x%9s2X&@E}&x5H6uY(@Qf@@0jveqJ>&welc7qsU2R?1|FUn;*;+6ns$ zjZEk-BM@r@@m$2PUCJ4%`Qh&?E+OIb$;R-$kb1@ISti9>duQ-llXVsD0^QxQdoZcvJNV@viU}?^N-)K_}V{MgM>k6Mi`=mfF>KeLr<6@ErO;7~EVgMn*<6@X;HiGjlubX;zd~ zujDTR+Yz~*uzZU!3j>z5OlpJRlTeT~0j%Z9(o(8Y9(V9ES1@zt?DJ3ymvNJ%s2?#8 z@RSCGrjpWpQCLp#emYJU_(kC08vP$wf)>*TCyiRwMon`!sQ~cI?3W4}q6~a^Rr_4XuTttkG{%brQs=UcX#Vp}VJy=g{t2fRvZ=C!6`@tAsA* z(b0BclYmp|3bgOCso+CKeGM)tlb=NnDS|B{>Mu4r|g-#nS_l zqvt4)9AiUX;i4N`y%5p~Wwuo>EqtR2+3GGA)3ZchQiH$OGY3gkb))~*@o<9wcXep~ zSO4&f*!rC|R?-JnYnHu|y%Sl~w0K5Y`rkHKk1!)p3Q&p9T7D#kiK`|?mZ&g;76Scg zSBWVH*w8))EyZh9ock;sHgA|$e2itIaZP%H4E#% zn~?X=>C0mXL`xcfcL^w|AduHYV382txY~J?ZMS-=PeWtsFY@UiT#l1|LezpcLWcO$y@V(GwO6a@X_h`#@qo(Lx)d#CjG&&<#6RyfyW`0fOYF1t@qIh z88RWesxJ@%&y%|o_&Xum&o>5o;v_SXlD=BFX6F7|mSI#RjBlW%GZL-TNq7AVX!8DZ zkmdhM?M{_{iI$T;BE0(L`h`$^0w#iN1I-)ZO^e`e3j^9_a$r)%5fT6IM+ zXlyMqyLWH35Fu@lWXujqNNt9;1xZb0z2`~#i?Z^#YdtMu)RWlJs*)YEqvzGtaO&3Y zlJ*M;2mqf!Du2oCxR-fzutGk1xGK*;A6+T1#U!KzCq_kr5B+1A-0{kA>mmdqj|OlA z^?^@-MX>*w8fJ<2PJR2dJ#%GAKErEq^>P{K*^esvf&LF`=(lL2&n!kY05S}7BrUA1uj&?q2#P^}+@kyZ{lklVE zeiCh@B`Q9CIz-SbVeweJH>l$f*L5HnF*HwVG~_@XeG{S9+NV5e3Q!x}bg9HH;q7Y` z_}2sD6pKkyD!SBfqQ$L$H{j%(|H;^`cn_Y*Hpkb@u#6m@m?$J*G}2NsGP4RLzY*@3 z4dl~PR;r}3wG#8zlo1y0s8jH{pAUpFa^d9I4o>Sm^SJ9Dghd%2&P$R*FqK6`eT4k-@@T=dB9;Rp z$@L#4#p^BA-!+0@qD8qOlqWp4nK^@6adQ%T7ZQj!YUzJ8qe%k;c{#a`Tm+2DH_?Zz z+6hZ?qa(?~{>*@W9h zXiU<7yVCUW{j9ywuY={c>S1Iq1sp+=GJ-osr()I>zS}F;fYf=XUxjPU0|DPC>yPzZt|q5Zmc4v^X^u!d*s{Ly6h-PCN&7t4KR`B`3zIv2tuI4;1PrBCl>Pr;=yw zR~DM+fBQ%MJ0nlIo;>|hVRW)o0Ck%r6cU35#3dK(q~l3e z?yFn#o1=K2FvB2wxLvs;Jf`5lZb$5qZ1<21^`1KOTnLfJ%xe+*Ew%Rs#dFkd+or`U zT_ruge}5$NSE=GSKNsX(o)1fK3`QwZB^g?loH3;k6)aF8X8#@V&dS`V#9{7~e+t{y z0Lh!UwDec}zce}@o~I-|A7+0XU8e{Pf+ddO(M?s7|FdOeyOiH4TlF!8g{FGgsh}}j zV*XM!>>xCO)8<8b&*!Yz$=h!aQ4|%9U5Ot*K~Eifx>k3fZS>YajBDsOBMO9R0MP9h z^)h~=1&7)&IHxLF(bzo+oIK{&;Rad@`CQph?K80cWAw6lA$JX~PqNfsbgL2yp`frj zoc#{?fa2E@u6a4u%XV&js`foBiGH(6?R`7#)3a!bhmaU!n2;)*u561*1|$A0?tB>Q zu=Gte<0hXe8mfo4O-5$YyS2PuHwpbzyy4W7_+jz;z%B3NVQ-$-@5o!o#UpAeGFBtY zD?{Sysktvr+KcuiA2aq*Q>4a-$|)&t+{q57C5ELX4;Q@<=i!NHV}vwu8AeC;_w;O? zeT#^Qz}QBUq$!cvk|*~g102`apLNL*`x($WM#%htNqgZt^7m*%CS^k*aT#2jTG2b&iI!)ta* zW$F-oi1s}_U}RC+iKzUHfE^e7qB$w zP=5b+?-qoRu)J^KHS`q@Ys^g~4l$+E@p(XlcV)O5wa@#cQn+cTWCdI2cuL0O!u+X= z9wKQ>OiYYU0Th#O*nv978@j3G@2?_^M-c2JxT5Ier|QYIY^Nf2CU*UO`ph3piEgOk zH4e}~OSVgNKs-NveSP8f>P{Fg&$amq!gV-dd=vxghAmyEUKraWr`aASmcv_VOz|P!!_x13mB? z1oBBdEWKn8BntSe6yl-1GDPY??nLUClt9RBKRJE%kUAdKe#DX2hxY(-*=}wZ`3!J4 zy!Ue{`66lft$=BPOY$GFzI%ZvF|>b0kAL7cMdW`O1Cc3ufxXZ3py>h_GF1OJ5%6`) z&DELS9&J;Mkdi|S>X7M(`{nW%Df{{I4BlXh)Zf?#euN_Vh;D~JCxq(V*dbPnvhLTf zAIaBL21m*bpYP5b6%>`99`5{06vWR|urR+&Do!At@{YHF?=)?`<_%p>>v!*kLFAec z7b_p%=W1SLA?p?6p}`k(7cFJAwr97yt_w0Xb{DVHIuUQRNfNZPEoei49p}Vffzs2K zh77h{SnK_1Z75CDrh`wdshs=uX2~+SV*$edG=9~N4>V9; zDxkyTn8@oEV8#L=1}^dBHBBLG6PUvmRdc8FuKPhiug&Daeo2*xg-*g97jrz1 zvNN&QKzXr}Gbp;kQnHydJzLH-W8DfcfX%|v^mFAGyKYyO5j|7~+#UZk1dgbo;PG<9 z*F!s%$73;dS6FFAJ1+!LU=Wp7L( zCBOweCg$!^J_*h57Smk-* zM?U+FyyJ67G4G!BNI$e*xbBc( zXtNu)PsLBSW6zpbiFNHD+scUi*edJPN=a6xcP zL1DF$&Be|XYnJp(f$S2=vtk3YCG)x2lC76bSVM`C&|>Xo$Y9N<#g;C+hu*2P#@D<1 zcYmgnZ--~iw~F@Qs^o)?qDMP<(!ix%U%%IenG_LFqq5z!<_xJa|efyRV#3Wd8#6w570Q7oaPG_z~Dds5w)wdIG znWNqEc39c6Ag{)HPx7p=e*Yg)bT>-%>JWfP^Vz<HTjXV)K)0zCUsSY6WY&)DWC4Gk@RT-w?aN4GWT?E!Gwa%r5A zpKm_-S5sq>1xe4#D=TR*=zxoZo|ZPA+cvYR>g@LBYP~;xn|ox0BVz8hDdg1SSl}fs zDpNj8XbO~njO#a)fnV!dA{-nXnzarE>FN3rFa)hK-BMxX(Dg!7zz-=YDborbE)~~< z;Z;Ri<=oNF$~Q0=Y|0kb{m*=(w?j?j;#T}VYtzaIagTVIf~qRF#dLXwM0D<0upkpo zK)W`Rf|8PW7#ocLK3EJ09wQ$gpW#44j$Dp83LfpID(C_U2}!ic3t-h}C}gPgQw{R* zkqERO1^xgU8X6GN zYuFdVY%$G721+~9(9(hksgtv_qP#rJ`#Ph%2s0KFgE1Qc32;HDv_p}0%S}c;ySYI% z<}zI``jhx)UJ~sZTRnX=bxcZ|Ep-#q-Nxqhsk_r+M9|t>paB#4-by?h`J&rw(aBCZ1FHecV zD6pI5fN$cXR-?o2^i#p>;svdAdmd;AOJC%JPc0v)B!t200tF8zAfutdeRDV?l`5`( z?R@~w0iTR8J3Bk_g*ajy+F{ms$9Zx&VCg=8{yhC4*US`J?-D83U~?h2Dy^W{=Is`T2Q}e-N+W1w!Ks zGxe$6yz*K-xf8DzR8f#l7PtZa z;1H7{q}L(t@1{UFOice8@P*JS7xu-mOFa7qfULZ#iX6e5J#)H2)1940m&cn#MJxd; ztJ@?3uIPG1%yPF0*Vk-%bX-)%4#LJ`Cq1tF^=F_qCvf<^w%_}J3gIj&`p7{_`g>=p z3>BB*`0y}}-7KoAs!BYA$97H9&Fv=pC8u9}(}n7DpCc_pUTskyl{JDscz z1yf07R<+P>h+GA1g+KPu^Z>b@{7yA1o2vg9{NH}=D=EB8g@3fUBF|{^*a#O0+zmKwQu{bTS!OqfMiQU>^=#>Qysg+ z8Sq_De1jEVC=*bg12@hw)i8=Fzp!u2H2qY-=#(W z5zP=#O9J*@H#%Mlfq35tI!oYq+Vg^1AEz$}{o?+2EP(q=Md47_|Bxh}^?~(oN|$pl zIQCx*Ykj2x!E+lxxopLf*BtNFm{_VGFl@#lVsKDXM+9JGCFoGmv536yL|4Z}$chjc z$Oa&c^&A8*JTD{YwZQ2cnL=ZFO~W%==JIdNpzz4m1Rjt9f&4ZE&gnzA(fC_7uo9;X z#;_tn4p7FkD59RPz!a5k40@q0H)PCaX=w?_fv6Kt-qIqP z#X+Ti(!oA^czAd|?ty1B-0zN136YZ0fRR@mfZSjU_4_CTpDXmL$8eJraMfeR)C!@} zXLiW$FmfIKazalhn9s~X`>$Sq<2=^*{=SDN4NpQ^-T5#uFoZT*d;Z8F^tXT3?@8YK zxBj;B1EDZir2vIlX9pp}H(|+w6i(mDl)UvONFbCuYdvc#E632TGhj%lzXMeJ{ zT!-$GYbcOJ-gLcq9_EXO8Wm4^pACna*@^dc5(=zD!Mdy+byq<2tGb!P(>(zRo4B9 z*48q^e#i08>#Pg0E8&BVZptDe_juv*L^?5rA4SN6caJ|o;r=7ec_+Q4Hb3CJCL10d zJ?Xwe(Gw?pY2* zQ5A!k;Oexhva-|^KI6}xV(ER^xG#-}fVjU>HdW;0u*b~>g|u;8RH_5y58(r@5aUokvqI?pC9WDfk*_0@vU#~$A~ zi(KcSZqE6>fr*+8=N;Ada1Y$qEG)fly)VuGC`In>?0jBNbbLOJ?SzH3a<}iHChKx^ zeMNG<_#uzIMRZWD;oxNT=JY<}7!E^Tp<2v@qw55$bnHa#P{E?zd;t_bhv1?4m%Pcp zFJW+ZqIFjRGG2l}hez8_ut@fSRmq~(8UM0=ipLh~uV`Gs9KZ@4X zAFQeOMit>P>z;3WTnY{4$I_f-x-YM-)m>ZDoPp=wq@lmy>k8B*t$ZkH;Dd(WQlURK z_GfXRU1D#0YtO~G6?LR2G1E|4Jc_RGD*MZ$4lsGU&Pw!Ax2U+=4?q3+{gxtxIQy-b zX@eT6Ux}BsoGnkHbQUerf^M1e_;|hohhXkjqFF*!cCKOQzBCyBH|jdGU5M|)9P>li z;^kE1#h6R9#NJgWis00liAFMQTMgV4?AzA-73Djt-WMmbNGUl3$N7T*BMOED>TQUd@Y3)Ty-cL- zrGRVosn#36Qa~7P-W)0^bOShpQ9L4mBdKhP?|j)fx_9+(>hwZu(GHAy9f-m^uoMB& z>Pc&cFE`(dbk;By*_JKDjnJNxO8wFC;k-Nek+VP0(=)scw!CK62=(3F#h`~~vGy0= z4Lev=6izZl@J$uDoI{$Bu)~W-YBdl!Wk<)V!KA)E$rKv69`~)Ntn3J6cAm%Wz1S%m zd@U6>3PLl;coG>H8D%cDQaX<&NvF(#W_W#to*vG_TmXQ{^1LrP30HSFKgQiyM%W7)4{d3LT-EW&5Ul0nDe2ExiWCZc(K<28u z+{$~uV&!o)b2KQ(NERY^d#_(Bp#oy&kyF$6qS{)%7GEi(%k&IozeX*d_Fwc>;U#!xoxYL&~)7M zjpL29=OS9}3{lQ45|7I@r>#44%B=t`zk%U%+oaD~D%avdu9cWhHRMHAe^06#8XAg< zcJ(H}B{^#8iDV~wRXR;tzV;PeKFGb{yg4u=aFGcirXKPOPY$8;dUkvYRp}?JTIu%b zHCnBs$DRsyBg2SJ3%7o;0-^UO11C}~Tn z%$t^r%+h?>e7CYFtIuxEjr=dgD=1{fl=@C*P3JXAbcOV;VQ`BKdkR2( z1u>`ti=V!I_H5Q>ij}n9!yc7iC^lPNqJ8|HAmfwj3GBUd&mMHUM`;2VU{+jN8RsT8 zwT4pt9v}hgCHT~c(K*tX0az9pi2shopw3qd;!;I0LOY(&_J9N2V&J`zdE#5jR!+NV z-vichERD^vFsKt;-R27?y7b_4_=*5``HQdEd}?mo^fdT1DUM%yLz9B)-b;uJ3#WhD zOQ;JWi1CGkE$O0|!Ar8?x}4OyeDl>z!~MkXy}6Osm6;9(`=uXG2szmY8%l(Q#oddk ztZ-uyzBB9}J*hIw-s>C^Rzm>2VDF_A4GdP{pKS-{>7B_bKG%cNmawI%{&=mI;Xt1< zq5OT~bY0YsNo(4uLHZ%Ir$v3)dqNsQ{d(Jg9jtBMAD%SeWaSn|`|)6pyQ1kOzH5)*nA^Vac#( zcG#2ISfo~NMvN)|pNo$^h2`aTe?&JoHkji3zX}FB)MzjyTO{9MgJjQ}cA7A|?n1&X z+iwBH>(3__nyFP4%j9z9wx`Ql`^Z~NGBc_km2{9I4c{kma5^G+IcpYsvgS z6!8P$xPq{GEBv*LGN=D_=mqH@Y^kIg$n+Y0k2~IYh4z@vS5wFcXb8}Dy5$*YNsV0w zi7M`5z+cpciy0U$>#jN9BYGDNP%CY>uJ-a!1p}YA{_{bQ^)|wn{t7v_v^)DJ>7B>j zi2rc>`odk)QyH}a3;~z%!=NI@{VGa@oJxJg{vPgn&Nxox{K;Qj+Pzv@B?msp^H1P! z@q44lf$vs*l-8M)eLX(*9nNj53K@uw54@sGQKN5M3~tx_OAlFA7n+>&#kzxRAJ#pE zKll8*0>_8{$xp4_D>Ijoh9|;+se&Ex$pd>@JSn#zhTb_K*Az&wNi-N>8P)6i!i{km zt}1GE7E8Cb3@Cctcio+|;={6LQI3fa0Z6HDXyWS#1ZHcTy4|32P(~{!oy&@mr4zOw zUIEi2`GW`v~HGCu>kFwlUI^Y9|($PlFXXnPV#i{w?N~d{0aRw5@ z&xW>p6qY-PN!SOegNuD(+w5n2s~t~@cEhd#WCXLbuhXl&V+PA?oIDWX1ofn>9Ca^v z6Q(x%1T0r?`#NdQXt~56@SB*x=94|VV(w4^5jMjya zO-q*Q`JAatXI$LTVW;1y)w#~p0elu|Go0lgn2Exv#g`z*JdCFHC|HZ{>S!cu=1%9L z6)*`!ucQhf#k4EhuHjft4vo&v9)B*z{&^kPJ4pB^cr8#U4fM8O#d(_yn2^M)kBAQ{!UdS!@Q^~T%y(>axfT($g zS|gJL?FdYXdS``6_FpiiFv(UrOTK7g+m2_|<3Q~X_3u0XY}`++mc-_3E7o#fzfog2H{}#>h1(Y{A)IDu7VuQuz!9!CL2SzfT987V~g=fSWdw zmoIYu%PH+(wT(mLSNQARXln#g?y~QlXFE%`%TwsE7ivK@m6YJ0k{ne57dN3aUp7mX z#rc}Ov?*HUlNVH0i@MGY`T1}b`C9e7t+Yk^3N>1>sBcCgg^0UopA_EeGe`~{0cSo{ zURWdom&TF;S+iRVWavya*Ohq+tR`3O%_hwDyv&w^6Z@Snmc4cu6L={H6Y*$s?3rr- zhY(XjhK5i|kGj_Y>h@w57T!u`$ioc{Q)E{hdu}?F6_%BY!%;-LFQs2ki5KvB^jZ5z zNbg`cH+Dq@c0F%g5f#?r3KZUxFMz{J+;sE@YIo)L_{s7VxC!AEsumU2L4werg8LxuT0S;vh0OUaa*g?on%0 zPL_?zcjtn&N#0Jj<=`aVw&3K239#**i{bFUm|ro3zx^jop8d>1e$B1U0&4fA@szSW z_PID88H;5%EVtpkpJM^7ZPDs6oYVjz3JW_}Opn%4Y0Y+<&hO+-e|S*_=#gg#~;|+|Qz=rzgIa<>BE0&YIsLX;46cMl*4#hEZSam2{WcOIVLp%?hgEP(;Lz{Lk_47v7ukY@V zEroNBVdxlB&aWt8KFDelb91|8l&q}s|0W@#|Hz_Om%bo&CQt_5V=k|N4SLgn%IY zW?i(pknYDPg}=uY{y#eK-{pq?yka8qfhQiJ;foXpT!`?dZYOs|E#wK_MrtWi5mm3f z!56N-iqU_O&f!q>y!j_*AYLTDe)g0xTh1IVDN}8~L&=d>#cE9dc$$OO^S4u1bhg?wvY3iX>hBKbBI z>uj>?5~KS}1Bwt11fyLFKGI8}7t!;chKf-|_vV1%K```M2qA5WbC%@s-@PYyb39V> zITM2$cgXMEDo32`M7&XsScR3oN%%AO1Y+%&Qzo>_0KuqsVy+j-Z`S@;iwg1d4TX;* zk=J&5u^6tg(xKPRP1{KBYQ74V&g$*zk^>5mr!O;@p{0REyYeJ19$kggA1STFyL7_F zzH{@o2fCsJP-UeoPCPUXWgG~bKj`Z7=0&>%Za2AIO-PD?K6-Z-5m2~+_Wear2q7T; zAE{50>7gfH?WDCd=ony&T<|H#0BSIpf;VJ913s(JuL1kP3ET(YZ^mf<-azne&1}eo z*Vig)sIR|q^04A6Y$ril6u68!Ys%2GT3rR11OL$>s{5kryJ76M!~6mwMGAI?3d_cm zw6e~P<38s{0h(4_Jp^H~p;PW#>FdfOx_HzrTuw-Oz8mRHlX`rRxSxww_ze3TfY_Yd#0I5w@rc2F0?fj8a)zK`&sWipwb&p(_`nh5(LJx%u`De7Gi^+!->m=C z!P^>!x77__LfsE(l-@Z5zZF|wddrdrel-&Fvq^=?nZO&-??Biw-rToCeg=r))iV!M z#9aN~Lnh!n9Z%*HSQvKQZX%)8x>o7mdsRQnOG`q3(k3B2#(x)3rKYeG!qKmxWTw=GM|yl{06nT8IBl~mofxsJB4Q(Kl0 z@sM&Za66EA82{oM$=(^t46YPmA5msh74q@h4Vq;@^cO8gL(ARTQpu@%oLpyH zORRvC!MiDkBSFbQx4ib7KdMN&s0GDSH-{?AzFs?vd%dT26`V=CeN5|E`cR(5oM-}3 zEd8f7PO#lTXdgLhhyF>$v4EsEw^3k1V+{MHO`6zUnAJjB+iCx1C96Yd{!UA%kgzw_ z3L0*WUFoi!&wXAl&vX%BHHc$(m}cwExhc4DbpQOjiJx;l5ZeSJw2>A{I>@gyHCuEY z1NwW`IeJsNw5x>gx-IiU`B1a5<81EIFDyNyylzvTZv1O8|u4A*Tgi^JvKD;YS+kifpL#e$7^r$aGMx~PfEXHy6p?_sm#m<72 z+wuHW*8Zm8Rn^6=mO>ppS&#k~_;Oto6!?haO7^cNDu-&Y0DETfj$pbTgKwj2wk;Pl*I5U`N%u7kubG)38KTsKbgxlZR-(hRC zLq>+LD#QXCNG_IuPuan>U!^U-E%B zpr~|J;wI0W6G!bY^P^XNFQ1~5I+yHVLX6csmtwjWg1YUkh$pvh#~Lqo1xcPs45&~b z2^=TMQJGCOF7RAT$GQid5tpmc)in4>5zFtZ%2zl@jZL%5tX$W$(CVED|I~_z*ig;v z!=vrLLRZU@Or#(KfeQTR>u34m*|xTHS*QDEkxF@EEO(W$nuBbg$}93u3fLn(e(Wn3 zK;M{0YYijk>wYI9t3e`EGb@SI(Xk z$;|!WJNJVJhT#i0iNw&PCnKwCDP&~2EJm>m4)!oko`}AY@gW1kDE84a2y{JiM|md^ zcQ+F)?bsv2_hyz8nyI6DD?{ATjP!~=7toqX)cn2tDtSuv8}%f<&4F4voY>F%)9h!b z^E$-gEg#IZ-A3e%7z)%}ZTHPhi>g=E=#eG?cm|VQUhf7| zb~4dIKL&8bvIJJXaP6{u@AwJQ{RMz6Tb{w4`}Q?+&5it2tbCUL$p|E4npuxdJlC*7 zt>A8x*O_a#RJAH(dkcqei%>|`)H6y0qj59;N`U|TMm=+Z$FL_STwPcVov7MqD_^GF z=FgjbtSL7}fl3)w$_vWF-sF8$yj`qAYv`PpXqMEV8*SCGpWHUdi>vk*7@+{FV1q34 zWd<|!Au@>+HoFn>_JjMF7$#3%gl|qrb07OOe6P-A)Z(>|+L<9=B2+I`rQhNkVXh-J zGZyi&P&oZGGlfYB6BY(=(${kXU2iB^k0}!_^#;4e-q&~1Wv4Q!%);p1dd9ptzqm6; zTUsi>kd-E&Ui~T@uc~|z=Mpl3HV8m^C8-)Y#=Foe4Gu~ZwEMhW#vyeRPD!!W}^Heoc3V*r5`7nLVrq`<*MPQDSjD>@9 z@aBA+!))JLj&a(JvFz;Iu3G^aS-t&E9j|55mdQ`amkl1dl%CCcVUC^s@>jPEq-0=9 zxh|r3ZIIh=)KdD+?XBX=B>ACN7P$40r94(Vi&4Wg_nxqBT-ddF);8A!OvgvfNa z!7)JAGXWY}SIGsW_ko##4Thm48WX*)Gk2MdKc0B;J@XWVZ*>EbMvWmCfP`A|TMcfx zcNB8cW4l#+B@R;iP;p{7ReBJVoet&74GInGmgri+iQPWLwRYk8FE_OXwIhj~oglgW z#~ozi%?_obyUJb{-R{)%yU@?@1?_Y#sx+NDos}pc3-cN}b&pMgRF00(J1N9sn-^Tk zCys^pRW?+dd>rEse|mkeYB-pLq?bzfWXy^^hd=oR%(1z=E*$4>@*SaMTB$7`Ze(QP zX$N||@2+I$AFmy+-uG?pxB5Q)v#uU8ofiSzX+HK9Ra5+~)6xW}k8QkNExm13r;~y! zF~df#vgfe_+o9*g;qpu(s?A)vUy;Qg!+>M-SHZi#rooh%jQ({0NH z(~<9|xC4%iGA5MnnAvNmvsdOdj!FVNhIxjCsy3w+AEv}I@l2oU5b?>NVI*P^y524~ z|E@D;!UL`Vpy1!0QNw5m1@355X-nLVhD{K<#xyRHvHHJV4!}MLgtTV@d1Elgea#A2 zBu}{e2l8f;4oAY$axaPhhQUWeLnB|w!?XN74WZ+_Z3t32U)D&<1mXWv*jI-|*>rI) zDk$AbN{DoWgrv9#sI;(jhbSQ3Wx)~x3nC?RBPEC+ z?qpj1?CYBywA%a$z*wyDo9g!SzzK@_ac(6iFONsyF><25@2aWxjFag9=wGCv^1KGd z7*BiO^|4=5R?~Iqeednp%vcy&Y4{~4|PN#6-K7wh$Z#^*8 z_Wi4lKIRdNAp`1Y81z0?>n~X-n?#reo*If8(jYMOG1V@UOTpdXZl(5>MOZ=911aMd z{uz|{(bdGi++&?K4poAt&+upyDFhm6tw29*C^&kwyX-+nM(?42`En zXnO?CSLbon$%PU9HP)!NvUFJ}Ejl*}WE2Rqozmo=-lDDRjoq4NY{Wn@F|N$zYPnMY zNNu4s7>92KCA%A31NRtpoM~(E-}i|! znzAK+d*7+=c{r3#cC%iKhm#`D?6;WDP4NoFdq48{ne8mZl1$k{07rE2H14glNjax< zMf+*W@6RX~jX6tUz^%mP`fxI$=GnNnDAYR^a;N;tO7DdZ6tKPcw@zl|SSY9&Z+hh~ zPvh6GQXeeM^@YW$c5h78BH+cfn>Q=v5_-f{TYz!S5_?ZU!BOt(`cwvyTAu8nm`y&1 zZ3ka6JE4NN)oXn*!u~|G)NNLHIez+w!SbMmd*cX7=c3t-z3}yZ{i$b@( z%)9!@+3(~EwLNhC2TE^EZ~c6*OPI@feSKQ``W^Qr`BcgF+N@0c^5uZgW(Kk%%d|_i zJ{vSBfKvE0MSUR~KL*12k9_TPh)lNavL~G5XCBZ0Zl+YCLovI4xwR3? zPDuVIFb#X>TRMk8X%GiYOpTvegkV=M>!9t#+oVPN+XE?X-a42QFOHZLoe@;p4sHj# z?e1PWQLj9~$ZNUSZKSN4TjoypPZ<+e+@8k61ELtAo+Q<`Tbp@#v&-n!R2PPeY(ZO3 zkw5+BUp?}@P#}oAFh9#oFnJ!yrZ;XxA}%m?DvZ(lj9V=27LKjK9p`c+X?lWJ=EGgI z?7c8=6nJ`!ZQRYk2l(aC`8$6;&?#yt!_fWcP;!Ab&pqK ze3e|tB~NwEU9U#=#InjQ^<;bkb~Z}%Bg@4)V(44273?@DJ&~>#0PEBe1&1ZkH9x{T zCA8L>Uz)}nf@_i8>C)POo*Ne|V{QpSre6t;-h%7n}zk`~Rz|8j>C z?!IX3J0JxUTU3OsKjv7U=r#3Tg8qlLohO*Q?4rAhACMxnF}8Cep$E~(AZ+c7K@Hot zZ*#s4G-1eiben4KJ;uPQR1+2@f;FB8_F{g2*@SHtJMjX`a|6F963P*&>JjDYZ4;sl za|dUHNK?77{k2*71)PsSJ+%dFdtRw21@m?b%J7CS^=6g$sD-(`#E0#Y!&;aNoLLTf z*(k=?{g_*b4UAhzFd+dXx=HpA;5H!lnya)g%1v=9=sYa)7C3xvq06UTXa-ZgE;qhN ztfo}I{jOMw@e6s-(CBD1NMeqhU?s2#r&Np=%`J;m-lK=X*Z@wEt5luQ?jPkj!25Kv zLa^NPA$jDnHiCLYjDp#mddA+kpl#GRw=xJB$NqB9R;@d766mNYE3#&}&}#IMY34wA zhdoJ?BuI#xXc>JDndH`!G`zMKxvsYZN;%P@1_tyh0X9RO5EAF?o2#p<>NW&0F*p=X zoia>{YFB`*ehTbzSS~f>7Nn{NG2f_5Aj-$ybT;}mZ`(Q8kP0*@=N%Bb z$re8A#t0^Y4Fiu6EX++8nxFWr`7$*B!dZM>C7Xd1rY?21eg>$S>umAoa-e3Dvt}67 zYzK_pmbT5Q#|o3Gs&NHlv@i{oyupI0m%R1Z=|E18E}`oDDG=i$0>fMG==R%cyltu%&pf#w0-uDrRCg~Y7Irq8Hht+iF*=46Z zx&}e{05n_I#N?z}+*Q^%$9G^keN=kJ>{4&F=0`1O6hiQKGI~A^>Lw6U*)lRFikPLy z+M}O%edXAeQEWAWg;jyGw6uT7Yx#G7ptwYOstFnU5*YgKwr|MMWctQ_52$m|zCTL` zqSq$SlG{2Hr6W7G=TG%P6<_5dR#^{E*Ub=AjzhU@Rh}rcHxKCw1gXx~`j!~H3D$GH znW5rxS(%JgNzoJzcTE}-I)X!FcgihO?v8Ibhl~JJxofRM+Gyo72n8~l zq36QLir4t~xR~q^C`~57Sv?)__lzQaU+1#|)R>jwT@` z9R|5Bx!llxzHW!&I(j64R?0VlY6N3fgIS${jGZ=qCOv{!vMMggX7ZCd4AvjN7`Zb(sg{_g2; z?K<)JlPsM4sWSTp7QR_C;whYbZ$lAyTt&9;+IQN%BPlb6tyqCFe0Q&|)$gK3O5{C% zqypf`$8Am72v2bd30tGfL@@U87J;PeIbr-bW|FYi%jPkAQaj!1$(_TmXY48%$z8%Z zZinX&D1f-{wOHDiP6w3LLMcqo^qWmlwYyq7Yet|X)~#48PlBmNodCw3)GC%lUuMij z1rTLJQaWNIlPMp?j5CZ=E$#FY0O9JsoOZscI~PekrC#nhjujaX-C!%Z@rU6p_i{rZ zmTyv*<NpauW z9wQ?o@j5FE#&5MxJ&;z~B{SE8)JJ5YW#0~hJGECo>3$V9_e!L zWD@w}hH!E;Ap%A=4eiBAud;s;Po#mX)T_s7RrYDt>XM;x=V8jG>h`G3HWmiY^)_2u zidV9G8~TWoMRfjdFGbGwXFNVs*6X(tDX9npS-_4F?D>3+!}=Scr<)sDE&NfZW_ZnZ z>E=5WyH6T%iP{uPOILe3Q-?;RK2+{iGD^%1IGx4PFR%0j4XlfQooF0(U6*pGbX@uK zT=SN>7SA4-kk0Oo`!}wU`JFBi$}gv%I}?+Gan||{4xP!ChP^5706h(TpoP_tx|>$u zY;FB6s+Ypgh{7~>%B$#~VmDE~{Bc*2$Bz!Nor#zy&iF^Tt!Qq2vcOBq(eID$`U40b zd;t`Y!zvBQMNdip;j|pQ@8XnYsCjx8z$qUXhMZvISZzT65yl=O7{Ru>pv=(MNP#5pSK$&G!kzuVytsB*+_&mxlrsRLNvIqmRtKTUoTvv<5 zhn72vOgDx$kCpCsci-{?=gFRXu^`cP%$qzLkGD(M%-Bx%_nraYS0ZoFUd-|jh*hQ` zW#>FkZ>yl;eH)Oap;8yj2N zJ6!#W-zg@&awmpCtb>OTrmT8gScdv|M~6Bz04Y>R3!@RS6O*cvuXzk$S=_WS1TZEb zg>qxN!h7jaq|3fI=kv;Ckp@7HLe?g!B`_?0xdBsv8kB|rkf`?(N{wT9yACPZD~hzi zr$j!#@X*kDXWUOP*zZaEd<~5K5wS23m+=Ye_WkXP9^=kZd4v9-c*~D0B}+J{=28XB z+A;;G-YAA%G7`OjLL)+mNQ-Y~mzSOOyuhA^8}r6iZr@e%DPW(Pss%0o>3OUm_v z22%@ju{GaU9C70(<)78G$;QNS_5J+vCwi5Y*4fQOjaN8fn--$3Ey@N9}L8M{9YPq?*FJ<|Z{g$}Kf0e~8}m4VMf zI8|o zAFG@L;G_(#D?NB?fN%7uq3QeZ+mELh9L@;qf(S1=_%^@Fj-)Uw-;wvQW`SH1Djh=93a zYu}kEmfzDxErN>-WZ_j+RWqk2SO{2qX#V7vfshUgAph66`IY4Fkh77s)Fp0g3G zj|3cYu$ErsF2z^V{ac+s2cC=$N77{qU+!wWfezlRod`7yUTBf{fF0#qSq~-6{Ua5KWmTB*O}B= zHR6Qmy;A>YD(sH}mSrm!sb>wT%*JiBi$X zj*jUeU%Te2cbwBNXX>U&HaZZe8&P+t3(6(s{kLBX6!i>jE%XbJsvahpsx=%Q^`D5!u_4L&qBEzo-h>3yRdIz92%`El+!vyNCOg%*B3vSpE80 zJF24tf#@#O_fbr&-s=Pn=tu;QB+>v54?o$Y$7(rs>}ndWwbdU!4`2PPwg9(iCcae! zPiyD5@hdKhI2D|Gx^xwKA7!{#X7@5$pag%oR${P0#!1zBXn=vsJsFBe=Q9Or zgO7ZuQ>C9+=<4dmd)n{1`lF9@ES*CJjy_^(bZ%|Zpn!y@7-;sB@mz{QzA(0<=&>m^ z`aIIacG08)Y|{JpoWO@i?-#lS;?T$#rWw=nF6Nf=AWH$mI*u^=4Uif)H7~b1a%I>mbG61>jrq=I5=%Y z8OVs9H~;bj$HH+hWDEUuZ)~_ivCpte1%h=eUSDHk$dbGER`Rko$II2=Am;Sn2q5QR?A+@1zwYG?rrzgH?4)b84ZiG2vJ1tNVMwH+*WXHAL$ea9L!OC{$poBO zxpg9htHI?n7v{kUC2q}n7HMUd$?4|>a#tUMK6~iHy#UWX+9%cNOqFmP+B_HbTKv%v zZ|^>!z=L(-u-6s4>aid=pFj$oJm6t!CTUaGpWdT|A%&uNfV5yXIKx_nwf*(O{R`^| z_GZna=B-v<^RG+(m!3Cen@#d`?;|7Cn~N*3H4UQBbPxN(3X0CaAeW;T%esUm7{~}F zWmsZC%GukS-snXSK$!RqYFbqIRBin-|9xmowt4uc;;;O(tUk3tH-W<^m@NW+l^CKlNCmohkVuvS|&xU0<|!jPqgBrw9$P^YYoxwp5KVIz9Zp zJPJ}t#pR&#kIHwWXU)dJI|~S45~N3q`deO`blsp`jP9+M&|6#CcN^$wSQwprv4g%_ z!c{8G^JXwl;lN!_rq2II+Kne@^Q5FCQl5e-Mu@z~XwlxJjEaBC%pQ#tjlca?OpeZd ziPgr>eYjFY<9<_mC5>235$m53AzdQ7uIRjIE**UGeJQz&t*)_bEEt1I<0{Yzgw~hV z2nHr4jk@sza0V=Fh<2%_3>qZwWNmNnkI}oU;Q}bktbK$XOi=iG{AYzKj(l1@wc=;G zKok-pb%IX&eVN4vv)9lobehVopig)M8nvQBNn^ zQKPNC3+g}8u;oXtQAcRm@sle(+y1Yy6D_`-=?!~kB_o?{^`mpT)#@8p_NfFI*knDp zu3ft(b|(76aQy)7$Zx7h>;ljsK=p630$*M2)Jb*Yu_pYxb5=LEP*L5Ir^{=JYvi>9 znP)OWndDN{vu%(d`CS&SrltnpNHm&ljZW0hwZ>MQ;p{(4n0XHy$oX#4p`xNl?3?F7 z8ZOcEriYTC-gX#e)#S!;EYjo1k+6;GLjG zc1AM4VnNcgSz!r-Vawi8hbKoTU@9TmPp7TDI7K${F@C0KX;ly!tP2E2YWMUhYue zXaCP1FCXXuSsx7hz(WU~$WD^B;o)I)TNJ$jyanj{KbEX1L=~QTeI`dCPb>=?@9W+c^y*7Wt~&D z3qJ)Y;=WadHoDKjBbuq3feQpYRP%*ESW9Z9SSbaK{q`7-V`JN?$BYN+8=v{BF3#9O zy_C}i&p5F^Ltqjl?gtka^*`)3svFxMcRXy4aFLq)hbQw3ZZ6t5BLhGHSJdL9GL^DW zD6y2ys)`D~Dv;&9tKC(OpTy5xslD@x=|+m927ha7D>*s&BP?eD_4`?*w)zWRnK@dP zZQ@8OLbV_jPIwb_R{SMhw_h!efb@|o_u)1m(~iC#4ni4B9v&>OUfs#jEg6+dOCuvB z1OWxNR{Gn?!j+R7V(Y-U0@zBS^$UM`YN}KDICQ$&m5n=#2UdkFs87maQZ5U7RaJCk zCx_i_E);}%=kv;bDic#g7;4xgxjb*`1LfZ1R|ac;I1IK#UIA{Y{C*kS)x7ubBMcWC z>c4)}fbVo6j=v0Xa&nG*bwrFxM$#!%gsjj}qn?W>?MzPH4a8n8G2|2WL*gecTrznS zeN!>yvz}oe)yut_doH@|Y6G^HULFW-Fp8{5h1gMX(1gb$a`x^Cp@qYBa2z{Nk`lh3 zAZxHKn>Tb=Kg-S*M-IwyVlzl)Zsg>)n0E58jfS*pV+v1PeY;5M!*hv7&5r&qW?$d zTlR;KymQw$$=-!q z3wU_^*`tpiEp?k0j)cG9pj(hj<)E966AF)CHa&c|cQ3FpPZchAo40Kleb^z&5TJp?&EX=c$C9^SzMWEwAXrk!kwCW4yW~$6YVVVfvnGuaJX6)5%QpW1nreZR<;f zB)Z66^E6`WtO_=jt%#m-bWGhE{lGa3k0%45HY6Gw!!hHiGC z@9Bv7DMosm`-h*(i?CdUD}Somt##JSxW%OZ_SzGdU8N&=rBNG~#~alGE{>XHRGgs%&)!gckEOhw&)0F4 z8jxZQ6XnC+yQB5^v$Pf4(gJYtRO>GLTR9!C4gwQETEaM$j z%9?wp!LVu*}nGc z1(KshyEGej>$2Hzj9s`6gHjcP%5msMjln_M$OIMBy0<;8;9jx7S|w8zOV9zT+7leo z$jZ&}!@-%wLRH89Q4y;@>mYEp(aX2P}G zybr+FRA1!XJ2H8y*6EUXqy}ih4<~zP0Yu80wsx0r&F3Lb3&ip^t4gQ8xv{yEQvr8^ zk?QJ)G7fO*N^N6<7)*ukzXJ2wHARkp$x#{(65+QvE=PX3G5Wt+zWP77Vk;KU<_N5& zN3GY&z`c?S4<+n6s4xzE?5!4B@>KsTgguTPd;@ZE7QzDjuRv~^_XJ+a7vM%*CdI$U zI@Oa-xu%r={14OjGdSqKPQCx<82tY?q%`~$%dj-72es!>jEX^DAUHA5AiRJ})W5Qp zDy2=&xO}__!V&{nZ_edH@)1#cc09u1|4Pm3|NVOpRBSJtI_sw{$0Uc4^5@!k?G6o7 a_div_MF#sfQJ6(Rzco~~;KeFd&;Ad6id;7U literal 0 HcmV?d00001 diff --git a/website/docs/assets/3dsmax_menu_OP.png b/website/docs/assets/3dsmax_menu_OP.png index f707a7fd956aeb7367abd3fda3ba520012540e95..bce2f9aac072f227c9f92cdf17c91c5e9b4d979c 100644 GIT binary patch literal 63305 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 T21Ktx`Ou5!a^gjzy59c_^@|^B literal 69195 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 Date: Fri, 27 Jan 2023 15:33:07 +0100 Subject: [PATCH 080/356] Fix: import workfile all families --- openpype/hosts/blender/plugins/load/import_workfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/import_workfile.py b/openpype/hosts/blender/plugins/load/import_workfile.py index 618fb83e31..bbdf1c7ea0 100644 --- a/openpype/hosts/blender/plugins/load/import_workfile.py +++ b/openpype/hosts/blender/plugins/load/import_workfile.py @@ -44,7 +44,7 @@ class AppendBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - families = ["*"] + families = ["workfile"] label = "Append Workfile" order = 9 @@ -68,7 +68,7 @@ class ImportBlendLoader(plugin.AssetLoader): """ representations = ["blend"] - families = ["*"] + families = ["workfile"] label = "Import Workfile" order = 9 From 06222e727b4fe3483bf0a0ccc1adaf3b3d641041 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 15:46:57 +0100 Subject: [PATCH 081/356] implemented "not equal" method for attribute definitions (Py2 comp) --- openpype/lib/attribute_definitions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 04db0edc64..ca82644894 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -156,6 +156,9 @@ class AbtractAttrDef(object): return False return self.key == other.key + def __ne__(self, other): + return not self.__eq__(other) + @abstractproperty def type(self): """Attribute definition type also used as identifier of class. From 36df58aba11a2334bc6e0d664f1dc08097f22e82 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 15:47:30 +0100 Subject: [PATCH 082/356] added more conditions for comparison --- openpype/lib/attribute_definitions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index ca82644894..1debd0aa79 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -154,7 +154,12 @@ class AbtractAttrDef(object): def __eq__(self, other): if not isinstance(other, self.__class__): return False - return self.key == other.key + return ( + self.key == other.key + and self.hidden == other.hidden + and self.default == other.default + and self.disabled == other.disabled + ) def __ne__(self, other): return not self.__eq__(other) From d48b73ed61c9ff385a8fd2bf376ecfaaa51ad91d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:28:00 +0100 Subject: [PATCH 083/356] fix typo 'AbtractAttrDef' to 'AbstractAttrDef' --- openpype/lib/__init__.py | 4 +-- openpype/lib/attribute_definitions.py | 36 +++++++++---------- openpype/pipeline/create/context.py | 2 +- openpype/pipeline/create/creator_plugins.py | 4 +-- openpype/pipeline/publish/publish_plugins.py | 2 +- .../workfile/workfile_template_builder.py | 6 ++-- openpype/tools/attribute_defs/widgets.py | 6 ++-- openpype/tools/loader/lib.py | 4 +-- openpype/tools/utils/widgets.py | 4 +-- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index a64b7c2911..b5fb955a84 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -30,7 +30,7 @@ from .vendor_bin_utils import ( ) from .attribute_definitions import ( - AbtractAttrDef, + AbstractAttrDef, UIDef, UISeparatorDef, @@ -246,7 +246,7 @@ __all__ = [ "get_ffmpeg_tool_path", "is_oiio_supported", - "AbtractAttrDef", + "AbstractAttrDef", "UIDef", "UISeparatorDef", diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index 1debd0aa79..efd38761c8 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -20,7 +20,7 @@ def register_attr_def_class(cls): Currently are registered definitions used to deserialize data to objects. Attrs: - cls (AbtractAttrDef): Non-abstract class to be registered with unique + cls (AbstractAttrDef): Non-abstract class to be registered with unique 'type' attribute. Raises: @@ -36,7 +36,7 @@ def get_attributes_keys(attribute_definitions): """Collect keys from list of attribute definitions. Args: - attribute_definitions (List[AbtractAttrDef]): Objects of attribute + attribute_definitions (List[AbstractAttrDef]): Objects of attribute definitions. Returns: @@ -57,7 +57,7 @@ def get_default_values(attribute_definitions): """Receive default values for attribute definitions. Args: - attribute_definitions (List[AbtractAttrDef]): Attribute definitions for + attribute_definitions (List[AbstractAttrDef]): Attribute definitions for which default values should be collected. Returns: @@ -76,15 +76,15 @@ def get_default_values(attribute_definitions): class AbstractAttrDefMeta(ABCMeta): - """Meta class to validate existence of 'key' attribute. + """Metaclass to validate existence of 'key' attribute. - Each object of `AbtractAttrDef` mus have defined 'key' attribute. + Each object of `AbstractAttrDef` mus have defined 'key' attribute. """ def __call__(self, *args, **kwargs): obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs) init_class = getattr(obj, "__init__class__", None) - if init_class is not AbtractAttrDef: + if init_class is not AbstractAttrDef: raise TypeError("{} super was not called in __init__.".format( type(obj) )) @@ -92,7 +92,7 @@ class AbstractAttrDefMeta(ABCMeta): @six.add_metaclass(AbstractAttrDefMeta) -class AbtractAttrDef(object): +class AbstractAttrDef(object): """Abstraction of attribute definiton. Each attribute definition must have implemented validation and @@ -145,7 +145,7 @@ class AbtractAttrDef(object): self.disabled = disabled self._id = uuid.uuid4().hex - self.__init__class__ = AbtractAttrDef + self.__init__class__ = AbstractAttrDef @property def id(self): @@ -220,7 +220,7 @@ class AbtractAttrDef(object): # UI attribute definitoins won't hold value # ----------------------------------------- -class UIDef(AbtractAttrDef): +class UIDef(AbstractAttrDef): is_value_def = False def __init__(self, key=None, default=None, *args, **kwargs): @@ -245,7 +245,7 @@ class UILabelDef(UIDef): # Attribute defintioins should hold value # --------------------------------------- -class UnknownDef(AbtractAttrDef): +class UnknownDef(AbstractAttrDef): """Definition is not known because definition is not available. This attribute can be used to keep existing data unchanged but does not @@ -262,7 +262,7 @@ class UnknownDef(AbtractAttrDef): return value -class HiddenDef(AbtractAttrDef): +class HiddenDef(AbstractAttrDef): """Hidden value of Any type. This attribute can be used for UI purposes to pass values related @@ -282,7 +282,7 @@ class HiddenDef(AbtractAttrDef): return value -class NumberDef(AbtractAttrDef): +class NumberDef(AbstractAttrDef): """Number definition. Number can have defined minimum/maximum value and decimal points. Value @@ -358,7 +358,7 @@ class NumberDef(AbtractAttrDef): return round(float(value), self.decimals) -class TextDef(AbtractAttrDef): +class TextDef(AbstractAttrDef): """Text definition. Text can have multiline option so endline characters are allowed regex @@ -423,7 +423,7 @@ class TextDef(AbtractAttrDef): return data -class EnumDef(AbtractAttrDef): +class EnumDef(AbstractAttrDef): """Enumeration of single item from items. Args: @@ -531,7 +531,7 @@ class EnumDef(AbtractAttrDef): return output -class BoolDef(AbtractAttrDef): +class BoolDef(AbstractAttrDef): """Boolean representation. Args: @@ -776,7 +776,7 @@ class FileDefItem(object): return output -class FileDef(AbtractAttrDef): +class FileDef(AbstractAttrDef): """File definition. It is possible to define filters of allowed file extensions and if supports folders. @@ -894,7 +894,7 @@ def serialize_attr_def(attr_def): """Serialize attribute definition to data. Args: - attr_def (AbtractAttrDef): Attribute definition to serialize. + attr_def (AbstractAttrDef): Attribute definition to serialize. Returns: Dict[str, Any]: Serialized data. @@ -907,7 +907,7 @@ def serialize_attr_defs(attr_defs): """Serialize attribute definitions to data. Args: - attr_defs (List[AbtractAttrDef]): Attribute definitions to serialize. + attr_defs (List[AbstractAttrDef]): Attribute definitions to serialize. Returns: List[Dict[str, Any]]: Serialized data. diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9c468ae8fc..c3cf3e9b4b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -208,7 +208,7 @@ class AttributeValues(object): Has dictionary like methods. Not all of them are allowed all the time. Args: - attr_defs(AbtractAttrDef): Defintions of value type and properties. + attr_defs(AbstractAttrDef): Defintions of value type and properties. values(dict): Values after possible conversion. origin_data(dict): Values loaded from host before conversion. """ diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8500dd1e22..8ac8959c76 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -425,7 +425,7 @@ class BaseCreator: keys/values when plugin attributes change. Returns: - List[AbtractAttrDef]: Attribute definitions that can be tweaked for + List[AbstractAttrDef]: Attribute definitions that can be tweaked for created instance. """ @@ -563,7 +563,7 @@ class Creator(BaseCreator): updating keys/values when plugin attributes change. Returns: - List[AbtractAttrDef]: Attribute definitions that can be tweaked for + List[AbstractAttrDef]: Attribute definitions that can be tweaked for created instance. """ return self.pre_create_attr_defs diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index d9145275f7..96e4aae237 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -118,7 +118,7 @@ class OpenPypePyblishPluginMixin: Attributes available for all families in plugin's `families` attribute. Returns: - list: Attribute definitions for plugin. + list: Attribute definitions for plugin. """ return [] diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 1266c27fd7..051eb444c6 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -842,7 +842,7 @@ class PlaceholderPlugin(object): """Placeholder options for data showed. Returns: - List[AbtractAttrDef]: Attribute definitions of placeholder options. + List[AbstractAttrDef]: Attribute definitions of placeholder options. """ return [] @@ -1143,7 +1143,7 @@ class PlaceholderLoadMixin(object): as defaults for attributes. Returns: - List[AbtractAttrDef]: Attribute definitions common for load + List[AbstractAttrDef]: Attribute definitions common for load plugins. """ @@ -1513,7 +1513,7 @@ class PlaceholderCreateMixin(object): as defaults for attributes. Returns: - List[AbtractAttrDef]: Attribute definitions common for create + List[AbstractAttrDef]: Attribute definitions common for create plugins. """ diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index bf61dc3776..3cec1d2683 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -4,7 +4,7 @@ import copy from qtpy import QtWidgets, QtCore from openpype.lib.attribute_definitions import ( - AbtractAttrDef, + AbstractAttrDef, UnknownDef, HiddenDef, NumberDef, @@ -33,9 +33,9 @@ def create_widget_for_attr_def(attr_def, parent=None): def _create_widget_for_attr_def(attr_def, parent=None): - if not isinstance(attr_def, AbtractAttrDef): + if not isinstance(attr_def, AbstractAttrDef): raise TypeError("Unexpected type \"{}\" expected \"{}\"".format( - str(type(attr_def)), AbtractAttrDef + str(type(attr_def)), AbstractAttrDef )) if isinstance(attr_def, NumberDef): diff --git a/openpype/tools/loader/lib.py b/openpype/tools/loader/lib.py index 552dc91a10..d47bc7e07a 100644 --- a/openpype/tools/loader/lib.py +++ b/openpype/tools/loader/lib.py @@ -2,7 +2,7 @@ import inspect from qtpy import QtGui import qtawesome -from openpype.lib.attribute_definitions import AbtractAttrDef +from openpype.lib.attribute_definitions import AbstractAttrDef from openpype.tools.attribute_defs import AttributeDefinitionsDialog from openpype.tools.utils.widgets import ( OptionalAction, @@ -43,7 +43,7 @@ def get_options(action, loader, parent, repre_contexts): if not getattr(action, "optioned", False) or not loader_options: return options - if isinstance(loader_options[0], AbtractAttrDef): + if isinstance(loader_options[0], AbstractAttrDef): qargparse_options = False dialog = AttributeDefinitionsDialog(loader_options, parent) else: diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index a9d6fa35b2..41573687e1 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -8,7 +8,7 @@ from openpype.style import ( get_objected_colors, get_style_image_path ) -from openpype.lib.attribute_definitions import AbtractAttrDef +from openpype.lib.attribute_definitions import AbstractAttrDef log = logging.getLogger(__name__) @@ -406,7 +406,7 @@ class OptionalAction(QtWidgets.QWidgetAction): def set_option_tip(self, options): sep = "\n\n" - if not options or not isinstance(options[0], AbtractAttrDef): + if not options or not isinstance(options[0], AbstractAttrDef): mak = (lambda opt: opt["name"] + " :\n " + opt["help"]) self.option_tip = sep.join(mak(opt) for opt in options) return From 36065080c7796cf61e9e1d8e2f55436bd58f46fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:30:02 +0100 Subject: [PATCH 084/356] import 'UnknownDef' at the top of file --- openpype/pipeline/create/context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c3cf3e9b4b..9d3aa37de2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -13,6 +13,9 @@ from openpype.settings import ( get_system_settings, get_project_settings ) +from openpype.lib.attribute_definitions import ( + UnknownDef, +) from openpype.host import IPublishHost from openpype.pipeline import legacy_io from openpype.pipeline.mongodb import ( @@ -214,8 +217,6 @@ class AttributeValues(object): """ def __init__(self, attr_defs, values, origin_data=None): - from openpype.lib.attribute_definitions import UnknownDef - if origin_data is None: origin_data = copy.deepcopy(values) self._origin_data = origin_data From 1d91ee53388c806e8c04be79e8e2249a052cd4d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:31:01 +0100 Subject: [PATCH 085/356] attr_defs property returns list copy of attributes --- openpype/pipeline/create/context.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9d3aa37de2..9360db63ed 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -289,8 +289,13 @@ class AttributeValues(object): @property def attr_defs(self): - """Pointer to attribute definitions.""" - return self._attr_defs + """Pointer to attribute definitions. + + Returns: + List[AbstractAttrDef]: Attribute definitions. + """ + + return list(self._attr_defs) def data_to_store(self): """Create new dictionary with data to store.""" From 8f6d36dfa0291590cf245cd10592b40e4606f5c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:33:50 +0100 Subject: [PATCH 086/356] implemented de/serialization methods for attribute values --- openpype/pipeline/create/context.py | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9360db63ed..fe88fbc5c3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -15,6 +15,8 @@ from openpype.settings import ( ) from openpype.lib.attribute_definitions import ( UnknownDef, + serialize_attr_defs, + deserialize_attr_defs, ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io @@ -331,6 +333,15 @@ class AttributeValues(object): elif self.get(key) != new_value: self[key] = new_value + def get_serialized_attr_defs(self): + """Serialize attribute definitions to json serializable types. + + Returns: + List[Dict[str, Any]]: Serialized attribute definitions. + """ + + return serialize_attr_defs(self._attr_defs) + class CreatorAttributeValues(AttributeValues): """Creator specific attribute values of an instance. @@ -515,6 +526,42 @@ class PublishAttributes: self, [], value, value ) + def serialize_attributes(self): + return { + "attr_defs": { + plugin_name: attrs_value.get_serialized_attr_defs() + for plugin_name, attrs_value in self._data.items() + }, + "plugin_names_order": self._plugin_names_order, + "missing_plugins": self._missing_plugins + } + + def deserialize_attributes(self, data): + self._plugin_names_order = data["plugin_names_order"] + self._missing_plugins = data["missing_plugins"] + + attr_defs = deserialize_attr_defs(data["attr_defs"]) + + origin_data = self._origin_data + data = self._data + self._data = {} + + added_keys = set() + for plugin_name, attr_defs_data in attr_defs.items(): + attr_defs = deserialize_attr_defs(attr_defs_data) + value = data.get(plugin_name) or {} + orig_value = copy.deepcopy(origin_data.get(plugin_name) or {}) + self._data[plugin_name] = PublishAttributeValues( + self, attr_defs, value, orig_value + ) + + for key, value in data.items(): + if key not in added_keys: + self._missing_plugins.append(key) + self._data[key] = PublishAttributeValues( + self, [], value, value + ) + class CreatedInstance: """Instance entity with data that will be stored to workfile. From eac4167a22703cc2b77adcf45d73978c28c3b5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 27 Jan 2023 16:33:51 +0100 Subject: [PATCH 087/356] :bug: hotfix condition --- openpype/hosts/houdini/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 4ca6b50702..61127bda57 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -113,7 +113,7 @@ class HoudiniCreatorBase(object): Dict[str, Any]: Shared data dictionary. """ - if shared_data.get("houdini_cached_subsets") is not None: + if shared_data.get("houdini_cached_subsets") is None: cache = dict() cache_legacy = dict() From 6a1acc166425ca716b10403f02d38c73ba2f2ab8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:34:36 +0100 Subject: [PATCH 088/356] 'CreatedInstance' can be initialized without creator --- openpype/pipeline/create/context.py | 50 +++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index fe88fbc5c3..2c1a13100b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -570,15 +570,22 @@ class CreatedInstance: about instance like "asset" and "task" and all data used for filling subset name as creators may have custom data for subset name filling. + Notes: + Object have 2 possible initialization. One using 'creator' object which + is recommended for api usage. Second by passing information about + creator. + Args: - family(str): Name of family that will be created. - subset_name(str): Name of subset that will be created. - data(dict): Data used for filling subset name or override data from - already existing instance. - creator(BaseCreator): Creator responsible for instance. - host(ModuleType): Host implementation loaded with - `openpype.pipeline.registered_host`. - new(bool): Is instance new. + family (str): Name of family that will be created. + subset_name (str): Name of subset that will be created. + data (Dict[str, Any]): Data used for filling subset name or override + data from already existing instance. + creator (Union[BaseCreator, None]): Creator responsible for instance. + creator_identifier (str): Identifier of creator plugin. + creator_label (str): Creator plugin label. + group_label (str): Default group label from creator plugin. + creator_attr_defs (List[AbstractAttrDef]): Attribute definitions from + creator. """ # Keys that can't be changed or removed from data after loading using @@ -595,9 +602,24 @@ class CreatedInstance: ) def __init__( - self, family, subset_name, data, creator, new=True + self, + family, + subset_name, + data, + creator=None, + creator_identifier=None, + creator_label=None, + group_label=None, + creator_attr_defs=None, ): - self.creator = creator + if creator is not None: + creator_identifier = creator.identifier + group_label = creator.get_group_label() + creator_label = creator.label + creator_attr_defs = creator.get_instance_attr_defs() + + self._creator_label = creator_label + self._group_label = group_label or creator_identifier # Instance members may have actions on them # TODO implement members logic @@ -627,7 +649,7 @@ class CreatedInstance: self._data["family"] = family self._data["subset"] = subset_name self._data["active"] = data.get("active", True) - self._data["creator_identifier"] = creator.identifier + self._data["creator_identifier"] = creator_identifier # Pop from source data all keys that are defined in `_data` before # this moment and through their values away @@ -641,10 +663,12 @@ class CreatedInstance: # Stored creator specific attribute values # {key: value} creator_values = copy.deepcopy(orig_creator_attributes) - creator_attr_defs = creator.get_instance_attr_defs() self._data["creator_attributes"] = CreatorAttributeValues( - self, creator_attr_defs, creator_values, orig_creator_attributes + self, + list(creator_attr_defs), + creator_values, + orig_creator_attributes ) # Stored publish specific attribute values From 373d2452423f7a7eca5161361022d7cba15fb705 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:35:20 +0100 Subject: [PATCH 089/356] moved context validation methods to bottom --- openpype/pipeline/create/context.py | 88 ++++++++++++++--------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2c1a13100b..081fdd86ff 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -763,51 +763,6 @@ class CreatedInstance: def creator_label(self): return self.creator.label or self.creator_identifier - @property - def create_context(self): - return self.creator.create_context - - @property - def host(self): - return self.create_context.host - - @property - def has_set_asset(self): - """Asset name is set in data.""" - return "asset" in self._data - - @property - def has_set_task(self): - """Task name is set in data.""" - return "task" in self._data - - @property - def has_valid_context(self): - """Context data are valid for publishing.""" - return self.has_valid_asset and self.has_valid_task - - @property - def has_valid_asset(self): - """Asset set in context exists in project.""" - if not self.has_set_asset: - return False - return self._asset_is_valid - - @property - def has_valid_task(self): - """Task set in context exists in project.""" - if not self.has_set_task: - return False - return self._task_is_valid - - def set_asset_invalid(self, invalid): - # TODO replace with `set_asset_name` - self._asset_is_valid = not invalid - - def set_task_invalid(self, invalid): - # TODO replace with `set_task_name` - self._task_is_valid = not invalid - @property def id(self): """Instance identifier.""" @@ -1039,6 +994,49 @@ class CreatedInstance: if current_value != new_value: self[key] = new_value + # Context validation related methods/properties + @property + def has_set_asset(self): + """Asset name is set in data.""" + + return "asset" in self._data + + @property + def has_set_task(self): + """Task name is set in data.""" + + return "task" in self._data + + @property + def has_valid_context(self): + """Context data are valid for publishing.""" + + return self.has_valid_asset and self.has_valid_task + + @property + def has_valid_asset(self): + """Asset set in context exists in project.""" + + if not self.has_set_asset: + return False + return self._asset_is_valid + + @property + def has_valid_task(self): + """Task set in context exists in project.""" + + if not self.has_set_task: + return False + return self._task_is_valid + + def set_asset_invalid(self, invalid): + # TODO replace with `set_asset_name` + self._asset_is_valid = not invalid + + def set_task_invalid(self, invalid): + # TODO replace with `set_task_name` + self._task_is_valid = not invalid + class ConvertorItem(object): """Item representing convertor plugin. From e7fdb1d151a117f975eb6094be4b4d761029136f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:36:42 +0100 Subject: [PATCH 090/356] don't use creator in methods --- openpype/pipeline/create/context.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 081fdd86ff..0f288f0bd5 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -753,15 +753,15 @@ class CreatedInstance: label = self._data.get("group") if label: return label - return self.creator.get_group_label() + return self._group_label @property def creator_identifier(self): - return self.creator.identifier + return self._data["creator_identifier"] @property def creator_label(self): - return self.creator.label or self.creator_identifier + return self._creator_label or self.creator_identifier @property def id(self): @@ -1477,7 +1477,7 @@ class CreateContext: self._instances_by_id[instance.id] = instance # Prepare publish plugin attributes and set it on instance attr_plugins = self._get_publish_plugins_with_attr_for_family( - instance.creator.family + instance.family ) instance.set_publish_plugins(attr_plugins) From 86ece89a3a2756516ec1e3903e1ba4d4f6b52f69 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:37:14 +0100 Subject: [PATCH 091/356] de/serialization of CreatedInstance does not require creator --- openpype/pipeline/create/context.py | 35 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0f288f0bd5..0a5d186273 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -904,9 +904,21 @@ class CreatedInstance: self._members.append(member) def serialize_for_remote(self): + """Serialize object into data to be possible recreated object. + + Returns: + Dict[str, Any]: Serialized data. + """ + + creator_attr_defs = self.creator_attributes.get_serialized_attr_defs() + publish_attributes = self.publish_attributes.serialize_attributes() return { "data": self.data_to_store(), - "orig_data": copy.deepcopy(self._orig_data) + "orig_data": copy.deepcopy(self._orig_data), + "creator_attr_defs": creator_attr_defs, + "publish_attributes": publish_attributes, + "creator_label": self._creator_label, + "group_label": self._group_label, } @classmethod @@ -927,17 +939,28 @@ class CreatedInstance: instance_data = copy.deepcopy(serialized_data["data"]) creator_identifier = instance_data["creator_identifier"] - creator_item = creator_items[creator_identifier] - family = instance_data.get("family", None) - if family is None: - family = creator_item.family + family = instance_data["family"] subset_name = instance_data.get("subset", None) + creator_label = serialized_data["creator_label"] + group_label = serialized_data["group_label"] + creator_attr_defs = deserialize_attr_defs( + serialized_data["creator_attr_defs"] + ) + publish_attributes = serialized_data["publish_attributes"] + obj = cls( - family, subset_name, instance_data, creator_item, new=False + family, + subset_name, + instance_data, + creator_identifier=creator_identifier, + creator_label=creator_label, + group_label=group_label, + creator_attributes=creator_attr_defs ) obj._orig_data = serialized_data["orig_data"] + obj.publish_attributes.deserialize_attributes(publish_attributes) return obj From 4ec904e2bfd592f956917888014bdc5acc1195e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:37:40 +0100 Subject: [PATCH 092/356] don't pass removed 'new' attribute --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 0a5d186273..2ccd50006e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -891,7 +891,7 @@ class CreatedInstance: subset_name = instance_data.get("subset", None) return cls( - family, subset_name, instance_data, creator, new=False + family, subset_name, instance_data, creator ) def set_publish_plugins(self, attr_plugins): From 951e8a50557c1e3545da8cb63b557813b163d179 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:42:22 +0100 Subject: [PATCH 093/356] don't pass creator_items to 'deserialize_on_remote' --- openpype/pipeline/create/context.py | 2 +- openpype/tools/publisher/control_qt.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2ccd50006e..b52c870329 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -922,7 +922,7 @@ class CreatedInstance: } @classmethod - def deserialize_on_remote(cls, serialized_data, creator_items): + def deserialize_on_remote(cls, serialized_data): """Convert instance data to CreatedInstance. This is fake instance in remote process e.g. in UI process. The creator diff --git a/openpype/tools/publisher/control_qt.py b/openpype/tools/publisher/control_qt.py index 3639c4bb30..132b42f9ec 100644 --- a/openpype/tools/publisher/control_qt.py +++ b/openpype/tools/publisher/control_qt.py @@ -136,10 +136,7 @@ class QtRemotePublishController(BasePublisherController): created_instances = {} for serialized_data in serialized_instances: - item = CreatedInstance.deserialize_on_remote( - serialized_data, - self._creator_items - ) + item = CreatedInstance.deserialize_on_remote(serialized_data) created_instances[item.id] = item self._created_instances = created_instances From 75d72f4d2dd7fa8c8e65085ee1351ca54ab21121 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:42:38 +0100 Subject: [PATCH 094/356] fix 'pre_create_attributes_defs' --- openpype/tools/publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 50a814de5c..ca2b083e80 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -910,7 +910,7 @@ class CreatorItem: pre_create_attributes_defs = None if self.pre_create_attributes_defs is not None: - instance_attributes_defs = serialize_attr_defs( + pre_create_attributes_defs = serialize_attr_defs( self.pre_create_attributes_defs ) From b5e35eaa63bd03463abd4f6b4a5d410745321afa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:43:06 +0100 Subject: [PATCH 095/356] CreatorItem in controller does not have 'instance_attributes_defs' --- openpype/tools/publisher/control.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index ca2b083e80..61969691c6 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -826,7 +826,6 @@ class CreatorItem: label, group_label, icon, - instance_attributes_defs, description, detailed_description, default_variant, @@ -847,12 +846,8 @@ class CreatorItem: self.default_variants = default_variants self.create_allow_context_change = create_allow_context_change self.create_allow_thumbnail = create_allow_thumbnail - self.instance_attributes_defs = instance_attributes_defs self.pre_create_attributes_defs = pre_create_attributes_defs - def get_instance_attr_defs(self): - return self.instance_attributes_defs - def get_group_label(self): return self.group_label @@ -891,7 +886,6 @@ class CreatorItem: creator.label or identifier, creator.get_group_label(), creator.get_icon(), - creator.get_instance_attr_defs(), description, detail_description, default_variant, @@ -902,12 +896,6 @@ class CreatorItem: ) def to_data(self): - instance_attributes_defs = None - if self.instance_attributes_defs is not None: - instance_attributes_defs = serialize_attr_defs( - self.instance_attributes_defs - ) - pre_create_attributes_defs = None if self.pre_create_attributes_defs is not None: pre_create_attributes_defs = serialize_attr_defs( @@ -927,18 +915,11 @@ class CreatorItem: "default_variants": self.default_variants, "create_allow_context_change": self.create_allow_context_change, "create_allow_thumbnail": self.create_allow_thumbnail, - "instance_attributes_defs": instance_attributes_defs, "pre_create_attributes_defs": pre_create_attributes_defs, } @classmethod def from_data(cls, data): - instance_attributes_defs = data["instance_attributes_defs"] - if instance_attributes_defs is not None: - data["instance_attributes_defs"] = deserialize_attr_defs( - instance_attributes_defs - ) - pre_create_attributes_defs = data["pre_create_attributes_defs"] if pre_create_attributes_defs is not None: data["pre_create_attributes_defs"] = deserialize_attr_defs( From d448004f541114c50a987a96bc5454a828c4de72 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:43:24 +0100 Subject: [PATCH 096/356] get attributes from instances directly --- openpype/tools/publisher/control.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 61969691c6..7c8da66744 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1860,12 +1860,12 @@ class PublisherController(BasePublisherController): which should be attribute definitions returned. """ + # NOTE it would be great if attrdefs would have hash method implemented + # so they could be used as keys in dictionary output = [] _attr_defs = {} for instance in instances: - creator_identifier = instance.creator_identifier - creator_item = self.creator_items[creator_identifier] - for attr_def in creator_item.instance_attributes_defs: + for attr_def in instance.creator_attribute_defs: found_idx = None for idx, _attr_def in _attr_defs.items(): if attr_def == _attr_def: From 96094324fabfe015730415b265188ce84e000924 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:43:50 +0100 Subject: [PATCH 097/356] check for 'creator_identifier' instead of 'creator' --- openpype/tools/publisher/widgets/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 2e8d0ce37c..587bcb059d 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1220,7 +1220,8 @@ class GlobalAttrsWidget(QtWidgets.QWidget): asset_task_combinations = [] for instance in instances: - if instance.creator is None: + # NOTE I'm not sure how this can even happen? + if instance.creator_identifier is None: editable = False variants.add(instance.get("variant") or self.unknown_value) From 3aaf349b694dcb6306eda7ebcda10a37636dd2f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 16:44:05 +0100 Subject: [PATCH 098/356] modified few docstrings --- openpype/pipeline/create/context.py | 60 ++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b52c870329..9d42764aef 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -33,6 +33,7 @@ from .creator_plugins import ( CreatorError, ) +# Changes of instances and context are send as tuple of 2 information UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"]) @@ -300,7 +301,12 @@ class AttributeValues(object): return list(self._attr_defs) def data_to_store(self): - """Create new dictionary with data to store.""" + """Create new dictionary with data to store. + + Returns: + Dict[str, Any]: Attribute values that should be stored. + """ + output = {} for key in self._data: output[key] = self[key] @@ -313,6 +319,7 @@ class AttributeValues(object): @staticmethod def calculate_changes(new_data, old_data): """Calculate changes of 2 dictionary objects.""" + changes = {} for key, new_value in new_data.items(): old_value = old_data.get(key) @@ -379,13 +386,14 @@ class PublishAttributes: """Wrapper for publish plugin attribute definitions. Cares about handling attribute definitions of multiple publish plugins. + Keep information about attribute definitions and their values. Args: parent(CreatedInstance, CreateContext): Parent for which will be data stored and from which are data loaded. origin_data(dict): Loaded data by plugin class name. - attr_plugins(list): List of publish plugins that may have defined - attribute definitions. + attr_plugins(Union[List[pyblish.api.Plugin], None]): List of publish + plugins that may have defined attribute definitions. """ def __init__(self, parent, origin_data, attr_plugins=None): @@ -765,7 +773,11 @@ class CreatedInstance: @property def id(self): - """Instance identifier.""" + """Instance identifier. + + Returns: + str: UUID of instance. + """ return self._data["instance_id"] @@ -774,6 +786,10 @@ class CreatedInstance: """Legacy access to data. Access to data is needed to modify values. + + Returns: + CreatedInstance: Object can be used as dictionary but with + validations of immutable keys. """ return self @@ -850,6 +866,12 @@ class CreatedInstance: @property def creator_attribute_defs(self): + """Attribute defintions defined by creator plugin. + + Returns: + List[AbstractAttrDef]: Attribute defitions. + """ + return self.creator_attributes.attr_defs @property @@ -861,7 +883,7 @@ class CreatedInstance: It is possible to recreate the instance using these data. - Todo: + Todos: We probably don't need OrderedDict. When data are loaded they are not ordered anymore. @@ -882,7 +904,15 @@ class CreatedInstance: @classmethod def from_existing(cls, instance_data, creator): - """Convert instance data from workfile to CreatedInstance.""" + """Convert instance data from workfile to CreatedInstance. + + Args: + instance_data (Dict[str, Any]): Data in a structure ready for + 'CreatedInstance' object. + creator (Creator): Creator plugin which is creating the instance + of for which the instance belong. + """ + instance_data = copy.deepcopy(instance_data) family = instance_data.get("family", None) @@ -895,10 +925,21 @@ class CreatedInstance: ) def set_publish_plugins(self, attr_plugins): + """Set publish plugins with attribute definitions. + + This method should be called only from 'CreateContext'. + + Args: + attr_plugins (List[pyblish.api.Plugin]): Pyblish plugins which + inherit from 'OpenPypePyblishPluginMixin' and may contain + attribute definitions. + """ + self.publish_attributes.set_publish_plugins(attr_plugins) def add_members(self, members): """Currently unused method.""" + for member in members: if member not in self._members: self._members.append(member) @@ -932,9 +973,6 @@ class CreatedInstance: Args: serialized_data (Dict[str, Any]): Serialized data for remote recreating. Should contain 'data' and 'orig_data'. - creator_items (Dict[str, Any]): Mapping of creator identifier and - objects that behave like a creator for most of attribute - access. """ instance_data = copy.deepcopy(serialized_data["data"]) @@ -1098,6 +1136,10 @@ class CreateContext: Context itself also can store data related to whole creation (workfile). - those are mainly for Context publish plugins + Todos: + Don't use 'AvalonMongoDB'. It's used only to keep track about current + context which should be handled by host. + Args: host(ModuleType): Host implementation which handles implementation and global metadata. From 5d9e7cf52d02ab2346d8a5e06a9ce06d1c2b675b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 27 Jan 2023 17:21:28 +0100 Subject: [PATCH 099/356] fix formatting --- openpype/lib/attribute_definitions.py | 5 +++-- openpype/pipeline/create/context.py | 4 ++-- openpype/pipeline/create/creator_plugins.py | 8 ++++---- openpype/pipeline/workfile/workfile_template_builder.py | 3 ++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index efd38761c8..a2fe8314b2 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -57,8 +57,8 @@ def get_default_values(attribute_definitions): """Receive default values for attribute definitions. Args: - attribute_definitions (List[AbstractAttrDef]): Attribute definitions for - which default values should be collected. + attribute_definitions (List[AbstractAttrDef]): Attribute definitions + for which default values should be collected. Returns: Dict[str, Any]: Default values for passet attribute definitions. @@ -531,6 +531,7 @@ class EnumDef(AbstractAttrDef): return output + class BoolDef(AbstractAttrDef): """Boolean representation. diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9d42764aef..2b4c8a05ca 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -867,11 +867,11 @@ class CreatedInstance: @property def creator_attribute_defs(self): """Attribute defintions defined by creator plugin. - + Returns: List[AbstractAttrDef]: Attribute defitions. """ - + return self.creator_attributes.attr_defs @property diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8ac8959c76..1f92056b23 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -425,8 +425,8 @@ class BaseCreator: keys/values when plugin attributes change. Returns: - List[AbstractAttrDef]: Attribute definitions that can be tweaked for - created instance. + List[AbstractAttrDef]: Attribute definitions that can be tweaked + for created instance. """ return self.instance_attr_defs @@ -563,8 +563,8 @@ class Creator(BaseCreator): updating keys/values when plugin attributes change. Returns: - List[AbstractAttrDef]: Attribute definitions that can be tweaked for - created instance. + List[AbstractAttrDef]: Attribute definitions that can be tweaked + for created instance. """ return self.pre_create_attr_defs diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 051eb444c6..119e4aaeb7 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -842,7 +842,8 @@ class PlaceholderPlugin(object): """Placeholder options for data showed. Returns: - List[AbstractAttrDef]: Attribute definitions of placeholder options. + List[AbstractAttrDef]: Attribute definitions of + placeholder options. """ return [] From 6246bac7742aa18f852704588ba37d228f0a1413 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:29:21 +0100 Subject: [PATCH 100/356] renamed 'reset_avalon_context' to 'reset_current_context' --- openpype/pipeline/create/context.py | 6 +++--- openpype/tools/publisher/control.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9c468ae8fc..29bc32b658 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1138,7 +1138,7 @@ class CreateContext: self.reset_preparation() - self.reset_avalon_context() + self.reset_current_context() self.reset_plugins(discover_publish_plugins) self.reset_context_data() @@ -1185,8 +1185,8 @@ class CreateContext: self._collection_shared_data = None self.refresh_thumbnails() - def reset_avalon_context(self): - """Give ability to reset avalon context. + def reset_current_context(self): + """Refresh current context. Reset is based on optional host implementation of `get_current_context` function or using `legacy_io.Session`. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 50a814de5c..c11d7c53d3 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1756,7 +1756,7 @@ class PublisherController(BasePublisherController): self._create_context.reset_preparation() # Reset avalon context - self._create_context.reset_avalon_context() + self._create_context.reset_current_context() self._asset_docs_cache.reset() From d1b41ebac0b7bbac4a1404ca0233d2a7d92e6230 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:30:24 +0100 Subject: [PATCH 101/356] AvalonMongoDB is not needed for CreateContext or Controller --- openpype/pipeline/create/context.py | 38 ++++++++++++++--------------- openpype/tools/publisher/control.py | 5 ++-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 29bc32b658..e421a76b6e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1003,8 +1003,6 @@ class CreateContext: Args: host(ModuleType): Host implementation which handles implementation and global metadata. - dbcon(AvalonMongoDB): Connection to mongo with context (at least - project). headless(bool): Context is created out of UI (Current not used). reset(bool): Reset context on initialization. discover_publish_plugins(bool): Discover publish plugins during reset @@ -1012,16 +1010,8 @@ class CreateContext: """ def __init__( - self, host, dbcon=None, headless=False, reset=True, - discover_publish_plugins=True + self, host, headless=False, reset=True, discover_publish_plugins=True ): - # Create conncetion if is not passed - if dbcon is None: - session = session_data_from_environment(True) - dbcon = AvalonMongoDB(session) - dbcon.install() - - self.dbcon = dbcon self.host = host # Prepare attribute for logger (Created on demand in `log` property) @@ -1045,6 +1035,10 @@ class CreateContext: " Missing methods: {}" ).format(joined_methods)) + self._current_project_name = None + self._current_asset_name = None + self._current_task_name = None + self._host_is_valid = host_is_valid # Currently unused variable self.headless = headless @@ -1119,9 +1113,16 @@ class CreateContext: def host_name(self): return os.environ["AVALON_APP"] - @property - def project_name(self): - return self.dbcon.active_project() + def get_current_project_name(self): + return self._current_project_name + + def get_current_asset_name(self): + return self._current_asset_name + + def get_current_task_name(self): + return self._current_task_name + + project_name = property(get_current_project_name) @property def log(self): @@ -1210,12 +1211,9 @@ class CreateContext: if not task_name: task_name = legacy_io.Session.get("AVALON_TASK") - if project_name: - self.dbcon.Session["AVALON_PROJECT"] = project_name - if asset_name: - self.dbcon.Session["AVALON_ASSET"] = asset_name - if task_name: - self.dbcon.Session["AVALON_TASK"] = task_name + self._current_project_name = project_name + self._current_asset_name = asset_name + self._current_task_name = task_name def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index c11d7c53d3..83c2dd4b1c 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1589,20 +1589,19 @@ class PublisherController(BasePublisherController): Handle both creation and publishing parts. Args: - dbcon (AvalonMongoDB): Connection to mongo with context. headless (bool): Headless publishing. ATM not implemented or used. """ _log = None - def __init__(self, dbcon=None, headless=False): + def __init__(self, headless=False): super(PublisherController, self).__init__() self._host = registered_host() self._headless = headless self._create_context = CreateContext( - self._host, dbcon, headless=headless, reset=False + self._host, headless=headless, reset=False ) self._publish_plugins_proxy = None From 0a900f8ae1e4ac3b1ba48aca017be03a7f743e89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:30:59 +0100 Subject: [PATCH 102/356] use 'name' attribute of host implementation if is available --- openpype/pipeline/create/context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index e421a76b6e..867809a4c1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1111,6 +1111,8 @@ class CreateContext: @property def host_name(self): + if hasattr(self.host, "name"): + return self.host.name return os.environ["AVALON_APP"] def get_current_project_name(self): From 8678f4e2fa36bab16aa8033bf518dd0231fa885c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:31:27 +0100 Subject: [PATCH 103/356] 'create' returns output from creator --- openpype/pipeline/create/context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 867809a4c1..413580526e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1429,6 +1429,7 @@ class CreateContext: failed = False add_traceback = False exc_info = None + result = None try: # Fake CreatorError (Could be maybe specific exception?) if creator is None: @@ -1436,7 +1437,7 @@ class CreateContext: "Creator {} was not found".format(identifier) ) - creator.create(*args, **kwargs) + result = creator.create(*args, **kwargs) except CreatorError: failed = True @@ -1458,6 +1459,7 @@ class CreateContext: identifier, label, exc_info, add_traceback ) ]) + return result def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. From d09b7812616bfaee29c4089e2ece893ff6bd3faa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:32:53 +0100 Subject: [PATCH 104/356] implemented helper function 'create_with_context' to trigger standartized creation --- openpype/pipeline/create/context.py | 64 ++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 413580526e..655af1b8ed 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -8,17 +8,13 @@ import inspect from uuid import uuid4 from contextlib import contextmanager -from openpype.client import get_assets +from openpype.client import get_assets, get_asset_by_name from openpype.settings import ( get_system_settings, get_project_settings ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io -from openpype.pipeline.mongodb import ( - AvalonMongoDB, - session_data_from_environment, -) from .creator_plugins import ( Creator, @@ -1461,6 +1457,64 @@ class CreateContext: ]) return result + def create_with_context( + self, + creator_identifier, + variant=None, + asset_doc=None, + task_name=None, + pre_create_data=None + ): + """Trigger create of plugins with standartized + + Args: + creator_identifier (str): + asset_doc (Dict[str, Any]): + task_name (str): Name of task to which is context related. + variant (str): Variant used for subset name. + pre_create_data (Dict[str, Any]): Pre-create attribute values. + + Returns: + Any: Output of triggered creator's 'create' method. + + Raises: + CreatorsCreateFailed: When creation fails. + """ + + if pre_create_data is None: + pre_create_data = {} + + project_name = self.project_name + if asset_doc is None: + asset_name = self.get_current_asset_name() + asset_doc = get_asset_by_name(project_name, asset_name) + task_name = self.get_current_task_name() + + creator = self.creators.get(creator_identifier) + family = None + subset_name = None + if creator is not None: + family = creator.family + subset_name = creator.get_subset_name( + variant, + task_name, + asset_doc, + project_name, + self.host_name + ) + instance_data = { + "asset": asset_doc["name"], + "task": task_name, + "variant": variant, + "family": family + } + return self.raw_create( + creator_identifier, + subset_name, + instance_data, + pre_create_data + ) + def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. From 430fe6aed42d8c08f57c7b91dd2eb9185a3d1fde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:33:33 +0100 Subject: [PATCH 105/356] renamed 'create'->'raw_create' and 'create_with_context'->'create' --- openpype/pipeline/create/context.py | 9 ++++++--- openpype/tools/publisher/control.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 655af1b8ed..a9f8ae3ce1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1407,8 +1407,8 @@ class CreateContext: with self.bulk_instances_collection(): self._bulk_instances_to_process.append(instance) - def create(self, identifier, *args, **kwargs): - """Wrapper for creators to trigger created. + def raw_create(self, identifier, *args, **kwargs): + """Wrapper for creators to trigger 'create' method. Different types of creators may expect different arguments thus the hints for args are blind. @@ -1417,6 +1417,9 @@ class CreateContext: identifier (str): Creator's identifier. *args (Tuple[Any]): Arguments for create method. **kwargs (Dict[Any, Any]): Keyword argument for create method. + + Raises: + CreatorsCreateFailed: When creation fails. """ error_message = "Failed to run Creator with identifier \"{}\". {}" @@ -1457,7 +1460,7 @@ class CreateContext: ]) return result - def create_with_context( + def create( self, creator_identifier, variant=None, diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 83c2dd4b1c..670c22a43e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2017,7 +2017,7 @@ class PublisherController(BasePublisherController): success = True try: - self._create_context.create( + self._create_context.raw_create( creator_identifier, subset_name, instance_data, options ) except CreatorsOperationFailed as exc: From 498c8564f71c4d85ad88a101d6de7ae11357bb7d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 28 Jan 2023 00:40:08 +0100 Subject: [PATCH 106/356] swapped argments order in docstring --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index a9f8ae3ce1..702731f8b2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1472,9 +1472,9 @@ class CreateContext: Args: creator_identifier (str): + variant (str): Variant used for subset name. asset_doc (Dict[str, Any]): task_name (str): Name of task to which is context related. - variant (str): Variant used for subset name. pre_create_data (Dict[str, Any]): Pre-create attribute values. Returns: From 1af1909e0e936b62fa1a029c4921a43744ff1633 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 28 Jan 2023 16:09:23 +0000 Subject: [PATCH 107/356] Revert clean up --- openpype/hosts/maya/api/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 71d890f46b..358996fc7f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -690,6 +690,11 @@ def get_current_renderlayer(): return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) +def get_renderer(layer): + with renderlayer(layer): + return cmds.getAttr("defaultRenderGlobals.currentRenderer") + + @contextlib.contextmanager def no_undo(flush=False): """Disable the undo queue during the context From 0f4ca3643eeca0568705570d6c2bf06a89ede319 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 28 Jan 2023 16:09:32 +0000 Subject: [PATCH 108/356] Remove redundant variable --- openpype/hosts/maya/api/menu.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index 67109e9958..791475173f 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -50,7 +50,6 @@ def install(): parent="MayaWindow" ) - renderer = cmds.getAttr('defaultRenderGlobals.currentRenderer').lower() # Create context menu context_label = "{}, {}".format( legacy_io.Session["AVALON_ASSET"], From 14c23246fbda7a6a4d571c9bbff93252f52466e9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 28 Jan 2023 16:14:10 +0000 Subject: [PATCH 109/356] Resolve plugin --- openpype/hosts/maya/plugins/publish/submit_maya_muster.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py index 8ae3e5124b..1a6463fb9d 100644 --- a/openpype/hosts/maya/plugins/publish/submit_maya_muster.py +++ b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py @@ -52,11 +52,6 @@ def _get_script(): return module_path -def get_renderer(layer): - with lib.renderlayer(layer): - return cmds.getAttr("defaultRenderGlobals.currentRenderer") - - def get_renderer_variables(renderlayer=None): """Retrieve the extension which has been set in the VRay settings @@ -71,7 +66,7 @@ def get_renderer_variables(renderlayer=None): dict """ - renderer = get_renderer(renderlayer or lib.get_current_renderlayer()) + renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) padding = cmds.getAttr("{}.{}".format(render_attrs["node"], From 40712089d94ce22a5d34981584dc7db8beed9554 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:48:57 +0100 Subject: [PATCH 110/356] Validate creator and asset doc --- openpype/pipeline/create/context.py | 33 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 702731f8b2..35024b5af8 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1484,27 +1484,32 @@ class CreateContext: CreatorsCreateFailed: When creation fails. """ - if pre_create_data is None: - pre_create_data = {} + creator = self.creators.get(creator_identifier) + if creator is None: + raise CreatorError( + "Creator {} was not found".format(creator_identifier) + ) project_name = self.project_name if asset_doc is None: asset_name = self.get_current_asset_name() asset_doc = get_asset_by_name(project_name, asset_name) task_name = self.get_current_task_name() + if asset_doc is None: + raise CreatorError( + "Asset with name {} was not found".format(asset_name) + ) - creator = self.creators.get(creator_identifier) - family = None - subset_name = None - if creator is not None: - family = creator.family - subset_name = creator.get_subset_name( - variant, - task_name, - asset_doc, - project_name, - self.host_name - ) + if pre_create_data is None: + pre_create_data = {} + + subset_name = creator.get_subset_name( + variant, + task_name, + asset_doc, + project_name, + self.host_name + ) instance_data = { "asset": asset_doc["name"], "task": task_name, From 75bffb4daeacc8a31dc584edf3b76b3989a0607d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:49:17 +0100 Subject: [PATCH 111/356] removed unnecessary family from instance data --- openpype/pipeline/create/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 35024b5af8..dbbde9218f 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1513,8 +1513,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, - "variant": variant, - "family": family + "variant": variant } return self.raw_create( creator_identifier, From daa961d24976a4b9a1d5f51320017fa346bbfc84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 10:49:27 +0100 Subject: [PATCH 112/356] variant is required argument --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index dbbde9218f..b10bbc17de 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1463,7 +1463,7 @@ class CreateContext: def create( self, creator_identifier, - variant=None, + variant, asset_doc=None, task_name=None, pre_create_data=None From 4d990e6f87964cb4f5fb2c61cebfcaea47ac3151 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 11:03:51 +0100 Subject: [PATCH 113/356] Updated docstrings --- openpype/pipeline/create/context.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index b10bbc17de..190d542724 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1191,7 +1191,15 @@ class CreateContext: function or using `legacy_io.Session`. Some hosts have ability to change context file without using workfiles - tool but that change is not propagated to + tool but that change is not propagated to 'legacy_io.Session' + nor 'os.environ'. + + Todos: + UI: Current context should be also checked on save - compare + initial values vs. current values. + Related to UI checks: Current workfile can be also considered + as current context information as that's where the metadata + are stored. We should store the workfile (if is available) too. """ project_name = asset_name = task_name = None @@ -1468,12 +1476,19 @@ class CreateContext: task_name=None, pre_create_data=None ): - """Trigger create of plugins with standartized + """Trigger create of plugins with standartized arguments. + + Arguments 'asset_doc' and 'task_name' use current context as default + values. If only 'task_name' is provided it will be overriden by + task name from current context. If 'task_name' is not provided + when 'asset_doc' is, it is considered that task name is not specified, + which can lead to error if subset name template requires task name. Args: - creator_identifier (str): + creator_identifier (str): Identifier of creator plugin. variant (str): Variant used for subset name. - asset_doc (Dict[str, Any]): + asset_doc (Dict[str, Any]): Asset document which define context of + creation (possible context of created instance/s). task_name (str): Name of task to which is context related. pre_create_data (Dict[str, Any]): Pre-create attribute values. @@ -1481,6 +1496,7 @@ class CreateContext: Any: Output of triggered creator's 'create' method. Raises: + CreatorError: If creator was not found or asset is empty. CreatorsCreateFailed: When creation fails. """ From 1f08a2734339ae7c40424df778d7402807beed10 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 10:15:04 +0000 Subject: [PATCH 114/356] Workaround for motion blur --- .../plugins/publish/extract_workfile_xgen.py | 147 +++++++++++++----- .../maya/plugins/publish/extract_xgen.py | 62 ++++---- 2 files changed, 146 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index d37a03d1f6..5847563e5b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -1,11 +1,14 @@ import os import shutil +import copy from maya import cmds import pyblish.api from openpype.hosts.maya.api import current_file +from openpype.hosts.maya.api.lib import extract_alembic from openpype.pipeline import publish +from openpype.lib import StringTemplate class ExtractWorkfileXgen(publish.Extractor): @@ -18,9 +21,22 @@ class ExtractWorkfileXgen(publish.Extractor): hosts = ["maya"] def process(self, instance): + transfers = [] + + # Validate there is any palettes in the scene. + if not cmds.ls(type="xgmPalette"): + self.log.debug( + "No collections found in the scene. Abort Xgen extraction." + ) + return + else: + import xgenm + # Validate to extract only when we are publishing a renderlayer as # well. renderlayer = False + start_frame = None + end_frame = None for i in instance.context: is_renderlayer = ( "renderlayer" in i.data.get("families", []) or @@ -28,6 +44,17 @@ class ExtractWorkfileXgen(publish.Extractor): ) if is_renderlayer and i.data["publish"]: renderlayer = True + + if start_frame is None: + start_frame = i.data["frameStart"] + if end_frame is None: + end_frame = i.data["frameEnd"] + + if i.data["frameStart"] < start_frame: + start_frame = i.data["frameStart"] + if i.data["frameEnd"] > end_frame: + end_frame = i.data["frameEnd"] + break if not renderlayer: @@ -37,6 +64,51 @@ class ExtractWorkfileXgen(publish.Extractor): ) return + # We decrement start frame and increment end frame so motion blur will + # render correctly. + start_frame -= 1 + end_frame += 1 + + # Extract patches alembic. + basename, _ = os.path.splitext(current_file()) + dirname = os.path.dirname(current_file()) + kwargs = {"attrPrefix": ["xgen"], "stripNamespaces": True} + alembic_files = [] + for palette in cmds.ls(type="xgmPalette"): + patch_names = [] + for description in xgenm.descriptions(palette): + for name in xgenm.boundGeometry(palette, description): + patch_names.append(name) + + alembic_file = os.path.join( + dirname, + "{}__{}.abc".format(basename, palette.replace(":", "__ns__")) + ) + extract_alembic( + alembic_file, + root=patch_names, + selection=False, + startFrame=float(start_frame), + endFrame=float(end_frame), + verbose=True, + **kwargs + ) + alembic_files.append(alembic_file) + + template_data = copy.deepcopy(instance.data["anatomyData"]) + published_maya_path = StringTemplate( + instance.context.data["anatomy"].templates["publish"]["file"] + ).format(template_data) + published_basename, _ = os.path.splitext(published_maya_path) + + for source in alembic_files: + destination = os.path.join( + os.path.dirname(instance.data["resourcesDir"]), + os.path.basename(source.replace(basename, published_basename)) + ) + transfers.append((source, destination)) + + # Validate that we are using the published workfile. deadline_settings = instance.context.get("deadline") if deadline_settings: publish_settings = deadline_settings["publish"] @@ -76,8 +148,9 @@ class ExtractWorkfileXgen(publish.Extractor): with open(destination, "r") as f: for line in [line.rstrip() for line in f]: if line.startswith("\txgProjectPath"): + path = os.path.dirname(instance.data["resourcesDir"]) line = "\txgProjectPath\t\t{}/".format( - instance.data["resourcesDir"].replace("\\", "/") + path.replace("\\", "/") ) lines.append(line) @@ -88,31 +161,30 @@ class ExtractWorkfileXgen(publish.Extractor): sources.append(destination) # Add resource files to workfile instance. - transfers = [] for source in sources: basename = os.path.basename(source) - destination = os.path.join(instance.data["resourcesDir"], basename) + destination = os.path.join( + os.path.dirname(instance.data["resourcesDir"]), basename + ) transfers.append((source, destination)) - import xgenm + destination_dir = os.path.join( + instance.data["resourcesDir"], "collections" + ) for palette in cmds.ls(type="xgmPalette"): - relative_data_path = xgenm.getAttr( - "xgDataPath", palette.replace("|", "") - ).split(os.pathsep)[0] - absolute_data_path = relative_data_path.replace( - "${PROJECT}", - xgenm.getAttr("xgProjectPath", palette.replace("|", "")) - ) - - for root, _, files in os.walk(absolute_data_path): - for file in files: - source = os.path.join(root, file).replace("\\", "/") - destination = os.path.join( - instance.data["resourcesDir"], - relative_data_path.replace("${PROJECT}", ""), - source.replace(absolute_data_path, "")[1:] - ) - transfers.append((source, destination.replace("\\", "/"))) + project_path = xgenm.getAttr("xgProjectPath", palette) + data_path = xgenm.getAttr("xgDataPath", palette) + data_path = data_path.replace("${PROJECT}", project_path) + for path in data_path.split(os.pathsep): + for root, _, files in os.walk(path): + for f in files: + source = os.path.join(root, f) + destination = "{}/{}{}".format( + destination_dir, + palette.replace(":", "__ns__"), + source.replace(path, "") + ) + transfers.append((source, destination)) for source, destination in transfers: self.log.debug("Transfer: {} > {}".format(source, destination)) @@ -120,21 +192,26 @@ class ExtractWorkfileXgen(publish.Extractor): instance.data["transfers"] = transfers # Set palette attributes in preparation for workfile publish. - attrs = ["xgFileName", "xgBaseFile"] + attrs = {"xgFileName": None, "xgBaseFile": ""} data = {} for palette in cmds.ls(type="xgmPalette"): - for attr in attrs: - value = cmds.getAttr(palette + "." + attr) - if value: - new_value = "resources/{}".format(value) - node_attr = "{}.{}".format(palette, attr) - self.log.info( - "Setting \"{}\" on \"{}\"".format(new_value, node_attr) - ) - cmds.setAttr(node_attr, new_value, type="string") - try: - data[palette][attr] = value - except KeyError: - data[palette] = {attr: value} + attrs["xgFileName"] = "resources/{}.xgen".format( + palette.replace(":", "__ns__") + ) + for attr, value in attrs.items(): + node_attr = palette + "." + attr + + old_value = cmds.getAttr(node_attr) + try: + data[palette][attr] = old_value + except KeyError: + data[palette] = {attr: old_value} + + cmds.setAttr(node_attr, value, type="string") + self.log.info( + "Setting \"{}\" on \"{}\"".format(value, node_attr) + ) + + cmds.setAttr(palette + "." + "xgExportAsDelta", False) instance.data["xgenAttributes"] = data diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 80b62275cd..fd85cadcac 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -1,5 +1,6 @@ import os import copy +import tempfile from maya import cmds import xgenm @@ -16,6 +17,7 @@ class ExtractXgenCache(publish.Extractor): - Duplicate nodes used for patches. - Export palette and import onto duplicate nodes. - Export/Publish duplicate nodes and palette. + - Export duplicate palette to .xgen file and add to publish. - Publish all xgen files as resources. """ @@ -32,29 +34,6 @@ class ExtractXgenCache(publish.Extractor): maya_filename = "{}.{}".format(instance.data["name"], self.scene_type) maya_filepath = os.path.join(staging_dir, maya_filename) - # Get published xgen file name. - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({"ext": "xgen"}) - templates = instance.context.data["anatomy"].templates["publish"] - xgen_filename = StringTemplate(templates["file"]).format(template_data) - name = instance.data["xgmPalette"].replace(":", "__").replace("|", "") - xgen_filename = xgen_filename.replace(".xgen", "__" + name + ".xgen") - - # Export xgen palette files. - xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") - xgenm.exportPalette( - instance.data["xgmPalette"].replace("|", ""), xgen_path - ) - self.log.info("Extracted to {}".format(xgen_path)) - - representation = { - "name": name, - "ext": "xgen", - "files": xgen_filename, - "stagingDir": staging_dir, - } - instance.data["representations"].append(representation) - # Collect nodes to export. duplicate_nodes = [] for node, connections in instance.data["xgenConnections"].items(): @@ -74,17 +53,43 @@ class ExtractXgenCache(publish.Extractor): duplicate_nodes.append(duplicate_transform) + # Export temp xgen palette files. + temp_xgen_path = os.path.join( + tempfile.gettempdir(), "temp.xgen" + ).replace("\\", "/") + xgenm.exportPalette( + instance.data["xgmPalette"].replace("|", ""), temp_xgen_path + ) + self.log.info("Extracted to {}".format(temp_xgen_path)) + # Import xgen onto the duplicate. with maintained_selection(): cmds.select(duplicate_nodes) - palette = xgenm.importPalette(xgen_path, []) + palette = xgenm.importPalette(temp_xgen_path, []) - attribute_data = { - "{}.xgFileName".format(palette): xgen_filename + # Get published xgen file name. + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "xgen"}) + templates = instance.context.data["anatomy"].templates["publish"] + xgen_filename = StringTemplate(templates["file"]).format(template_data) + + # Export duplicated palette. + xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") + xgenm.exportPalette(palette, xgen_path) + + representation = { + "name": "xgen", + "ext": "xgen", + "files": xgen_filename, + "stagingDir": staging_dir, } + instance.data["representations"].append(representation) # Export Maya file. type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" + attribute_data = { + "{}.xgFileName".format(palette): xgen_filename + } with attribute_values(attribute_data): with maintained_selection(): cmds.select(duplicate_nodes + [palette]) @@ -106,12 +111,13 @@ class ExtractXgenCache(publish.Extractor): "name": self.scene_type, "ext": self.scene_type, "files": maya_filename, - "stagingDir": staging_dir, - "data": {"xgenName": palette} + "stagingDir": staging_dir } instance.data["representations"].append(representation) + # Clean up. cmds.delete(duplicate_nodes + [palette]) + os.remove(temp_xgen_path) # Collect all files under palette root as resources. data_path = xgenm.getAttr( From c908bd2abaeb2a44b8beb88a4ed30aa1f2dad56c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 10:15:27 +0000 Subject: [PATCH 115/356] Workaround for motion blur --- openpype/hosts/maya/plugins/load/load_xgen.py | 76 +++++++------------ 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 8249c9092e..0f2e13dd79 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -1,5 +1,4 @@ import os -import shutil import maya.cmds as cmds import xgenm @@ -12,7 +11,6 @@ from openpype.hosts.maya.api.lib import ( ) from openpype.hosts.maya.api import current_file from openpype.hosts.maya.api.plugin import get_reference_node -from openpype.pipeline import get_representation_path class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -25,55 +23,19 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" - def setup_xgen_palette_file(self, maya_filepath, namespace, name): - # Setup xgen palette file. - project_path = os.path.dirname(current_file()) - - # Copy the xgen palette file from published version. - _, maya_extension = os.path.splitext(maya_filepath) - source = maya_filepath.replace(maya_extension, ".xgen") - xgen_file = os.path.join( - project_path, - "{basename}__{namespace}__{name}.xgen".format( - basename=os.path.splitext(os.path.basename(current_file()))[0], - namespace=namespace, - name=name - ) - ).replace("\\", "/") - self.log.info("Copying {} to {}".format(source, xgen_file)) - shutil.copy(source, xgen_file) - - # Modify xgDataPath and xgProjectPath to have current workspace first - # and published version directory second. This ensure that any newly - # created xgen files are created in the current workspace. - resources_path = os.path.join(os.path.dirname(source), "resources") + def write_xgen_file(self, data, xgen_file): lines = [] with open(xgen_file, "r") as f: for line in [line.rstrip() for line in f]: - if line.startswith("\txgDataPath"): - data_path = line.split("\t")[-1] - line = "\txgDataPath\t\t{}{}{}".format( - data_path, - os.pathsep, - data_path.replace( - "${PROJECT}xgen", resources_path.replace("\\", "/") - ) - ) - - if line.startswith("\txgProjectPath"): - line = "\txgProjectPath\t\t{}/".format( - project_path.replace("\\", "/") - ) + for key, value in data.items(): + if line.startswith("\t{}".format(key)): + line = "\t{}\t\t{}".format(key, value) lines.append(line) with open(xgen_file, "w") as f: f.write("\n".join(lines)) - xgd_file = xgen_file.replace(".xgen", ".xgd") - - return xgen_file, xgd_file - def process_reference(self, context, name, namespace, options): # Validate workfile has a path. if current_file() is None: @@ -89,11 +51,6 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.fname, context["project"]["name"] ) - name = context["representation"]["data"]["xgenName"] - xgen_file, xgd_file = self.setup_xgen_palette_file( - maya_filepath, namespace, name - ) - # Reference xgen. Xgen does not like being referenced in under a group. new_nodes = [] @@ -106,9 +63,32 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True ) - xgen_palette = cmds.ls(nodes, type="xgmPalette", long=True)[0] + xgen_palette = cmds.ls( + nodes, type="xgmPalette", long=True + )[0].replace("|", "") + + _, maya_extension = os.path.splitext(current_file()) + xgen_file = current_file().replace( + maya_extension, + "__{}.xgen".format(xgen_palette.replace(":", "__")) + ) + xgd_file = xgen_file.replace(".xgen", ".xgd") self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) + # Change the cache and disk values of xgDataPath and xgProjectPath + # to ensure paths are setup correctly. + project_path = os.path.dirname(current_file()).replace("\\", "/") + xgenm.setAttr("xgProjectPath", project_path, xgen_palette) + data_path = "${{PROJECT}}xgen/collections/{}{}{}".format( + xgen_palette.replace(":", "__ns__"), + os.pathsep, + xgenm.getAttr("xgDataPath", xgen_palette) + ) + xgenm.setAttr("xgDataPath", data_path, xgen_palette) + + data = {"xgProjectPath": project_path, "xgDataPath": data_path} + self.write_xgen_file(data, xgen_file) + # This create an expression attribute of float. If we did not add # any changes to collection, then Xgen does not create an xgd file # on save. This gives errors when launching the workfile again due From 0d527a477bfa3f6d66a4745b5424534bc575ecbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 30 Jan 2023 12:07:24 +0100 Subject: [PATCH 116/356] fix super call --- openpype/lib/attribute_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py index a2fe8314b2..b5cd15f41a 100644 --- a/openpype/lib/attribute_definitions.py +++ b/openpype/lib/attribute_definitions.py @@ -465,7 +465,7 @@ class EnumDef(AbstractAttrDef): return self.default def serialize(self): - data = super(TextDef, self).serialize() + data = super(EnumDef, self).serialize() data["items"] = copy.deepcopy(self.items) return data From c8fb00c9c81c9a60f0436dbcb43b546060cf6664 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:43:40 +0100 Subject: [PATCH 117/356] global: expanding staging dir maker abstraction so it supports `OPENPYPE_TEMP_DIR` with anatomy formatting keys --- openpype/pipeline/publish/lib.py | 55 ++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c76671fa39..5591acf57d 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -10,7 +10,11 @@ import six import pyblish.plugin import pyblish.api -from openpype.lib import Logger, filter_profiles +from openpype.lib import ( + Logger, + filter_profiles, + StringTemplate +) from openpype.settings import ( get_project_settings, get_system_settings, @@ -623,12 +627,51 @@ def get_instance_staging_dir(instance): Returns: str: Path to staging dir of instance. """ + staging_dir = instance.data.get('stagingDir', None) + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - staging_dir = instance.data.get("stagingDir") if not staging_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data["stagingDir"] = staging_dir + custom_temp_dir = None + if openpype_temp_dir: + if "{" in openpype_temp_dir: + anatomy = instance.context.data["anatomy"] + # get anatomy formating data + # so template formating is supported + anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) + anatomy_data["root"] = anatomy.roots + """Template path formating is supporting: + - optional key formating + - available tokens: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + """ + custom_temp_dir = StringTemplate.format_template( + openpype_temp_dir, anatomy_data) + custom_temp_dir = os.path.normpath(custom_temp_dir) + # create the dir in case it doesnt exists + os.makedirs(os.path.dirname(custom_temp_dir)) + elif os.path.exists(openpype_temp_dir): + custom_temp_dir = openpype_temp_dir + + + if custom_temp_dir: + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=custom_temp_dir + ) + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + instance.context.data["cleanupFullPaths"].append(staging_dir) return staging_dir From ef86f1451542a1c9a85e9472da9ced35f6b92d95 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:51:13 +0100 Subject: [PATCH 118/356] global: update docstrings at `get_instance_staging_dir` --- openpype/pipeline/publish/lib.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 5591acf57d..cb01d4633e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -613,8 +613,21 @@ def context_plugin_should_run(plugin, context): def get_instance_staging_dir(instance): """Unified way how staging dir is stored and created on instances. - First check if 'stagingDir' is already set in instance data. If there is - not create new in tempdir. + First check if 'stagingDir' is already set in instance data. + In case there already is new tempdir will not be created. + + It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp shared + repository per project or even per more granular context. Template formating + is supported also with optional keys. Folder is created in case it doesnt exists. + + Available anatomy formating keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app Note: Staging dir does not have to be necessarily in tempdir so be carefull @@ -641,7 +654,7 @@ def get_instance_staging_dir(instance): anatomy_data["root"] = anatomy.roots """Template path formating is supporting: - optional key formating - - available tokens: + - available keys: - root[work | ] - project[name | code] - asset From 43399a08c82393c8522b3b4e7f59f16be354dbe6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 12:52:11 +0100 Subject: [PATCH 119/356] flame: removing class override for staging dir creation it is already available in more expanded feature at parent class --- .../publish/extract_subset_resources.py | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index d5294d61c2..c6148162a6 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -548,30 +548,3 @@ class ExtractSubsetResources(publish.Extractor): "Path `{}` is containing more that one clip".format(path) ) return clips[0] - - def staging_dir(self, instance): - """Provide a temporary directory in which to store extracted files - - Upon calling this method the staging directory is stored inside - the instance.data['stagingDir'] - """ - staging_dir = instance.data.get('stagingDir', None) - openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - - if not staging_dir: - if openpype_temp_dir and os.path.exists(openpype_temp_dir): - staging_dir = os.path.normpath( - tempfile.mkdtemp( - prefix="pyblish_tmp_", - dir=openpype_temp_dir - ) - ) - else: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data['stagingDir'] = staging_dir - - instance.context.data["cleanupFullPaths"].append(staging_dir) - - return staging_dir From 4dc9fadc424222a3f99444aaea3df8a8fd701a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 30 Jan 2023 13:56:56 +0100 Subject: [PATCH 120/356] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cb01d4633e..33f23ddb97 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -640,7 +640,9 @@ def get_instance_staging_dir(instance): Returns: str: Path to staging dir of instance. """ - staging_dir = instance.data.get('stagingDir', None) + staging_dir = instance.data.get('stagingDir') + if staging_dir: + return staging_dir openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") if not staging_dir: From a9cc08120d7f6c47b65f22b64081c73d4d5e1804 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 30 Jan 2023 13:59:39 +0100 Subject: [PATCH 121/356] global: refactor code for better readibility --- openpype/pipeline/publish/lib.py | 94 +++++++++++++++++--------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 33f23ddb97..cc4304cebd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -616,11 +616,12 @@ def get_instance_staging_dir(instance): First check if 'stagingDir' is already set in instance data. In case there already is new tempdir will not be created. - It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp shared - repository per project or even per more granular context. Template formating - is supported also with optional keys. Folder is created in case it doesnt exists. + It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp + shared repository per project or even per more granular context. + Template formating is supported also with optional keys. Folder is + created in case it doesnt exists. - Available anatomy formating keys: + Available anatomy formatting keys: - root[work | ] - project[name | code] - asset @@ -643,50 +644,55 @@ def get_instance_staging_dir(instance): staging_dir = instance.data.get('stagingDir') if staging_dir: return staging_dir + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") - - if not staging_dir: - custom_temp_dir = None - if openpype_temp_dir: - if "{" in openpype_temp_dir: - anatomy = instance.context.data["anatomy"] - # get anatomy formating data - # so template formating is supported - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - anatomy_data["root"] = anatomy.roots - """Template path formating is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - """ - custom_temp_dir = StringTemplate.format_template( - openpype_temp_dir, anatomy_data) - custom_temp_dir = os.path.normpath(custom_temp_dir) - # create the dir in case it doesnt exists - os.makedirs(os.path.dirname(custom_temp_dir)) - elif os.path.exists(openpype_temp_dir): - custom_temp_dir = openpype_temp_dir - - - if custom_temp_dir: - staging_dir = os.path.normpath( - tempfile.mkdtemp( - prefix="pyblish_tmp_", - dir=custom_temp_dir - ) + custom_temp_dir = None + if openpype_temp_dir: + if "{" in openpype_temp_dir: + custom_temp_dir = _formated_staging_dir( + instance, openpype_temp_dir ) - else: - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") + elif os.path.exists(openpype_temp_dir): + custom_temp_dir = openpype_temp_dir + + + if custom_temp_dir: + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=custom_temp_dir ) - instance.data['stagingDir'] = staging_dir + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir instance.context.data["cleanupFullPaths"].append(staging_dir) return staging_dir + + +def _formated_staging_dir(instance, openpype_temp_dir): + anatomy = instance.context.data["anatomy"] + # get anatomy formating data + # so template formating is supported + anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) + anatomy_data["root"] = anatomy.roots + """Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + """ + result = StringTemplate.format_template(openpype_temp_dir, anatomy_data) + result = os.path.normpath(result) + # create the dir in case it doesnt exists + os.makedirs(os.path.dirname(result)) + return result From a5ca4f93b8cdf4102828d5fd91d563e808081440 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 30 Jan 2023 15:21:29 +0100 Subject: [PATCH 122/356] cancel recursivity removal --- .../workfile/workfile_template_builder.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index d262d6a771..fd5ac16579 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -521,6 +521,11 @@ class AbstractTemplateBuilder(object): if not level_limit: level_limit = 1000 + placeholder_by_scene_id = { + placeholder.scene_identifier: placeholder + for placeholder in placeholders + } + all_processed = len(placeholders) == 0 # Counter is checked at the ned of a loop so the loop happens at least # once. @@ -561,14 +566,24 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() - # Clear shared data before getting new placeholders - self.clear_shared_populate_data() + # self.clear_shared_populate_data() iter_counter += 1 if iter_counter >= level_limit: break all_processed = True + + collected_placeholders = self.get_placeholders() + for placeholder in collected_placeholders: + identifier = placeholder.scene_identifier + if identifier in placeholder_by_scene_id: + continue + + all_processed = False + placeholder_by_scene_id[identifier] = placeholder + placeholders.append(placeholder) + self.refresh() def _get_build_profiles(self): @@ -988,7 +1003,7 @@ class PlaceholderItem(object): return self._log def __repr__(self): - return "< {} {} >".format(self.__class__.__name__, self.name) + return "< {} {} >".format(self.__class__.__name__, self.data['family']) @property def order(self): From a2176420b7ed584d10b7bff37d729a0924000b39 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 17:20:39 +0000 Subject: [PATCH 123/356] Working extraction --- .../hosts/maya/plugins/publish/extract_xgen.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index fd85cadcac..9549aba76d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -6,7 +6,9 @@ from maya import cmds import xgenm from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.hosts.maya.api.lib import ( + maintained_selection, attribute_values, write_xgen_file +) from openpype.lib import StringTemplate @@ -77,6 +79,18 @@ class ExtractXgenCache(publish.Extractor): xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") xgenm.exportPalette(palette, xgen_path) + data = { + "xgDataPath": os.path.join( + instance.data["resourcesDir"], + "collections", + palette.replace(":", "__ns__") + ).replace("\\", "/"), + "xgProjectPath": os.path.dirname( + instance.data["resourcesDir"] + ).replace("\\", "/") + } + write_xgen_file(data, xgen_path) + representation = { "name": "xgen", "ext": "xgen", @@ -136,7 +150,7 @@ class ExtractXgenCache(publish.Extractor): destination = os.path.join( instance.data["resourcesDir"], "collections", - os.path.basename(data_path), + palette, source.replace(data_path, "")[1:] ) transfers.append((source, destination.replace("\\", "/"))) From 5a280a32bbec68d6a9284f005f062bf387a2a476 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 17:20:46 +0000 Subject: [PATCH 124/356] Working updating --- openpype/hosts/maya/plugins/load/load_xgen.py | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 0f2e13dd79..35f7c21c58 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -7,10 +7,13 @@ from Qt import QtWidgets import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.lib import ( - maintained_selection, get_container_members, attribute_values + maintained_selection, + get_container_members, + attribute_values, + write_xgen_file ) from openpype.hosts.maya.api import current_file -from openpype.hosts.maya.api.plugin import get_reference_node +from openpype.pipeline import get_representation_path class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -23,18 +26,14 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" - def write_xgen_file(self, data, xgen_file): - lines = [] - with open(xgen_file, "r") as f: - for line in [line.rstrip() for line in f]: - for key, value in data.items(): - if line.startswith("\t{}".format(key)): - line = "\t{}\t\t{}".format(key, value) - - lines.append(line) - - with open(xgen_file, "w") as f: - f.write("\n".join(lines)) + def get_xgen_xgd_paths(self, palette): + _, maya_extension = os.path.splitext(current_file()) + xgen_file = current_file().replace( + maya_extension, + "__{}.xgen".format(palette.replace("|", "").replace(":", "__")) + ) + xgd_file = xgen_file.replace(".xgen", ".xgd") + return xgen_file, xgd_file def process_reference(self, context, name, namespace, options): # Validate workfile has a path. @@ -67,12 +66,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): nodes, type="xgmPalette", long=True )[0].replace("|", "") - _, maya_extension = os.path.splitext(current_file()) - xgen_file = current_file().replace( - maya_extension, - "__{}.xgen".format(xgen_palette.replace(":", "__")) - ) - xgd_file = xgen_file.replace(".xgen", ".xgd") + xgen_file, xgd_file = self.get_xgen_xgd_paths(xgen_palette) self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) # Change the cache and disk values of xgDataPath and xgProjectPath @@ -87,7 +81,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): xgenm.setAttr("xgDataPath", data_path, xgen_palette) data = {"xgProjectPath": project_path, "xgDataPath": data_path} - self.write_xgen_file(data, xgen_file) + write_xgen_file(data, xgen_file) # This create an expression attribute of float. If we did not add # any changes to collection, then Xgen does not create an xgd file @@ -138,21 +132,35 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): container_node = container["objectName"] members = get_container_members(container_node) - xgen_palette = cmds.ls(members, type="xgmPalette", long=True)[0] - reference_node = get_reference_node(members, self.log) - namespace = cmds.referenceQuery(reference_node, namespace=True)[1:] - - xgen_file, xgd_file = self.setup_xgen_palette_file( - get_representation_path(representation), - namespace, - representation["data"]["xgenName"] - ) + xgen_palette = cmds.ls( + members, type="xgmPalette", long=True + )[0].replace("|", "") + xgen_file, xgd_file = self.get_xgen_xgd_paths(xgen_palette) # Export current changes to apply later. xgenm.createDelta(xgen_palette.replace("|", ""), xgd_file) self.set_palette_attributes(xgen_palette, xgen_file, xgd_file) + maya_file = get_representation_path(representation) + _, extension = os.path.splitext(maya_file) + new_xgen_file = maya_file.replace(extension, ".xgen") + data_path = "" + with open(new_xgen_file, "r") as f: + for line in [line.rstrip() for line in f]: + if line.startswith("\txgDataPath"): + data_path = line.split("\t")[-1] + break + + project_path = os.path.dirname(current_file()).replace("\\", "/") + data_path = "${{PROJECT}}xgen/collections/{}{}{}".format( + xgen_palette.replace(":", "__ns__"), + os.pathsep, + data_path + ) + data = {"xgProjectPath": project_path, "xgDataPath": data_path} + write_xgen_file(data, xgen_file) + attribute_data = { "{}.xgFileName".format(xgen_palette): os.path.basename(xgen_file), "{}.xgBaseFile".format(xgen_palette): "", From 22ddb58cca40a320af68688e073cf98bb9e73fbc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 17:20:58 +0000 Subject: [PATCH 125/356] Refactor to lib --- openpype/hosts/maya/api/lib.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index dd5da275e8..65f39270f5 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3446,3 +3446,17 @@ def iter_visible_nodes_in_range(nodes, start, end): def get_attribute_input(attr): connections = cmds.listConnections(attr, plugs=True, destination=False) return connections[0] if connections else None + + +def write_xgen_file(data, filepath): + lines = [] + with open(filepath, "r") as f: + for line in [line.rstrip() for line in f]: + for key, value in data.items(): + if line.startswith("\t{}".format(key)): + line = "\t{}\t\t{}".format(key, value) + + lines.append(line) + + with open(filepath, "w") as f: + f.write("\n".join(lines)) From 2e9f60693b2da0b824ae8ad5283f445673e0c29a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 18:05:52 +0000 Subject: [PATCH 126/356] Fix Edge case of loading xgen while existing xgen in cache. --- openpype/hosts/maya/plugins/load/load_xgen.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 35f7c21c58..d0ed3e05b3 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -87,9 +87,11 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # any changes to collection, then Xgen does not create an xgd file # on save. This gives errors when launching the workfile again due # to trying to find the xgd file. - xgenm.addCustomAttr( - "custom_float_ignore", xgen_palette.replace("|", "") - ) + name = "custom_float_ignore" + if name not in xgenm.customAttrs(xgen_palette): + xgenm.addCustomAttr( + "custom_float_ignore", xgen_palette + ) shapes = cmds.ls(nodes, shapes=True, long=True) From 8ec87cba01a8c0d91c67843d6c575cf647cd18e3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 18:12:53 +0000 Subject: [PATCH 127/356] Fix Resetting Xgen attributes after incremental save. --- .../maya/plugins/publish/reset_xgen_attributes.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py index 0f763613c9..d3408f2c76 100644 --- a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -7,8 +7,8 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): """Reset Xgen attributes.""" label = "Reset Xgen Attributes." - # Offset to run after global integrator. - order = pyblish.api.IntegratorOrder + 1.0 + # Offset to run after workfile increment plugin. + order = pyblish.api.IntegratorOrder + 10.0 families = ["workfile"] def process(self, instance): @@ -19,3 +19,9 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): "Setting \"{}\" on \"{}\"".format(value, node_attr) ) cmds.setAttr(node_attr, value, type="string") + + cmds.setAttr(palette + "." + "xgExportAsDelta", True) + + if instance.data.get("xgenAttributes", {}): + self.log.info("Saving changes.") + cmds.file(save=True) From f1e8803f590e90e0cf38e5601cad97b12a5f97ae Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 30 Jan 2023 18:18:37 +0000 Subject: [PATCH 128/356] Update docs --- website/docs/artist_hosts_maya_xgen.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index fc75959f2b..191826f49c 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -8,7 +8,7 @@ sidebar_label: Xgen ### Settings -Go to project settings > Maya > enable "Open Workfile Post Initialization"; +Go to project settings > `Maya` > enable `Open Workfile Post Initialization`; `project_settings/maya/open_workfile_post_initialization` @@ -27,6 +27,12 @@ Importing XGen Collections... # Error: XGen: Failed to import collection from file P:/PROJECTS/OP01_CG_demo/shots/sh040/work/Lighting/cg_ball_xgenMain_v035__ball_rigMain_01___collection.xgen # ``` +Go to project settings > `Deadline` > `Publish plugins` > `Maya Submit to Deadline` > disable `Use Published scene`; + +`project_settings/deadline/publish/MayaSubmitDeadline/use_published` + +This is due to temporary workaround while fixing rendering with published scenes. + ## Create Create an Xgen instance to publish. This needs to contain only **one Xgen collection**. From b8c7f067c31d7dd1c7a18de65072e4fbdb1375a1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 Jan 2023 07:56:20 +0100 Subject: [PATCH 129/356] global: host settings should be optional --- openpype/pipeline/colorspace.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index e1ffe9d333..a13e6df811 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -438,7 +438,8 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): # get file rules from global and host_name frules_global = imageio_global["file_rules"] - frules_host = imageio_host["file_rules"] + # host is optional, some might not have any settings + frules_host = imageio_host.get("file_rules", {}) # compile file rules dictionary file_rules = {} @@ -455,7 +456,7 @@ def _get_imageio_settings(project_settings, host_name): Args: project_settings (dict): project settings. - Defaults to None. + Defaults to None. host_name (str): host name Returns: @@ -463,6 +464,7 @@ def _get_imageio_settings(project_settings, host_name): """ # get image io from global and host_name imageio_global = project_settings["global"]["imageio"] - imageio_host = project_settings[host_name]["imageio"] + # host is optional, some might not have any settings + imageio_host = project_settings[host_name].get("imageio", {}) return imageio_global, imageio_host From a8fcd42b6afedbcc0bb82869d0c703edf09f30e2 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Tue, 31 Jan 2023 10:59:47 +0100 Subject: [PATCH 130/356] adjusted according comments --- website/docs/artist_getting_started.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 40961dbc77..5ab89e31d8 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -19,19 +19,22 @@ If this is not the case, please contact your administrator to consult on how to If you are working from **home** though, you'll **need to install** it yourself. You should, however, receive the OpenPype installer files from your studio admin, supervisor or production, because OpenPype versions and executables might not be compatible between studios. -Installing OpenPype is possible by Windows installer or by unzipping it anywhere on the disk from downloaded ZIP archive. +Installing OpenPype is possible by Installer or by unzipping downloaded ZIP archive to any drive location. -For more detailed info about installation on different OS please visit [Installation section](artist_install.md). +> For more detailed info about installing OpenPype please visit [Installation section](artist_install.md). -There are two ways running OpenPype +--- -first most common one by using OP icon on the Desktop triggering +You can run OpenPype by desktop "OP" icon (if exists after installing) or by directly executing -**openpype_gui.exe** suitable **for artists**. It runs OpenPype GUI in the OS tray. From there you can run all the available tools. To use any of the features, OpenPype must be running in the tray. +**openpype_gui.exe** located in the OpenPype folder. This executable being suitable **for artists**. -or alternatively by using +or alternatively by + +**openpype_console.exe** which is more suitable for **TDs/Admin** for debugging and error reporting. This one runs with opened console window where all the necessary info will appear during user's work session. + +> By seeing the "OP" icon in the OS tray user can easily tell OpenPype already running. -**openpype_console.exe** located in the OpenPype folder which is suitable for **TDs/Admin** for debugging and error reporting. This one runs with opened console window where all the necessary info will appear during user's work session. ## First Launch From 25b7d69aabbb90dbbc328f98efafc64668dbe8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 31 Jan 2023 11:58:38 +0100 Subject: [PATCH 131/356] Update openpype/pipeline/colorspace.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index a13e6df811..15d545c49f 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -465,6 +465,6 @@ def _get_imageio_settings(project_settings, host_name): # get image io from global and host_name imageio_global = project_settings["global"]["imageio"] # host is optional, some might not have any settings - imageio_host = project_settings[host_name].get("imageio", {}) + imageio_host = project_settings.get(host_name, {}).get("imageio", {}) return imageio_global, imageio_host From 06863de23e512bdd0aac5787376c4b4724308dcf Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 31 Jan 2023 12:01:43 +0100 Subject: [PATCH 132/356] PR comments --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 15d545c49f..14daa44db8 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -445,7 +445,7 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): file_rules = {} if frules_global["enabled"]: file_rules.update(frules_global["rules"]) - if frules_host["enabled"]: + if frules_host.get("enabled"): file_rules.update(frules_host["rules"]) return file_rules From 153783c2fd290006e3ba5abda2cbf430137bee28 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 31 Jan 2023 12:17:52 +0100 Subject: [PATCH 133/356] Refactor `self.` to `cls.` --- .../hosts/maya/plugins/publish/validate_attributes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_attributes.py b/openpype/hosts/maya/plugins/publish/validate_attributes.py index 136c38bc1d..7a1f0cf086 100644 --- a/openpype/hosts/maya/plugins/publish/validate_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_attributes.py @@ -58,23 +58,23 @@ class ValidateAttributes(pyblish.api.ContextPlugin): # Filter families. families = [instance.data["family"]] families += instance.data.get("families", []) - families = list(set(families) & set(self.attributes.keys())) + families = list(set(families) & set(cls.attributes.keys())) if not families: continue # Get all attributes to validate. attributes = {} for family in families: - for preset in self.attributes[family]: + for preset in cls.attributes[family]: [node_name, attribute_name] = preset.split(".") try: attributes[node_name].update( - {attribute_name: self.attributes[family][preset]} + {attribute_name: cls.attributes[family][preset]} ) except KeyError: attributes.update({ node_name: { - attribute_name: self.attributes[family][preset] + attribute_name: cls.attributes[family][preset] } }) From fe688bd2437304f95e729c5672e83b6e31b37c48 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 31 Jan 2023 12:34:33 +0100 Subject: [PATCH 134/356] Remove unused `version` attribute --- openpype/hosts/maya/plugins/publish/collect_maya_workspace.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_lamina_faces.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_single_uv_set.py | 1 - .../hosts/maya/plugins/publish/validate_no_default_camera.py | 1 - openpype/hosts/maya/plugins/publish/validate_no_namespace.py | 1 - .../hosts/maya/plugins/publish/validate_no_null_transforms.py | 1 - .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 1 - .../hosts/maya/plugins/publish/validate_scene_set_workspace.py | 1 - .../hosts/maya/plugins/publish/validate_shape_default_names.py | 1 - .../maya/plugins/publish/validate_transform_naming_suffix.py | 1 - openpype/hosts/maya/plugins/publish/validate_transform_zero.py | 1 - 13 files changed, 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_workspace.py b/openpype/hosts/maya/plugins/publish/collect_maya_workspace.py index 1250ea438f..122fabe8a1 100644 --- a/openpype/hosts/maya/plugins/publish/collect_maya_workspace.py +++ b/openpype/hosts/maya/plugins/publish/collect_maya_workspace.py @@ -12,7 +12,6 @@ class CollectMayaWorkspace(pyblish.api.ContextPlugin): label = "Maya Workspace" hosts = ['maya'] - version = (0, 1, 0) def process(self, context): workspace = cmds.workspace(rootDirectory=True, query=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py index 4427c6eece..8cd33aa1be 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py @@ -16,7 +16,6 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['model'] category = 'geometry' - version = (0, 1, 0) label = 'Mesh Lamina Faces' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py index 0ef2716559..fc6da82338 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py @@ -20,7 +20,6 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): families = ['model'] hosts = ['maya'] category = 'geometry' - version = (0, 1, 0) label = 'Mesh Edge Length Non Zero' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py index c8892a8e59..2079c159c2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py @@ -21,7 +21,6 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): hosts = ['maya'] families = ['model'] category = 'geometry' - version = (0, 1, 0) label = 'Mesh Normals Unlocked' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py index 6ca8c06ba5..155ab23294 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py @@ -23,7 +23,6 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): families = ['model', 'pointcache'] category = 'uv' optional = True - version = (0, 1, 0) label = "Mesh Single UV Set" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py index 1a5773e6a7..a4fb938d43 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py @@ -16,7 +16,6 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['camera'] - version = (0, 1, 0) label = "No Default Cameras" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py index 01c77e5b2e..61b531aa0f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -24,7 +24,6 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['model'] category = 'cleanup' - version = (0, 1, 0) label = 'No Namespaces' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py index b430c2b63c..291fe6890a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -44,7 +44,6 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['model'] category = 'cleanup' - version = (0, 1, 0) label = 'No Empty/Null Transforms' actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index d5bf7fd1cf..30d95128a2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -24,7 +24,6 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['rig'] - version = (0, 1, 0) label = "Joints Hidden" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index ec2bea220d..91f30f2f4f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -32,7 +32,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): order = ValidatePipelineOrder hosts = ['maya'] category = 'scene' - version = (0, 1, 0) label = 'Maya Workspace Set' def process(self, context): diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py index 651c6bcec9..6b3375ae7e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py @@ -40,7 +40,6 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): families = ['model'] category = 'cleanup' optional = True - version = (0, 1, 0) label = "Shape Default Naming" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] 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 65551c8d5e..2bb8bca3e1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -34,7 +34,6 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): families = ['model'] category = 'cleanup' optional = True - version = (0, 1, 0) label = 'Suffix Naming Conventions' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] SUFFIX_NAMING_TABLE = {"mesh": ["_GEO", "_GES", "_GEP", "_OSD"], diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py index da569195e8..034d325091 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py @@ -19,7 +19,6 @@ class ValidateTransformZero(pyblish.api.Validator): hosts = ["maya"] families = ["model"] category = "geometry" - version = (0, 1, 0) label = "Transform Zero (Freeze)" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] From 7d81f1906c371e35c153c1fd78ab9cc491f0fee4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 31 Jan 2023 12:35:33 +0100 Subject: [PATCH 135/356] Remove unused `category` attribute --- openpype/hosts/maya/plugins/publish/validate_color_sets.py | 1 - .../maya/plugins/publish/validate_mesh_arnold_attributes.py | 1 - openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_lamina_faces.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py | 1 - .../hosts/maya/plugins/publish/validate_mesh_single_uv_set.py | 1 - .../maya/plugins/publish/validate_mesh_vertices_have_edges.py | 1 - openpype/hosts/maya/plugins/publish/validate_no_namespace.py | 1 - .../hosts/maya/plugins/publish/validate_no_null_transforms.py | 1 - .../hosts/maya/plugins/publish/validate_scene_set_workspace.py | 1 - .../hosts/maya/plugins/publish/validate_shape_default_names.py | 1 - .../maya/plugins/publish/validate_transform_naming_suffix.py | 1 - openpype/hosts/maya/plugins/publish/validate_transform_zero.py | 1 - .../maya/plugins/publish/validate_unreal_mesh_triangulated.py | 1 - 16 files changed, 16 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_color_sets.py b/openpype/hosts/maya/plugins/publish/validate_color_sets.py index 905417bafa..7ce3cca61a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_color_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_color_sets.py @@ -19,7 +19,6 @@ class ValidateColorSets(pyblish.api.Validator): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh ColorSets' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] 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 c1c0636b9e..fa4c66952c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -19,7 +19,6 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ["maya"] families = ["model"] - category = "geometry" label = "Mesh Arnold Attributes" actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py index 36a0da7a59..0eece1014e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py @@ -48,7 +48,6 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh Has UVs' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py index 8cd33aa1be..f120361583 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py @@ -15,7 +15,6 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh Lamina Faces' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py index fc6da82338..78e844d201 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py @@ -19,7 +19,6 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): order = ValidateMeshOrder families = ['model'] hosts = ['maya'] - category = 'geometry' label = 'Mesh Edge Length Non Zero' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py index 2079c159c2..1b754a9829 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py @@ -20,7 +20,6 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh Normals Unlocked' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] 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 be7324a68f..be23f61ec5 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py @@ -235,7 +235,6 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh Has Overlapping UVs' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py index 155ab23294..faa360380e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py @@ -21,7 +21,6 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ['maya'] families = ['model', 'pointcache'] - category = 'uv' optional = True label = "Mesh Single UV Set" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 1e6d290ae7..9ac7735501 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -63,7 +63,6 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ['maya'] families = ['model'] - category = 'geometry' label = 'Mesh Vertices Have Edges' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py index 61b531aa0f..e91b99359d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -23,7 +23,6 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['model'] - category = 'cleanup' label = 'No Namespaces' actions = [openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py index 291fe6890a..f77fc81dc1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -43,7 +43,6 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['model'] - category = 'cleanup' label = 'No Empty/Null Transforms' actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index 91f30f2f4f..f1fa4d3c4c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -31,7 +31,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): order = ValidatePipelineOrder hosts = ['maya'] - category = 'scene' label = 'Maya Workspace Set' def process(self, context): diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py index 6b3375ae7e..4ab669f46b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py @@ -38,7 +38,6 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['model'] - category = 'cleanup' optional = True label = "Shape Default Naming" actions = [openpype.hosts.maya.api.action.SelectInvalidAction, 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 2bb8bca3e1..0147aa8a52 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -32,7 +32,6 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ['maya'] families = ['model'] - category = 'cleanup' optional = True label = 'Suffix Naming Conventions' actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py index 034d325091..abd9e00af1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py @@ -18,7 +18,6 @@ class ValidateTransformZero(pyblish.api.Validator): order = ValidateContentsOrder hosts = ["maya"] families = ["model"] - category = "geometry" label = "Transform Zero (Freeze)" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py index 4211e76a73..e78962bf97 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -13,7 +13,6 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): order = ValidateMeshOrder hosts = ["maya"] families = ["staticMesh"] - category = "geometry" label = "Mesh is Triangulated" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] active = False From aad5a4f6eb84f6d131dc0c7e9f3f2c66b9772837 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 31 Jan 2023 11:36:07 +0000 Subject: [PATCH 136/356] Update openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py Co-authored-by: Roy Nieterau --- .../plugins/publish/reset_xgen_attributes.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py index d3408f2c76..08f367f2d5 100644 --- a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -12,16 +12,18 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): families = ["workfile"] def process(self, instance): - for palette, data in instance.data.get("xgenAttributes", {}).items(): + xgen_attributes = instance.data.get("xgenAttributes", {}) + if not xgen_attributes : + return + + for palette, data in xgen_attributes.items(): for attr, value in data.items(): node_attr = "{}.{}".format(palette, attr) self.log.info( "Setting \"{}\" on \"{}\"".format(value, node_attr) ) cmds.setAttr(node_attr, value, type="string") - - cmds.setAttr(palette + "." + "xgExportAsDelta", True) - - if instance.data.get("xgenAttributes", {}): - self.log.info("Saving changes.") - cmds.file(save=True) + cmds.setAttr(palette + ".xgExportAsDelta", True) + + self.log.info("Saving changes.") + cmds.file(save=True) From ca6d6f7ccafa7b85d72f573c4a4519a326d60b54 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 31 Jan 2023 12:36:22 +0100 Subject: [PATCH 137/356] Remove unused `category` attribute from blender plug-ins too --- openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py | 1 - .../blender/plugins/publish/validate_mesh_no_negative_scale.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index cee855671d..edf47193be 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -14,7 +14,6 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "geometry" label = "Mesh Has UV's" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = True diff --git a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index 45ac08811d..618feb95c1 100644 --- a/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -14,7 +14,6 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - category = "geometry" label = "Mesh No Negative Scale" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] From 529ab6ca9bf0de6656fa11ba5f4e38af4b048049 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 31 Jan 2023 12:37:28 +0100 Subject: [PATCH 138/356] Remove unused `version` attribute from blender plug-ins too --- .../blender/plugins/publish/validate_camera_zero_keyframe.py | 1 - .../hosts/blender/plugins/publish/validate_no_colons_in_name.py | 1 - .../hosts/blender/plugins/publish/validate_transform_zero.py | 1 - 3 files changed, 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py index 84b9dd1a6e..48c267fd18 100644 --- a/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py +++ b/openpype/hosts/blender/plugins/publish/validate_camera_zero_keyframe.py @@ -19,7 +19,6 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["camera"] - version = (0, 1, 0) label = "Zero Keyframe" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] diff --git a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py index f5dc9fdd5c..1a98ec4c1d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py +++ b/openpype/hosts/blender/plugins/publish/validate_no_colons_in_name.py @@ -19,7 +19,6 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model", "rig"] - version = (0, 1, 0) label = "No Colons in names" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] diff --git a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py index 742826d3d9..66ef731e6e 100644 --- a/openpype/hosts/blender/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/blender/plugins/publish/validate_transform_zero.py @@ -21,7 +21,6 @@ class ValidateTransformZero(pyblish.api.InstancePlugin): order = ValidateContentsOrder hosts = ["blender"] families = ["model"] - version = (0, 1, 0) label = "Transform Zero" actions = [openpype.hosts.blender.api.action.SelectInvalidAction] From 768b3d8ac9d4ab72a8d776b115232df079802dda Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 11:38:43 +0000 Subject: [PATCH 139/356] Improve docs --- .../maya/plugins/publish/reset_xgen_attributes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py index 08f367f2d5..b90885663c 100644 --- a/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py +++ b/openpype/hosts/maya/plugins/publish/reset_xgen_attributes.py @@ -4,7 +4,11 @@ import pyblish.api class ResetXgenAttributes(pyblish.api.InstancePlugin): - """Reset Xgen attributes.""" + """Reset Xgen attributes. + + When the incremental save of the workfile triggers, the Xgen attributes + changes so this plugin will change it back to the values before publishing. + """ label = "Reset Xgen Attributes." # Offset to run after workfile increment plugin. @@ -13,7 +17,7 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): def process(self, instance): xgen_attributes = instance.data.get("xgenAttributes", {}) - if not xgen_attributes : + if not xgen_attributes: return for palette, data in xgen_attributes.items(): @@ -24,6 +28,9 @@ class ResetXgenAttributes(pyblish.api.InstancePlugin): ) cmds.setAttr(node_attr, value, type="string") 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 + # changes. self.log.info("Saving changes.") cmds.file(save=True) From 658ad2a0c2f34bdc14b4ac586ca88b08db4f8b57 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 31 Jan 2023 11:40:55 +0000 Subject: [PATCH 140/356] Update openpype/hosts/maya/plugins/load/load_xgen.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/load/load_xgen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index d0ed3e05b3..81a525fe61 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -149,8 +149,9 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): new_xgen_file = maya_file.replace(extension, ".xgen") data_path = "" with open(new_xgen_file, "r") as f: - for line in [line.rstrip() for line in f]: + for line in f: if line.startswith("\txgDataPath"): + line = line.rstrip() data_path = line.split("\t")[-1] break From 67e438dcb90e9050c1d03a7de52489e678eebdf0 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 31 Jan 2023 11:56:55 +0000 Subject: [PATCH 141/356] Update openpype/hosts/maya/plugins/inventory/connect_geometry.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/inventory/connect_geometry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/inventory/connect_geometry.py b/openpype/hosts/maya/plugins/inventory/connect_geometry.py index 9fe4f9e195..bcfc577104 100644 --- a/openpype/hosts/maya/plugins/inventory/connect_geometry.py +++ b/openpype/hosts/maya/plugins/inventory/connect_geometry.py @@ -89,9 +89,7 @@ class ConnectGeometry(InventoryAction): return # Setup live worldspace blendshape connection. - for match in matches: - source = match[0] - target = match[1] + for source, target in matches: blendshape = cmds.blendShape(source, target)[0] cmds.setAttr(blendshape + ".origin", 0) cmds.setAttr(blendshape + "." + target.split(":")[-1], 1) From 311944edb6ebf5e740a9a46830e4bf708b6ccc50 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Tue, 31 Jan 2023 13:25:38 +0100 Subject: [PATCH 142/356] updated 3dsmax int. article --- website/docs/artist_hosts_3dsmax.md | 62 ++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index eac89f740b..baee07fbb0 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 for Opening / Saving when dealing with work files not standard 3dsmax ```File Menu``` even though still possible. +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. ::: ## Working With Scene Files @@ -61,37 +61,68 @@ In OpenPype menu first go to ```Work Files``` menu item so **Work Files Window* You first choose particular asset and assigned task and corresponding workfile you would like to open. -If not any workfile present simply hit ```Save As``` and keep ```Subversion``` empty and hitting ```Ok```. +If not any workfile present simply hit ```Save As``` and keep ```Subversion``` empty and hit ```Ok```. ![Save As Dialog](assets/3dsmax_SavingFirstFile_OP.png) -OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like ```workfileName_v001``` ```workfileName_v002``` etc. +OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like -> There also additional tools for naming like ```Subversion``` in ```Save As``` dialog but we won't dive into it for now. +```workfileName_v001``` + +```workfileName_v002``` + + etc. + +Basically meaning user is free of guessing what is the correct naming and other neccessities to keep everthing 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. + +Here you can see resulting work file after ```Save As``` action. ![Save As Dialog](assets/3dsmax_SavingFirstFile2_OP.png) ## Understanding Context -It is good to be aware that whenever you as a user choose ```asset``` and ```task``` you happen to be in so called **context** meaning that all user actions are in relation with particular ```asset```. This could be quickly seen in host application header and or ```OpenPype Menu``` and its accompanying tools. +As seen on our example OpenPype created pretty first workfile and named it ```220901_couch_modeling_v001.max``` meaning it sits in the Project ```220901``` being it ```couch``` asset and workfile being ```modeling``` task and obviously ```v001``` telling user its first existing version of this workfile. + +It is good to be aware that whenever you as a user choose ```asset``` and ```task``` you happen to be in so called **context** meaning that all user actions are in relation with particular ```asset```. This could be quickly seen in host application header and ```OpenPype Menu``` and its accompanying tools. ![Workfile Context](assets/3dsmax_context.png) +> Whenever you choose different ```asset``` and its ```task``` in **Work Files window** you are basically changing context to the current asset/task you have chosen. + + +This concludes the basics of working with workfiles in 3dsmax using OpenPype and its tools. Following chapters will cover other aspects like creating multiple assets types and their publishing for later usage in the production. + --- -# *...to be edited for 3dsmax* +## Creating and Publishing Instances -## ~~Setting scene data~~ +:::warning Important +Before proceeding further please check [Glossary](artist_concepts.md) and [What Is Publishing?](artist_publish.md) So you have clear idea about terminology. +::: -3dsmax settings concerning framerate, resolution and frame range are handled -by OpenPype. If set correctly in OP Project Manager/Ftrack, 3dsmax will automatically set the -values for you. - - -## ~~Publishing models~~ ### Intro +Current OpenPype integration (ver 3.15.0) supports only ```PointCache``` and ```Camera``` families now. + +**Pointcache** family being basically any geometry outputted as Alembic cache (.abc) format + +**Camera** family being 3dsmax Camera object with/without animation outputted as native .max, FBX, Alembic format + + +--- + +:::note Work in progress +This part of documentation is still work in progress. +::: + +## ...to be added + + + + + From ab0e3fab01f150cc963579e921c5f9547b276060 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 17:47:12 +0000 Subject: [PATCH 143/356] BigRoy feedback --- openpype/hosts/maya/api/lib.py | 28 +++- openpype/hosts/maya/api/plugin.py | 30 ---- .../plugins/inventory/connect_geometry.py | 32 +++- openpype/hosts/maya/plugins/load/load_xgen.py | 31 ++++ .../maya/plugins/publish/collect_xgen.py | 32 ++++ .../plugins/publish/extract_workfile_xgen.py | 97 +++++++---- .../maya/plugins/publish/extract_xgen.py | 158 ++++++++---------- .../maya/plugins/publish/validate_xgen.py | 18 +- 8 files changed, 255 insertions(+), 171 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 65f39270f5..e9956762f2 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,6 +5,7 @@ import sys import platform import uuid import math +import re import json import logging @@ -3449,14 +3450,31 @@ def get_attribute_input(attr): def write_xgen_file(data, filepath): + """Overwrites data in .xgen files. + + Quite naive approach to mainly overwrite "xgDataPath" and "xgProjectPath". + + Args: + data (dict): Dictionary of key, value. Key matches with xgen file. + For example: + {"xgDataPath": "some/path"} + filepath (string): Absolute path of .xgen file. + """ + # Generate regex lookup for line to key basically + # match any of the keys in `\t{key}\t\t` + keys = "|".join(re.escape(key) for key in data.keys()) + re_keys = re.compile("^\t({})\t\t".format(keys)) + lines = [] with open(filepath, "r") as f: - for line in [line.rstrip() for line in f]: - for key, value in data.items(): - if line.startswith("\t{}".format(key)): - line = "\t{}\t\t{}".format(key, value) + for line in f: + match = re_keys.match(line) + if match: + key = match.group(1) + value = data[key] + line = "\t{}\t\t{}\n".format(key, value) lines.append(line) with open(filepath, "w") as f: - f.write("\n".join(lines)) + f.writelines(lines) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index b7adf6edfc..82df85a8be 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -300,36 +300,6 @@ class ReferenceLoader(Loader): str(representation["_id"]), type="string") - # Update any xgen containers. - compound_name = "xgenContainers" - if cmds.objExists("{}.{}".format(node, compound_name)): - import xgenm - container_amount = cmds.getAttr( - "{}.{}".format(node, compound_name), size=True - ) - # loop through all compound children - for i in range(container_amount): - attr = "{}.{}[{}].container".format(node, compound_name, i) - objectset = cmds.listConnections(attr)[0] - reference_node = cmds.sets(objectset, query=True)[0] - palettes = cmds.ls( - cmds.referenceQuery(reference_node, nodes=True), - type="xgmPalette" - ) - for palette in palettes: - for description in xgenm.descriptions(palette): - xgenm.setAttr( - "cacheFileName", - path.replace("\\", "/"), - palette, - description, - "SplinePrimitive" - ) - - # Refresh UI and viewport. - de = xgenm.xgGlobal.DescriptionEditor - de.refresh("Full") - def remove(self, container): """Remove an existing `container` from Maya scene diff --git a/openpype/hosts/maya/plugins/inventory/connect_geometry.py b/openpype/hosts/maya/plugins/inventory/connect_geometry.py index bcfc577104..a12487cf7e 100644 --- a/openpype/hosts/maya/plugins/inventory/connect_geometry.py +++ b/openpype/hosts/maya/plugins/inventory/connect_geometry.py @@ -1,6 +1,7 @@ from maya import cmds from openpype.pipeline import InventoryAction, get_representation_context +from openpype.hosts.maya.api.lib import get_id class ConnectGeometry(InventoryAction): @@ -64,12 +65,12 @@ class ConnectGeometry(InventoryAction): source_data = self.get_container_data(source_object) matches = [] - node_types = [] + node_types = set() for target_container in target_containers: target_data = self.get_container_data( target_container["objectName"] ) - node_types.extend(target_data["node_types"]) + node_types.update(target_data["node_types"]) for id, transform in target_data["ids"].items(): source_match = source_data["ids"].get(id) if source_match: @@ -99,15 +100,30 @@ class ConnectGeometry(InventoryAction): cmds.xgmPreview() def get_container_data(self, container): - data = {"node_types": [], "ids": {}} + """Collects data about the container nodes. + + Args: + container (dict): Container instance. + + Returns: + data (dict): + "node_types": All node types in container nodes. + "ids": If the node is a mesh, we collect its parent transform + id. + """ + data = {"node_types": set(), "ids": {}} ref_node = cmds.sets(container, query=True, nodesOnly=True)[0] for node in cmds.referenceQuery(ref_node, nodes=True): node_type = cmds.nodeType(node) - data["node_types"].append(node_type) - if node_type == "mesh": - transform = cmds.listRelatives(node, parent=True)[0] - id = cmds.getAttr(transform + ".cbId") - data["ids"][id] = transform + data["node_types"].add(node_type) + + # Only interested in mesh transforms for connecting geometry with + # blendshape. + if node_type != "mesh": + continue + + transform = cmds.listRelatives(node, parent=True)[0] + data["ids"][get_id(transform)] = transform return data diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 81a525fe61..5110d4ca05 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -173,3 +173,34 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): super().update(container, representation) xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) + + # Update any xgen containers. + compound_name = "xgenContainers" + import xgenm + container_amount = cmds.getAttr( + "{}.{}".format(container_node, compound_name), size=True + ) + # loop through all compound children + for i in range(container_amount): + attr = "{}.{}[{}].container".format( + container_node, compound_name, i + ) + objectset = cmds.listConnections(attr)[0] + reference_node = cmds.sets(objectset, query=True)[0] + palettes = cmds.ls( + cmds.referenceQuery(reference_node, nodes=True), + type="xgmPalette" + ) + for palette in palettes: + for description in xgenm.descriptions(palette): + xgenm.setAttr( + "cacheFileName", + maya_file.replace("\\", "/"), + palette, + description, + "SplinePrimitive" + ) + + # Refresh UI and viewport. + de = xgenm.xgGlobal.DescriptionEditor + de.refresh("Full") diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index 5a48b1d221..da0549b2d8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -1,3 +1,5 @@ +import os + from maya import cmds import pyblish.api @@ -35,5 +37,35 @@ class CollectXgen(pyblish.api.InstancePlugin): input = get_attribute_input("{}.{}".format(node, attr)) data["xgenConnections"][node][attr] = input + # Collect all files under palette root as resources. + import xgenm + + data_path = xgenm.getAttr( + "xgDataPath", data["xgmPalette"].replace("|", "") + ).split(os.pathsep)[0] + data_path = data_path.replace( + "${PROJECT}", + xgenm.getAttr("xgProjectPath", data["xgmPalette"].replace("|", "")) + ) + transfers = [] + + # Since we are duplicating this palette when extracting we predict that + # the name will be the basename without namespaces. + predicted_palette_name = data["xgmPalette"].split(":")[-1] + predicted_palette_name = predicted_palette_name.replace("|", "") + + for root, _, files in os.walk(data_path): + for file in files: + source = os.path.join(root, file).replace("\\", "/") + destination = os.path.join( + instance.data["resourcesDir"], + "collections", + predicted_palette_name, + source.replace(data_path, "")[1:] + ) + transfers.append((source, destination.replace("\\", "/"))) + + data["transfers"] = transfers + self.log.info(data) instance.data.update(data) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index 5847563e5b..fe28427ae7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -5,14 +5,16 @@ import copy from maya import cmds import pyblish.api -from openpype.hosts.maya.api import current_file from openpype.hosts.maya.api.lib import extract_alembic from openpype.pipeline import publish from openpype.lib import StringTemplate class ExtractWorkfileXgen(publish.Extractor): - """Extract Workfile Xgen.""" + """Extract Workfile Xgen. + + When submitting a render, we need to prep Xgen side car files. + """ # Offset to run before workfile scene save. order = pyblish.api.ExtractorOrder - 0.499 @@ -20,13 +22,65 @@ class ExtractWorkfileXgen(publish.Extractor): families = ["workfile"] hosts = ["maya"] + def get_render_max_frame_range(self, context): + """Return start to end frame range including all renderlayers in + context. + + This will return the full frame range which includes all frames of the + renderlayer instances to be published/submitted. + + Args: + context (pyblish.api.Context): Current publishing context. + + Returns: + tuple or None: Start frame, end frame tuple if any renderlayers + found. Otherwise None is returned. + + """ + + def _is_active_renderlayer(i): + """Return whether instance is active renderlayer""" + if not i.data.get("publish", True): + return False + + is_renderlayer = ( + "renderlayer" in i.data.get("families", []) or + i.data["family"] == "renderlayer" + ) + return is_renderlayer + + start_frame = None + end_frame = None + for instance in context: + if not _is_active_renderlayer(instance): + # Only consider renderlyare instances + continue + + render_start_frame = instance.data["frameStart"] + render_end_frame = instance.data["frameStart"] + + if start_frame is None: + start_frame = render_start_frame + else: + start_frame = min(start_frame, render_start_frame) + + if end_frame is None: + end_frame = render_end_frame + else: + end_frame = max(end_frame, render_end_frame) + + if start_frame is None or end_frame is None: + return + + return start_frame, end_frame + def process(self, instance): transfers = [] # Validate there is any palettes in the scene. if not cmds.ls(type="xgmPalette"): self.log.debug( - "No collections found in the scene. Abort Xgen extraction." + "No collections found in the scene. Skipping Xgen extraction." ) return else: @@ -34,44 +88,24 @@ class ExtractWorkfileXgen(publish.Extractor): # Validate to extract only when we are publishing a renderlayer as # well. - renderlayer = False - start_frame = None - end_frame = None - for i in instance.context: - is_renderlayer = ( - "renderlayer" in i.data.get("families", []) or - i.data["family"] == "renderlayer" - ) - if is_renderlayer and i.data["publish"]: - renderlayer = True - - if start_frame is None: - start_frame = i.data["frameStart"] - if end_frame is None: - end_frame = i.data["frameEnd"] - - if i.data["frameStart"] < start_frame: - start_frame = i.data["frameStart"] - if i.data["frameEnd"] > end_frame: - end_frame = i.data["frameEnd"] - - break - - if not renderlayer: + render_range = self.get_render_max_frame_range(instance.context) + if not render_range: self.log.debug( - "No publishable renderlayers found in context. Abort Xgen" + "No publishable renderlayers found in context. Skipping Xgen" " extraction." ) return + start_frame, end_frame = render_range + # We decrement start frame and increment end frame so motion blur will # render correctly. start_frame -= 1 end_frame += 1 # Extract patches alembic. - basename, _ = os.path.splitext(current_file()) - dirname = os.path.dirname(current_file()) + basename, _ = os.path.splitext(instance.context.data["currentFile"]) + dirname = os.path.dirname(instance.context.data["currentFile"]) kwargs = {"attrPrefix": ["xgen"], "stripNamespaces": True} alembic_files = [] for palette in cmds.ls(type="xgmPalette"): @@ -121,8 +155,7 @@ class ExtractWorkfileXgen(publish.Extractor): # Collect Xgen and Delta files. xgen_files = [] sources = [] - file_path = current_file() - current_dir = os.path.dirname(file_path) + current_dir = os.path.dirname(instance.context.data["currentFile"]) attrs = ["xgFileName", "xgBaseFile"] for palette in cmds.ls(type="xgmPalette"): for attr in attrs: diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 9549aba76d..7428fab53f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -7,7 +7,7 @@ import xgenm from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( - maintained_selection, attribute_values, write_xgen_file + maintained_selection, attribute_values, write_xgen_file, delete_after ) from openpype.lib import StringTemplate @@ -36,48 +36,81 @@ class ExtractXgenCache(publish.Extractor): maya_filename = "{}.{}".format(instance.data["name"], self.scene_type) maya_filepath = os.path.join(staging_dir, maya_filename) - # Collect nodes to export. - duplicate_nodes = [] - for node, connections in instance.data["xgenConnections"].items(): - transform_name = connections["transform"].split(".")[0] - - # Duplicate_transform subd patch geometry. - duplicate_transform = cmds.duplicate(transform_name)[0] - - # Discard the children. - shapes = cmds.listRelatives(duplicate_transform, shapes=True) - children = cmds.listRelatives(duplicate_transform, children=True) - cmds.delete(set(children) - set(shapes)) - - duplicate_transform = cmds.parent( - duplicate_transform, world=True - )[0] - - duplicate_nodes.append(duplicate_transform) - - # Export temp xgen palette files. - temp_xgen_path = os.path.join( - tempfile.gettempdir(), "temp.xgen" - ).replace("\\", "/") - xgenm.exportPalette( - instance.data["xgmPalette"].replace("|", ""), temp_xgen_path - ) - self.log.info("Extracted to {}".format(temp_xgen_path)) - - # Import xgen onto the duplicate. - with maintained_selection(): - cmds.select(duplicate_nodes) - palette = xgenm.importPalette(temp_xgen_path, []) - # Get published xgen file name. template_data = copy.deepcopy(instance.data["anatomyData"]) template_data.update({"ext": "xgen"}) templates = instance.context.data["anatomy"].templates["publish"] xgen_filename = StringTemplate(templates["file"]).format(template_data) - # Export duplicated palette. - xgen_path = os.path.join(staging_dir, xgen_filename).replace("\\", "/") - xgenm.exportPalette(palette, xgen_path) + xgen_path = os.path.join( + self.staging_dir(instance), xgen_filename + ).replace("\\", "/") + type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" + + # Duplicate xgen setup. + with delete_after() as delete_bin: + duplicate_nodes = [] + # Collect nodes to export. + for node, connections in instance.data["xgenConnections"].items(): + transform_name = connections["transform"].split(".")[0] + + # Duplicate_transform subd patch geometry. + duplicate_transform = cmds.duplicate(transform_name)[0] + delete_bin.append(duplicate_transform) + + # Discard the children. + shapes = cmds.listRelatives(duplicate_transform, shapes=True) + children = cmds.listRelatives( + duplicate_transform, children=True + ) + cmds.delete(set(children) - set(shapes)) + + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] + + duplicate_nodes.append(duplicate_transform) + + # Export temp xgen palette files. + temp_xgen_path = os.path.join( + tempfile.gettempdir(), "temp.xgen" + ).replace("\\", "/") + xgenm.exportPalette( + instance.data["xgmPalette"].replace("|", ""), temp_xgen_path + ) + self.log.info("Extracted to {}".format(temp_xgen_path)) + + # Import xgen onto the duplicate. + with maintained_selection(): + cmds.select(duplicate_nodes) + palette = xgenm.importPalette(temp_xgen_path, []) + + delete_bin.append(palette) + + # Export duplicated palettes. + xgenm.exportPalette(palette, xgen_path) + + # Export Maya file. + attribute_data = {"{}.xgFileName".format(palette): xgen_filename} + with attribute_values(attribute_data): + with maintained_selection(): + cmds.select(duplicate_nodes + [palette]) + cmds.file( + maya_filepath, + force=True, + type=type, + exportSelected=True, + preserveReferences=False, + constructionHistory=True, + shader=True, + constraints=True, + expressions=True + ) + + self.log.info("Extracted to {}".format(maya_filepath)) + + if os.path.exists(temp_xgen_path): + os.remove(temp_xgen_path) data = { "xgDataPath": os.path.join( @@ -91,6 +124,7 @@ class ExtractXgenCache(publish.Extractor): } write_xgen_file(data, xgen_path) + # Adding representations. representation = { "name": "xgen", "ext": "xgen", @@ -99,28 +133,6 @@ class ExtractXgenCache(publish.Extractor): } instance.data["representations"].append(representation) - # Export Maya file. - type = "mayaAscii" if self.scene_type == "ma" else "mayaBinary" - attribute_data = { - "{}.xgFileName".format(palette): xgen_filename - } - with attribute_values(attribute_data): - with maintained_selection(): - cmds.select(duplicate_nodes + [palette]) - cmds.file( - maya_filepath, - force=True, - type=type, - exportSelected=True, - preserveReferences=False, - constructionHistory=True, - shader=True, - constraints=True, - expressions=True - ) - - self.log.info("Extracted to {}".format(maya_filepath)) - representation = { "name": self.scene_type, "ext": self.scene_type, @@ -128,31 +140,3 @@ class ExtractXgenCache(publish.Extractor): "stagingDir": staging_dir } instance.data["representations"].append(representation) - - # Clean up. - cmds.delete(duplicate_nodes + [palette]) - os.remove(temp_xgen_path) - - # Collect all files under palette root as resources. - data_path = xgenm.getAttr( - "xgDataPath", instance.data["xgmPalette"].replace("|", "") - ).split(os.pathsep)[0] - data_path = data_path.replace( - "${PROJECT}", - xgenm.getAttr( - "xgProjectPath", instance.data["xgmPalette"].replace("|", "") - ) - ) - transfers = [] - for root, _, files in os.walk(data_path): - for file in files: - source = os.path.join(root, file).replace("\\", "/") - destination = os.path.join( - instance.data["resourcesDir"], - "collections", - palette, - source.replace(data_path, "")[1:] - ) - transfers.append((source, destination.replace("\\", "/"))) - - instance.data["transfers"] = transfers diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 19cf612848..2870909974 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -4,7 +4,7 @@ import maya.cmds as cmds import xgenm import pyblish.api -from openpype.pipeline.publish import KnownPublishError +from openpype.pipeline.publish import PublishValidationError class ValidateXgen(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateXgen(pyblish.api.InstancePlugin): # Only 1 collection/node per instance. if len(set_members) != 1: - raise KnownPublishError( + raise PublishValidationError( "Only one collection per instance is allowed." " Found:\n{}".format(set_members) ) @@ -28,7 +28,7 @@ class ValidateXgen(pyblish.api.InstancePlugin): # Only xgen palette node is allowed. node_type = cmds.nodeType(set_members[0]) if node_type != "xgmPalette": - raise KnownPublishError( + raise PublishValidationError( "Only node of type \"xgmPalette\" are allowed. Referred to as" " \"collection\" in the Maya UI." " Node type found: {}".format(node_type) @@ -50,10 +50,10 @@ class ValidateXgen(pyblish.api.InstancePlugin): except KeyError: inactive_modifiers[description] = [name] - msg = ( - "There are inactive modifiers on the collection. " - "Please delete these:\n{}".format( - json.dumps(inactive_modifiers, indent=4, sort_keys=True) + if inactive_modifiers: + raise PublishValidationError( + "There are inactive modifiers on the collection. " + "Please delete these:\n{}".format( + json.dumps(inactive_modifiers, indent=4, sort_keys=True) + ) ) - ) - assert not inactive_modifiers, msg From caf26b8ab671ee90977a78fdf9485381b1ae1f68 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 17:49:10 +0000 Subject: [PATCH 144/356] Hound --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 7428fab53f..0719be3a1e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -51,7 +51,7 @@ class ExtractXgenCache(publish.Extractor): with delete_after() as delete_bin: duplicate_nodes = [] # Collect nodes to export. - for node, connections in instance.data["xgenConnections"].items(): + for _, connections in instance.data["xgenConnections"].items(): transform_name = connections["transform"].split(".")[0] # Duplicate_transform subd patch geometry. From 8f83673f7f8dd93c063072437c8c32d8278677df Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:12:02 +0100 Subject: [PATCH 145/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 5ab89e31d8..3db4421181 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -21,7 +21,9 @@ admin, supervisor or production, because OpenPype versions and executables might Installing OpenPype is possible by Installer or by unzipping downloaded ZIP archive to any drive location. -> For more detailed info about installing OpenPype please visit [Installation section](artist_install.md). +:::tip Using the OpenPype Installer +See the [Installation section](artist_install.md) for more information on how to use the OpenPype Installer +::: --- From 7ee305a17329892a28025d65fb906fd376a4d3a3 Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:12:15 +0100 Subject: [PATCH 146/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 3db4421181..b9a75786cd 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -35,7 +35,9 @@ or alternatively by **openpype_console.exe** which is more suitable for **TDs/Admin** for debugging and error reporting. This one runs with opened console window where all the necessary info will appear during user's work session. -> By seeing the "OP" icon in the OS tray user can easily tell OpenPype already running. +:::tip Is OpenPype running? +OpenPype runs in the operating system's tray. If you see the OpenPype icon in the tray you can easily tell OpenPype is currently running. +::: From 4193ab3a9a8bc07f3cd21a03b434fcff5bf9f0dd Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:12:41 +0100 Subject: [PATCH 147/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index b9a75786cd..ef7e682c94 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -44,7 +44,7 @@ OpenPype runs in the operating system's tray. If you see the OpenPype icon in th ## First Launch -When you first start OpenPype, you will be asked to fill in some basic informations. +When you first start OpenPype, you will be asked to fill in some basic information. ### MongoDB From 51ad73c7b3f8b3376693fd946cefb38d716ad180 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 09:12:47 +0000 Subject: [PATCH 148/356] BigRoy feedback --- openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py | 4 ++-- website/docs/artist_hosts_maya_xgen.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index fe28427ae7..49d724960f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -83,8 +83,8 @@ class ExtractWorkfileXgen(publish.Extractor): "No collections found in the scene. Skipping Xgen extraction." ) return - else: - import xgenm + + import xgenm # Validate to extract only when we are publishing a renderlayer as # well. diff --git a/website/docs/artist_hosts_maya_xgen.md b/website/docs/artist_hosts_maya_xgen.md index 191826f49c..ec5f2ed921 100644 --- a/website/docs/artist_hosts_maya_xgen.md +++ b/website/docs/artist_hosts_maya_xgen.md @@ -4,6 +4,8 @@ title: Xgen for Maya sidebar_label: Xgen --- +OpenPype supports Xgen classic with the follow workflow. It eases the otherwise cumbersome issues around Xgen's side car files and hidden behaviour inside Maya. The workflow supports publishing, loading and updating of Xgen collections, along with connecting animation from geometry and (guide) curves. + ## Setup ### Settings From 56c7c00bf8ecefbb7330f41f4b50bc3145a9474d Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:12:53 +0100 Subject: [PATCH 149/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index ef7e682c94..0ad98961d5 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -48,8 +48,8 @@ When you first start OpenPype, you will be asked to fill in some basic informati ### MongoDB -In most cases that will only be your studio MongoDB Address. -It's a URL that you should have received from your Studio admin and most often will look like this +In most cases you will only have to supply the MongoDB Address. +It's the database URL you should have received from your Studio admin and often will look like this `mongodb://username:passwword@mongo.mystudiodomain.com:12345` From 1ae99a2d2fae6274115aaeb0cd170587938972cb Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:14:00 +0100 Subject: [PATCH 150/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 0ad98961d5..72a218f77e 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -62,7 +62,7 @@ asks for it, just put it in the corresponding text field and press `install` but ### OpenPype Version Repository -Sometimes your Studio might also ask you to fill in the path to it's version +Sometimes your Studio might also ask you to fill in the path to its version repository. This is a location where OpenPype will be looking for when checking if it's up to date and where updates are installed from automatically. From fae61816c60e2c2b351749ae240f279b00cce538 Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:14:16 +0100 Subject: [PATCH 151/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 72a218f77e..1b32c6a985 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -27,7 +27,7 @@ See the [Installation section](artist_install.md) for more information on how to --- -You can run OpenPype by desktop "OP" icon (if exists after installing) or by directly executing +You can run OpenPype by desktop "OP" icon (if it exists after installing) or by directly executing **openpype_gui.exe** located in the OpenPype folder. This executable being suitable **for artists**. From 022e369d311dc09ca4404e7862f57e6b13323c4f Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:15:36 +0100 Subject: [PATCH 152/356] Update website/docs/artist_getting_started.md Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 1b32c6a985..8350333610 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -63,7 +63,7 @@ asks for it, just put it in the corresponding text field and press `install` but ### OpenPype Version Repository Sometimes your Studio might also ask you to fill in the path to its version -repository. This is a location where OpenPype will be looking for when checking +repository. This is a location where OpenPype will search for the latest versions, check if it's up to date and where updates are installed from automatically. This path is usually taken from the database directly, so you shouldn't need it. From 8351ce0fba1e09689f9e41339e90463a4fde419a Mon Sep 17 00:00:00 2001 From: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:17:04 +0100 Subject: [PATCH 153/356] grammar fixes Co-authored-by: Roy Nieterau --- website/docs/artist_getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 8350333610..14951e599d 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -71,7 +71,7 @@ This path is usually taken from the database directly, so you shouldn't need it. ## Updates -If you're connected to your Studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start, it will go through a quick update installation process, even though you might have just installed it. +If you're connected to your Studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start it can go through a quick update installation process, even though you might have just installed it. ## Advanced Usage From 062259fae27e75939fdedd202637bfdfe3844dca Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Wed, 1 Feb 2023 10:52:54 +0100 Subject: [PATCH 154/356] removed commented parts --- website/docs/artist_hosts_3dsmax.md | 131 ---------------------------- 1 file changed, 131 deletions(-) diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index baee07fbb0..71ba8785dc 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -122,135 +122,4 @@ This part of documentation is still work in progress. - - - - - - From 0fb0f74250761c4e6f1582b8ca64613caf0087a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:23:52 +0100 Subject: [PATCH 155/356] Fix pyproject.toml version because of Poetry Automatization injects wrong format --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 634aeda5ac..2fc4f6fe39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1-nightly.2" # OpenPype +version = "3.15.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From fb56e169dc745d6b934dcd509367dee27b91b0f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 12:34:21 +0100 Subject: [PATCH 156/356] check for source class instead of for function by name availability --- openpype/pipeline/context_tools.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 06538dd91f..6610fd7da7 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -11,6 +11,7 @@ import pyblish.api from pyblish.lib import MessageHandler import openpype +from openpype.host import HostBase from openpype.client import ( get_project, get_asset_by_id, @@ -317,7 +318,7 @@ def get_current_host_name(): """ host = registered_host() - if host is not None and hasattr(host, "name"): + if isinstance(host, HostBase): return host.name return os.environ.get("AVALON_APP") @@ -332,28 +333,28 @@ def get_global_context(): def get_current_context(): host = registered_host() - if host is not None and hasattr(host, "get_current_context"): + if isinstance(host, HostBase): return host.get_current_context() return get_global_context() def get_current_project_name(): host = registered_host() - if host is not None and hasattr(host, "get_current_project_name"): + if isinstance(host, HostBase): return host.get_current_project_name() return get_global_context()["project_name"] def get_current_asset_name(): host = registered_host() - if host is not None and hasattr(host, "get_current_asset_name"): + if isinstance(host, HostBase): return host.get_current_asset_name() return get_global_context()["asset_name"] def get_current_task_name(): host = registered_host() - if host is not None and hasattr(host, "get_current_task_name"): + if isinstance(host, HostBase): return host.get_current_task_name() return get_global_context()["task_name"] From b37359979cd9f52034f77e5df250e7cefe535877 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 1 Feb 2023 12:38:02 +0100 Subject: [PATCH 157/356] :recycle: force modules to sys.path --- openpype/hosts/max/startup/startup.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/startup/startup.py b/openpype/hosts/max/startup/startup.py index 37bcef5db1..21da115baa 100644 --- a/openpype/hosts/max/startup/startup.py +++ b/openpype/hosts/max/startup/startup.py @@ -1,6 +1,20 @@ # -*- coding: utf-8 -*- -from openpype.hosts.max.api import MaxHost -from openpype.pipeline import install_host +import os +import sys + +# this might happen in some 3dsmax version where PYTHONPATH isn't added +# to sys.path automatically +try: + from openpype.hosts.max.api import MaxHost + from openpype.pipeline import install_host +except (ImportError, ModuleNotFoundError): + + for path in os.environ["PYTHONPATH"].split(os.pathsep): + if path and path not in sys.path: + sys.path.append(path) + + from openpype.hosts.max.api import MaxHost + from openpype.pipeline import install_host host = MaxHost() install_host(host) From 2313cd0507c73b5175cc5f353d74db64e016c367 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 12:38:54 +0100 Subject: [PATCH 158/356] added context getter functions to pipeline init --- openpype/pipeline/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index f5319c5a48..7a2ef59a5a 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -86,6 +86,12 @@ from .context_tools import ( registered_host, deregister_host, get_process_id, + + get_current_context, + get_current_host_name, + get_current_project_name, + get_current_asset_name, + get_current_task_name, ) install = install_host uninstall = uninstall_host @@ -176,6 +182,13 @@ __all__ = ( "register_host", "registered_host", "deregister_host", + "get_process_id", + + "get_current_context", + "get_current_host_name", + "get_current_project_name", + "get_current_asset_name", + "get_current_task_name", # Backwards compatible function names "install", From 981d454802dded37955a2c8c573c24173a5d5957 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 11:57:12 +0000 Subject: [PATCH 159/356] Remove old xgen cache plugin --- .../maya/plugins/publish/extract_xgen.py | 2 +- .../plugins/publish/extract_xgen_cache.py | 64 ------------------- 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_xgen_cache.py diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index 0719be3a1e..0cc842b4ec 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -12,7 +12,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.lib import StringTemplate -class ExtractXgenCache(publish.Extractor): +class ExtractXgen(publish.Extractor): """Extract Xgen Workflow: diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py deleted file mode 100644 index 77350f343e..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py +++ /dev/null @@ -1,64 +0,0 @@ -import os - -from maya import cmds - -from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import ( - suspended_refresh, - maintained_selection -) - - -class ExtractXgenCache(publish.Extractor): - """Produce an alembic of just xgen interactive groom - - """ - - label = "Extract Xgen ABC Cache" - hosts = ["maya"] - families = ["xgen"] - optional = True - - def process(self, instance): - - # Collect the out set nodes - out_descriptions = [node for node in instance - if cmds.nodeType(node) == "xgmSplineDescription"] - - start = 1 - end = 1 - - self.log.info("Extracting Xgen Cache..") - dirname = self.staging_dir(instance) - - parent_dir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(parent_dir, filename) - - with suspended_refresh(): - with maintained_selection(): - command = ( - '-file ' - + path - + ' -df "ogawa" -fr ' - + str(start) - + ' ' - + str(end) - + ' -step 1 -mxf -wfw' - ) - for desc in out_descriptions: - command += (" -obj " + desc) - cmds.xgmSplineCache(export=True, j=command) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, - "stagingDir": dirname, - } - instance.data["representations"].append(representation) - - self.log.info("Extracted {} to {}".format(instance, dirname)) From 1690a64216b16e6305485d80b5710c8f2f1dab70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 1 Feb 2023 15:21:18 +0100 Subject: [PATCH 160/356] Update openpype/pipeline/colorspace.py Co-authored-by: Roy Nieterau --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 14daa44db8..cb37b2c4ae 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -445,7 +445,7 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): file_rules = {} if frules_global["enabled"]: file_rules.update(frules_global["rules"]) - if frules_host.get("enabled"): + if frules_host and frules_host["enabled"]: file_rules.update(frules_host["rules"]) return file_rules From e10859d322d675a6e0945e1e9958480c9ee33ca1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 15:54:18 +0100 Subject: [PATCH 161/356] pr comments --- .../publish/extract_subset_resources.py | 3 ++ openpype/pipeline/publish/lib.py | 50 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index c6148162a6..5082217db0 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -143,6 +143,9 @@ class ExtractSubsetResources(publish.Extractor): # create staging dir path staging_dir = self.staging_dir(instance) + # append staging dir for later cleanup + instance.context.data["cleanupFullPaths"].append(staging_dir) + # add default preset type for thumbnail and reviewable video # update them with settings and override in case the same # are found in there diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cc4304cebd..a32b076775 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -616,7 +616,7 @@ def get_instance_staging_dir(instance): First check if 'stagingDir' is already set in instance data. In case there already is new tempdir will not be created. - It also supports `OPENPYPE_TEMP_DIR`, so studio can define own temp + It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. Template formating is supported also with optional keys. Folder is created in case it doesnt exists. @@ -645,17 +645,16 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") + openpype_temp_dir = os.getenv("OPENPYPE_TMPDIR") custom_temp_dir = None if openpype_temp_dir: if "{" in openpype_temp_dir: - custom_temp_dir = _formated_staging_dir( + custom_temp_dir = _format_staging_dir( instance, openpype_temp_dir ) elif os.path.exists(openpype_temp_dir): custom_temp_dir = openpype_temp_dir - if custom_temp_dir: staging_dir = os.path.normpath( tempfile.mkdtemp( @@ -669,30 +668,39 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - instance.context.data["cleanupFullPaths"].append(staging_dir) - return staging_dir -def _formated_staging_dir(instance, openpype_temp_dir): +def _format_staging_dir(instance, openpype_temp_dir): + """ Formating template + + Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + + Args: + instance (pyblish.Instance): instance object + openpype_temp_dir (str): path string + + Returns: + str: formated path + """ anatomy = instance.context.data["anatomy"] # get anatomy formating data # so template formating is supported anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) anatomy_data["root"] = anatomy.roots - """Template path formatting is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - """ - result = StringTemplate.format_template(openpype_temp_dir, anatomy_data) - result = os.path.normpath(result) - # create the dir in case it doesnt exists + + result = StringTemplate.format_template( + openpype_temp_dir, anatomy_data).normalized() + + # create the dir in case it doesnt exists os.makedirs(os.path.dirname(result)) return result From c71fc217da17da5e93b7129240ffc6e418d7cd12 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 15:05:15 +0000 Subject: [PATCH 162/356] BigRoy feedback --- .../maya/plugins/publish/extract_workfile_xgen.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index 49d724960f..b95add3306 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -104,7 +104,7 @@ class ExtractWorkfileXgen(publish.Extractor): end_frame += 1 # Extract patches alembic. - basename, _ = os.path.splitext(instance.context.data["currentFile"]) + path_no_ext, _ = os.path.splitext(instance.context.data["currentFile"]) dirname = os.path.dirname(instance.context.data["currentFile"]) kwargs = {"attrPrefix": ["xgen"], "stripNamespaces": True} alembic_files = [] @@ -116,7 +116,9 @@ class ExtractWorkfileXgen(publish.Extractor): alembic_file = os.path.join( dirname, - "{}__{}.abc".format(basename, palette.replace(":", "__ns__")) + "{}__{}.abc".format( + path_no_ext, palette.replace(":", "__ns__") + ) ) extract_alembic( alembic_file, @@ -138,7 +140,9 @@ class ExtractWorkfileXgen(publish.Extractor): for source in alembic_files: destination = os.path.join( os.path.dirname(instance.data["resourcesDir"]), - os.path.basename(source.replace(basename, published_basename)) + os.path.basename( + source.replace(path_no_ext, published_basename) + ) ) transfers.append((source, destination)) From b2ed65c17ad147e44fab334534f8fd1ad837d53c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 16:06:01 +0100 Subject: [PATCH 163/356] pr comments --- openpype/pipeline/publish/lib.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index a32b076775..b3d273781e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -649,12 +649,21 @@ def get_instance_staging_dir(instance): custom_temp_dir = None if openpype_temp_dir: if "{" in openpype_temp_dir: + # path is anatomy template custom_temp_dir = _format_staging_dir( instance, openpype_temp_dir ) - elif os.path.exists(openpype_temp_dir): + else: + # path is absolute custom_temp_dir = openpype_temp_dir + if not os.path.exists(custom_temp_dir): + try: + # create it if it doesnt exists + os.makedirs(custom_temp_dir) + except IOError as error: + raise IOError("Path couldn't be created: {}".format(error)) + if custom_temp_dir: staging_dir = os.path.normpath( tempfile.mkdtemp( From 7a7102337bfe73ab7d7e8e805e681a7ec743fd40 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 15:19:38 +0000 Subject: [PATCH 164/356] BigRoy feedback --- .../hosts/maya/plugins/publish/extract_workfile_xgen.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index b95add3306..c8d0d63344 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -105,7 +105,6 @@ class ExtractWorkfileXgen(publish.Extractor): # Extract patches alembic. path_no_ext, _ = os.path.splitext(instance.context.data["currentFile"]) - dirname = os.path.dirname(instance.context.data["currentFile"]) kwargs = {"attrPrefix": ["xgen"], "stripNamespaces": True} alembic_files = [] for palette in cmds.ls(type="xgmPalette"): @@ -114,11 +113,8 @@ class ExtractWorkfileXgen(publish.Extractor): for name in xgenm.boundGeometry(palette, description): patch_names.append(name) - alembic_file = os.path.join( - dirname, - "{}__{}.abc".format( - path_no_ext, palette.replace(":", "__ns__") - ) + alembic_file = "{}__{}.abc".format( + path_no_ext, palette.replace(":", "__ns__") ) extract_alembic( alembic_file, From 76ab705e0c33aa18b009725896d1375b5a9432a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 1 Feb 2023 16:25:52 +0100 Subject: [PATCH 165/356] added documenation --- website/docs/admin_settings_system.md | 38 ++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 8aeb281109..39b58e6f81 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -13,18 +13,44 @@ Settings applicable to the full studio. ![general_settings](assets/settings/settings_system_general.png) -**`Studio Name`** - Full name of the studio (can be used as variable on some places) +### Studio Name + - Full name of the studio (can be used as variable on some places) -**`Studio Code`** - Studio acronym or a short code (can be used as variable on some places) +### Studio Code + - Studio acronym or a short code (can be used as variable on some places) -**`Admin Password`** - After setting admin password, normal user won't have access to OpenPype settings +### Admin Password + - After setting admin password, normal user won't have access to OpenPype settings and Project Manager GUI. Please keep in mind that this is a studio wide password and it is meant purely as a simple barrier to prevent artists from accidental setting changes. -**`Environment`** - Globally applied environment variables that will be appended to any OpenPype process in the studio. +### Environment + - Globally applied environment variables that will be appended to any OpenPype process in the studio. + - OpenPype is using some keys to configure some tools. Here are some: -**`Disk mapping`** - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. -Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). +#### OPENPYPE_TMPDIR: + - Custom staging dir directory + - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` + - supported formating keys: + - root[work] + - project[name | code] + - asset + - hierarchy + - task + - username + - app + +#### OPENPYPE_DEBUG + - setting logger to debug mode + - example value: "1" (to activate) + +#### OPENPYPE_LOG_LEVEL + - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) + - example value: "10" + +### Disk mapping +- Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. +- Uses `subst` command, if configured volume character in `Destination` field already exists, no re-mapping is done for that character(volume). ### FFmpeg and OpenImageIO tools We bundle FFmpeg tools for all platforms and OpenImageIO tools for Windows and Linux. By default, bundled tools are used, but it is possible to set environment variables `OPENPYPE_FFMPEG_PATHS` and `OPENPYPE_OIIO_PATHS` in system settings environments to look for them in different directory. From fce85d069471f9e5364e41b147f9ecabd1703af4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 17:22:40 +0100 Subject: [PATCH 166/356] check ftrack url before adding ftrackapp --- openpype/modules/ftrack/ftrack_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 6f14f8428d..853b7999da 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -510,7 +510,10 @@ def resolve_ftrack_url(url, logger=None): url = "https://" + url ftrack_url = None - if not url.endswith("ftrackapp.com"): + if url and _check_ftrack_url(url): + ftrack_url = url + + if not ftrack_url and not url.endswith("ftrackapp.com"): ftrackapp_url = url + ".ftrackapp.com" if _check_ftrack_url(ftrackapp_url): ftrack_url = ftrackapp_url From a2dbc6d51ddfdbdf27cca40472c46336d146eb86 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 17:22:53 +0100 Subject: [PATCH 167/356] added option to receive ftrack url from settings --- openpype/modules/ftrack/ftrack_module.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 853b7999da..269ec726ee 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -73,8 +73,19 @@ class FtrackModule( ftrack_url = property(get_ftrack_url) + @property + def settings_ftrack_url(self): + """Ftrack url from settings in a format as it is. + + Returns: + str: Ftrack url from settings. + """ + + return self._settings_ftrack_url + def get_global_environments(self): """Ftrack's global environments.""" + return { "FTRACK_SERVER": self.ftrack_url } From b661f16f88610fcbd02d2e7db01dcc4945f128e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 17:23:11 +0100 Subject: [PATCH 168/356] added docstring for 'get_ftrack_url' --- openpype/modules/ftrack/ftrack_module.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 269ec726ee..d61b5f0b26 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -64,6 +64,16 @@ class FtrackModule( self._timers_manager_module = None def get_ftrack_url(self): + """Resolved ftrack url. + + Resolving is trying to fill missing information in url and tried to + connect to the server. + + Returns: + Union[str, None]: Final variant of url or None if url could not be + reached. + """ + if self._ftrack_url is _URL_NOT_SET: self._ftrack_url = resolve_ftrack_url( self._settings_ftrack_url, From 7e213daca01dddfa3b0cf8d87d8870c4bf537f8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 17:23:38 +0100 Subject: [PATCH 169/356] login url is not modifying the url from module --- openpype/modules/ftrack/tray/login_dialog.py | 27 ++++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py index fbb3455775..0e676545f7 100644 --- a/openpype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -139,8 +139,7 @@ class CredentialsDialog(QtWidgets.QDialog): self.fill_ftrack_url() def fill_ftrack_url(self): - url = os.getenv("FTRACK_SERVER") - checked_url = self.check_url(url) + checked_url = self.check_url() if checked_url == self.ftsite_input.text(): return @@ -154,7 +153,7 @@ class CredentialsDialog(QtWidgets.QDialog): self.api_input.setEnabled(enabled) self.user_input.setEnabled(enabled) - if not url: + if not checked_url: self.btn_advanced.hide() self.btn_simple.hide() self.btn_ftrack_login.hide() @@ -254,7 +253,7 @@ class CredentialsDialog(QtWidgets.QDialog): ) def _on_ftrack_login_clicked(self): - url = self.check_url(self.ftsite_input.text()) + url = self.check_url() if not url: return @@ -302,21 +301,21 @@ class CredentialsDialog(QtWidgets.QDialog): if is_logged is not None: self.set_is_logged(is_logged) - def check_url(self, url): - if url is not None: - url = url.strip("/ ") - - if not url: + def check_url(self): + settings_url = self._module.settings_ftrack_url + url = self._module.ftrack_url + if not settings_url: self.set_error( "Ftrack URL is not defined in settings!" ) return - if "http" not in url: - if url.endswith("ftrackapp.com"): - url = "https://" + url - else: - url = "https://{}.ftrackapp.com".format(url) + if url is None: + self.set_error( + "Specified URL does not lead to a valid Ftrack server." + ) + return + try: result = requests.get( url, From dc656050b7184c965774e289c5827c735533ab5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 18:27:44 +0100 Subject: [PATCH 170/356] remove api.pi in openpype root --- openpype/api.py | 112 ------------------------------------------------ 1 file changed, 112 deletions(-) delete mode 100644 openpype/api.py diff --git a/openpype/api.py b/openpype/api.py deleted file mode 100644 index b60cd21d2b..0000000000 --- a/openpype/api.py +++ /dev/null @@ -1,112 +0,0 @@ -from .settings import ( - get_system_settings, - get_project_settings, - get_current_project_settings, - get_anatomy_settings, - - SystemSettings, - ProjectSettings -) -from .lib import ( - PypeLogger, - Logger, - Anatomy, - execute, - run_subprocess, - version_up, - get_asset, - get_workdir_data, - get_version_from_path, - get_last_version_from_path, - get_app_environments_for_context, - source_hash, - get_latest_version, - get_local_site_id, - change_openpype_mongo_url, - create_project_folders, - get_project_basic_paths -) - -from .lib.mongo import ( - get_default_components -) - -from .lib.applications import ( - ApplicationManager -) - -from .lib.avalon_context import ( - BuildWorkfile -) - -from . import resources - -from .plugin import ( - Extractor, - - ValidatePipelineOrder, - ValidateContentsOrder, - ValidateSceneOrder, - ValidateMeshOrder, -) - -# temporary fix, might -from .action import ( - get_errored_instances_from_context, - RepairAction, - RepairContextAction -) - - -__all__ = [ - "get_system_settings", - "get_project_settings", - "get_current_project_settings", - "get_anatomy_settings", - "get_project_basic_paths", - - "SystemSettings", - "ProjectSettings", - - "PypeLogger", - "Logger", - "Anatomy", - "execute", - "get_default_components", - "ApplicationManager", - "BuildWorkfile", - - # Resources - "resources", - - # plugin classes - "Extractor", - # ordering - "ValidatePipelineOrder", - "ValidateContentsOrder", - "ValidateSceneOrder", - "ValidateMeshOrder", - # action - "get_errored_instances_from_context", - "RepairAction", - "RepairContextAction", - - # get contextual data - "version_up", - "get_asset", - "get_workdir_data", - "get_version_from_path", - "get_last_version_from_path", - "get_app_environments_for_context", - "source_hash", - - "run_subprocess", - "get_latest_version", - - "get_local_site_id", - "change_openpype_mongo_url", - - "get_project_basic_paths", - "create_project_folders" - -] From 14ecf3aec18b2f89d6e0a53119d7e4adff3d5c84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 18:33:32 +0100 Subject: [PATCH 171/356] remove last usage of openpype.api --- openpype/hosts/nuke/plugins/publish/collect_context_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_context_data.py b/openpype/hosts/nuke/plugins/publish/collect_context_data.py index 5a1cdcf49e..b487c946f0 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_context_data.py +++ b/openpype/hosts/nuke/plugins/publish/collect_context_data.py @@ -1,7 +1,7 @@ import os import nuke import pyblish.api -import openpype.api as api +from openpype.lib import get_version_from_path import openpype.hosts.nuke.api as napi from openpype.pipeline import KnownPublishError @@ -57,7 +57,7 @@ class CollectContextData(pyblish.api.ContextPlugin): "fps": root_node['fps'].value(), "currentFile": current_file, - "version": int(api.get_version_from_path(current_file)), + "version": int(get_version_from_path(current_file)), "host": pyblish.api.current_host(), "hostVersion": nuke.NUKE_VERSION_STRING From 8fd4c06e712c9b8ffb606de5784e4172a4b0eaf3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 1 Feb 2023 18:33:33 +0100 Subject: [PATCH 172/356] :heavy_minus_sign: pop QT_AUTO_SCREEN_SCALE_FACTOR --- openpype/hosts/max/addon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/max/addon.py b/openpype/hosts/max/addon.py index d3245bbc7e..9d6ab5a8b3 100644 --- a/openpype/hosts/max/addon.py +++ b/openpype/hosts/max/addon.py @@ -12,6 +12,11 @@ class MaxAddon(OpenPypeModule, IHostAddon): def initialize(self, module_settings): self.enabled = True + def add_implementation_envs(self, env, _app): + # Remove auto screen scale factor for Qt + # - let 3dsmax decide it's value + env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None) + def get_workfile_extensions(self): return [".max"] From 6934233c25512bd9ace0e954a51e67aea3246d12 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 19:04:14 +0100 Subject: [PATCH 173/356] skip lock check if in untitled scene --- openpype/hosts/maya/api/pipeline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 3798170671..7f31001cd0 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -514,6 +514,9 @@ def check_lock_on_current_file(): # add the lock file when opening the file filepath = current_file() + # Skip if current file is 'untitled' + if not filepath: + return if is_workfile_locked(filepath): # add lockfile dialog From fc2fd70f09b5d8024ee154203748b6ede94afb99 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 1 Feb 2023 19:04:33 +0100 Subject: [PATCH 174/356] remove unnecessary indentation --- openpype/hosts/maya/api/pipeline.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 7f31001cd0..5323717fa7 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -683,10 +683,12 @@ def before_workfile_save(event): def after_workfile_save(event): workfile_name = event["filename"] - if handle_workfile_locks(): - if workfile_name: - if not is_workfile_locked(workfile_name): - create_workfile_lock(workfile_name) + if ( + handle_workfile_locks() + and workfile_name + and not is_workfile_locked(workfile_name) + ): + create_workfile_lock(workfile_name) class MayaDirmap(HostDirmap): From e0f18363cd2017fecab4dae2eceb5f1029c15f21 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 20:57:47 +0000 Subject: [PATCH 175/356] Fix file paths. --- openpype/hosts/maya/plugins/load/load_xgen.py | 6 ++---- .../hosts/maya/plugins/publish/extract_workfile_xgen.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 5110d4ca05..fc86596208 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -73,9 +73,8 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # to ensure paths are setup correctly. project_path = os.path.dirname(current_file()).replace("\\", "/") xgenm.setAttr("xgProjectPath", project_path, xgen_palette) - data_path = "${{PROJECT}}xgen/collections/{}{}{}".format( + data_path = "${{PROJECT}}xgen/collections/{};{}".format( xgen_palette.replace(":", "__ns__"), - os.pathsep, xgenm.getAttr("xgDataPath", xgen_palette) ) xgenm.setAttr("xgDataPath", data_path, xgen_palette) @@ -156,9 +155,8 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): break project_path = os.path.dirname(current_file()).replace("\\", "/") - data_path = "${{PROJECT}}xgen/collections/{}{}{}".format( + data_path = "${{PROJECT}}xgen/collections/{};{}".format( xgen_palette.replace(":", "__ns__"), - os.pathsep, data_path ) data = {"xgProjectPath": project_path, "xgDataPath": data_path} diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index c8d0d63344..20e1bd37d8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -208,7 +208,7 @@ class ExtractWorkfileXgen(publish.Extractor): project_path = xgenm.getAttr("xgProjectPath", palette) data_path = xgenm.getAttr("xgDataPath", palette) data_path = data_path.replace("${PROJECT}", project_path) - for path in data_path.split(os.pathsep): + for path in data_path.split(";"): for root, _, files in os.walk(path): for f in files: source = os.path.join(root, f) From 926f23d39275ece8fbdd04b97541cf874e9b22cd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 20:57:53 +0000 Subject: [PATCH 176/356] Fix updating --- openpype/hosts/maya/api/plugin.py | 33 +++++++++++++++++++ openpype/hosts/maya/plugins/load/load_xgen.py | 31 ----------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 82df85a8be..916fddd923 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -300,6 +300,39 @@ class ReferenceLoader(Loader): str(representation["_id"]), type="string") + # When an animation or pointcache gets connected to an Xgen container, + # the compound attribute "xgenContainers" gets created. When animation + # containers gets updated we also need to update the cacheFileName on + # the Xgen collection. + compound_name = "xgenContainers" + if cmds.objExists("{}.{}".format(node, compound_name)): + import xgenm + container_amount = cmds.getAttr( + "{}.{}".format(node, compound_name), size=True + ) + # loop through all compound children + for i in range(container_amount): + attr = "{}.{}[{}].container".format(node, compound_name, i) + objectset = cmds.listConnections(attr)[0] + reference_node = cmds.sets(objectset, query=True)[0] + palettes = cmds.ls( + cmds.referenceQuery(reference_node, nodes=True), + type="xgmPalette" + ) + for palette in palettes: + for description in xgenm.descriptions(palette): + xgenm.setAttr( + "cacheFileName", + path.replace("\\", "/"), + palette, + description, + "SplinePrimitive" + ) + + # Refresh UI and viewport. + de = xgenm.xgGlobal.DescriptionEditor + de.refresh("Full") + def remove(self, container): """Remove an existing `container` from Maya scene diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index fc86596208..1600cd49bd 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -171,34 +171,3 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): super().update(container, representation) xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) - - # Update any xgen containers. - compound_name = "xgenContainers" - import xgenm - container_amount = cmds.getAttr( - "{}.{}".format(container_node, compound_name), size=True - ) - # loop through all compound children - for i in range(container_amount): - attr = "{}.{}[{}].container".format( - container_node, compound_name, i - ) - objectset = cmds.listConnections(attr)[0] - reference_node = cmds.sets(objectset, query=True)[0] - palettes = cmds.ls( - cmds.referenceQuery(reference_node, nodes=True), - type="xgmPalette" - ) - for palette in palettes: - for description in xgenm.descriptions(palette): - xgenm.setAttr( - "cacheFileName", - maya_file.replace("\\", "/"), - palette, - description, - "SplinePrimitive" - ) - - # Refresh UI and viewport. - de = xgenm.xgGlobal.DescriptionEditor - de.refresh("Full") From 7217ba6b770d03c77736ba7e72cb2e04ff3b8dff Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Thu, 2 Feb 2023 09:06:01 +0100 Subject: [PATCH 177/356] added pic for systray icon --- website/docs/artist_getting_started.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/website/docs/artist_getting_started.md b/website/docs/artist_getting_started.md index 14951e599d..301a58fa56 100644 --- a/website/docs/artist_getting_started.md +++ b/website/docs/artist_getting_started.md @@ -17,7 +17,7 @@ If this is not the case, please contact your administrator to consult on how to ## Working from home If you are working from **home** though, you'll **need to install** it yourself. You should, however, receive the OpenPype installer files from your studio -admin, supervisor or production, because OpenPype versions and executables might not be compatible between studios. +admin, supervisor or production, because OpenPype versions and executables might not be compatible between studios. Installing OpenPype is possible by Installer or by unzipping downloaded ZIP archive to any drive location. @@ -25,7 +25,6 @@ Installing OpenPype is possible by Installer or by unzipping downloaded ZIP arch See the [Installation section](artist_install.md) for more information on how to use the OpenPype Installer ::: ---- You can run OpenPype by desktop "OP" icon (if it exists after installing) or by directly executing @@ -33,12 +32,15 @@ You can run OpenPype by desktop "OP" icon (if it exists after installing) or by or alternatively by -**openpype_console.exe** which is more suitable for **TDs/Admin** for debugging and error reporting. This one runs with opened console window where all the necessary info will appear during user's work session. +**openpype_console.exe** which is more suitable for **TDs/Admin** for debugging and error reporting. This one runs with +opened console window where all the necessary info will appear during user's work session. :::tip Is OpenPype running? -OpenPype runs in the operating system's tray. If you see the OpenPype icon in the tray you can easily tell OpenPype is currently running. +OpenPype runs in the operating system's tray. If you see turquoise OpenPype icon in the tray you can easily tell OpenPype is currently running. +Keep in mind that on Windows this icon might be hidden by default, in which case, the artist can simply drag the icon down to the tray. ::: +![Systray](assets/artist_systray.png) ## First Launch @@ -49,7 +51,7 @@ When you first start OpenPype, you will be asked to fill in some basic informati ### MongoDB In most cases you will only have to supply the MongoDB Address. -It's the database URL you should have received from your Studio admin and often will look like this +It's the database URL you should have received from your Studio admin and often will look like this `mongodb://username:passwword@mongo.mystudiodomain.com:12345` @@ -64,14 +66,14 @@ asks for it, just put it in the corresponding text field and press `install` but Sometimes your Studio might also ask you to fill in the path to its version repository. This is a location where OpenPype will search for the latest versions, check -if it's up to date and where updates are installed from automatically. +if it's up to date and where updates are installed from automatically. -This path is usually taken from the database directly, so you shouldn't need it. +This path is usually taken from the database directly, so you shouldn't need it. ## Updates -If you're connected to your Studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start it can go through a quick update installation process, even though you might have just installed it. +If you're connected to your Studio, OpenPype will check for, and install updates automatically every time you run it. That's why during the first start it can go through a quick update installation process, even though you might have just installed it. ## Advanced Usage From df6031d810a8d3e6c222087f5149e86cc84d4a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:40:06 +0100 Subject: [PATCH 178/356] Update openpype/hosts/max/startup/startup.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/max/startup/startup.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/startup/startup.py b/openpype/hosts/max/startup/startup.py index 21da115baa..0d3135a16f 100644 --- a/openpype/hosts/max/startup/startup.py +++ b/openpype/hosts/max/startup/startup.py @@ -4,17 +4,12 @@ import sys # this might happen in some 3dsmax version where PYTHONPATH isn't added # to sys.path automatically -try: - from openpype.hosts.max.api import MaxHost - from openpype.pipeline import install_host -except (ImportError, ModuleNotFoundError): +for path in os.environ["PYTHONPATH"].split(os.pathsep): + if path and path not in sys.path: + sys.path.append(path) - for path in os.environ["PYTHONPATH"].split(os.pathsep): - if path and path not in sys.path: - sys.path.append(path) - - from openpype.hosts.max.api import MaxHost - from openpype.pipeline import install_host +from openpype.hosts.max.api import MaxHost +from openpype.pipeline import install_host host = MaxHost() install_host(host) From 0dbb63df944d81d7318f0ea503f00cf7cca1f48d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 11:54:55 +0100 Subject: [PATCH 179/356] modified change item a little --- openpype/pipeline/create/context.py | 324 ++++++++++++++++++++++------ 1 file changed, 254 insertions(+), 70 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 59314b9236..52e43f500e 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -183,92 +183,169 @@ def prepare_failed_creator_operation_info( } -class ChangedItem(object): +_EMPTY_VALUE = object() + + +class TrackChangesItem(object): + """Helper object to track changes in data. + + Has access to full old and new data and will create deep copy of them, + so it is not needed to create copy before passed in. + + Can work as a dictionary if old or new value is a dictionary. In + that case received object is another object of 'TrackChangesItem'. + + Goal is to be able to get old or new value as was or only changed values + or get information about removed/changed keys, and all of that on + any "dictionary level". + + ``` + # Example of possible usages + old_value = { + "key_1": "value_1", + "key_2": { + "key_sub_1": 1, + "key_sub_2": { + "enabled": True + } + }, + "key_3": "value_2" + } + + new_value = { + "key_1": "value_1", + "key_2": { + "key_sub_2": { + "enabled": False + }, + "key_sub_3": 3 + }, + "key_3": "value_3" + } + + changes = TrackChangesItem(old_value, new_value) + print(changes.changed) + >>> True + print(changes.changed_keys) + >>> {"key_2", "key_3"} + print(changes["key_2"]["key_sub_2"]["enabled"].changed) + >>> True + print(changes["key_2"].removed_keys) + >>> {"key_sub_1"} + print(changes["key_2"].available_keys) + >>> {"key_sub_1", "key_sub_2", "key_sub_3"} + print(changes.new_value == new_value) + >>> True + + only_changed_new_values = { + key: changes[key].new_value + for key in changes + } + ``` + + Args: + old_value (Any): Previous value. + new_value (Any): New value. + """ + def __init__(self, old_value, new_value): + self._changed = old_value != new_value + if old_value is _EMPTY_VALUE: + old_value = None + if new_value is _EMPTY_VALUE: + new_value = None self._old_value = copy.deepcopy(old_value) self._new_value = copy.deepcopy(new_value) - self._changed = self._old_value != self._new_value - old_is_dict = isinstance(old_value, dict) - new_is_dict = isinstance(new_value, dict) - children = {} - changed_keys = set() - available_keys = set() - new_keys = set() - old_keys = set() - if old_is_dict and new_is_dict: - old_keys = set(old_value.keys()) - new_keys = set(new_value.keys()) - available_keys = old_keys | new_keys - for key in available_keys: - item = ChangedItem( - old_value.get(key), new_value.get(key) - ) - children[key] = item - if item.changed or key not in old_keys or key not in new_keys: - changed_keys.add(key) + self._old_is_dict = isinstance(old_value, dict) + self._new_is_dict = isinstance(new_value, dict) - elif old_is_dict: - old_keys = set(old_value.keys()) - available_keys = set(old_keys) - changed_keys = set(available_keys) - for key in available_keys: - children[key] = ChangedItem(old_value.get(key), None) + self._old_keys = None + self._new_keys = None + self._available_keys = None + self._removed_keys = None - elif new_is_dict: - new_keys = set(new_value.keys()) - available_keys = set(new_keys) - changed_keys = set(available_keys) - for key in available_keys: - children[key] = ChangedItem(None, new_value.get(key)) + self._changed_keys = None - self._changed_keys = changed_keys - self._available_keys = available_keys - self._children = children - self._old_is_dict = old_is_dict - self._new_is_dict = new_is_dict - self._old_keys = old_keys - self._new_keys = new_keys + self._sub_items = None def __getitem__(self, key): - return self._children[key] + """Getter looks into subitems if object is dictionary.""" + + if self._sub_items is None: + self._prepare_sub_items() + return self._sub_items[key] def __bool__(self): + """Boolean of object is if old and new value are the same.""" + return self._changed - def __iter__(self): - for key in self.changed_keys: - yield key - def get(self, key, default=None): - return self._children.get(key, default) + """Try to get sub item.""" - def keys(self): - return self.changed_keys + if self._sub_items is None: + self._prepare_sub_items() + return self._sub_items.get(key, default) - def items(self): - if not self.is_dict: - yield None, self.changes - else: - for item in self.changes.items(): - yield item + @property + def old_value(self): + """Get copy of old value. + + Returns: + Any: Whatever old value was. + """ + + return copy.deepcopy(self._old_value) + + @property + def new_value(self): + """Get copy of new value. + + Returns: + Any: Whatever new value was. + """ + + return copy.deepcopy(self._new_value) @property def changed(self): + """Value changed. + + Returns: + bool: If data changed. + """ + return self._changed @property def is_dict(self): + """Object can be used as dictionary. + + Returns: + bool: When can be used that way. + """ + return self._old_is_dict or self._new_is_dict @property def changes(self): + """Get changes in raw data. + + This method should be used only if 'is_dict' value is 'True'. + + Returns: + Dict[str, Tuple[Any, Any]]: Changes are by key in tuple + (, ). If 'is_dict' is 'False' then + output is always empty dictionary. + """ + + output = {} if not self.is_dict: - return (self.old_value, self.new_value) + return output old_value = self.old_value new_value = self.new_value - output = {} for key in self.changed_keys: _old = None _new = None @@ -279,29 +356,135 @@ class ChangedItem(object): output[key] = (_old, _new) return output - @property - def changed_keys(self): - return set(self._changed_keys) - - @property - def available_keys(self): - return set(self._available_keys) - + # Methods/properties that can be used when 'is_dict' is 'True' @property def old_keys(self): + """Keys from old value. + + Empty set is returned if old value is not a dict. + + Returns: + Set[str]: Keys from old value. + """ + + if self._old_keys is None: + self._prepare_keys() return set(self._old_keys) @property def new_keys(self): + """Keys from new value. + + Empty set is returned if old value is not a dict. + + Returns: + Set[str]: Keys from new value. + """ + + if self._new_keys is None: + self._prepare_keys() return set(self._new_keys) @property - def old_value(self): - return copy.deepcopy(self._old_value) + def changed_keys(self): + """Keys that has changed from old to new value. + + Empty set is returned if both old and new value are not a dict. + + Returns: + Set[str]: Keys of changed keys. + """ + + if self._changed_keys is None: + self._prepare_sub_items() + return set(self._changed_keys) @property - def new_value(self): - return copy.deepcopy(self._new_value) + def available_keys(self): + """All keys that are available in old and new value. + + Empty set is returned if both old and new value are not a dict. + Output it is Union of 'old_keys' and 'new_keys'. + + Returns: + Set[str]: All keys from old and new value. + """ + + if self._available_keys is None: + self._prepare_keys() + return set(self._available_keys) + + @property + def removed_keys(self): + """Key that are not available in new value but were in old value. + + Returns: + Set[str]: All removed keys. + """ + + if self._removed_keys is None: + self._prepare_sub_items() + return set(self._removed_keys) + + def _prepare_keys(self): + old_keys = set() + new_keys = set() + if self._old_is_dict and self._new_is_dict: + old_keys = set(self._old_value.keys()) + new_keys = set(self._new_value.keys()) + + elif self._old_is_dict: + old_keys = set(self._old_value.keys()) + + elif self._new_is_dict: + new_keys = set(self._new_value.keys()) + + self._old_keys = old_keys + self._new_keys = new_keys + self._available_keys = old_keys | new_keys + self._removed_keys = old_keys - new_keys + + def _prepare_sub_items(self): + sub_items = {} + changed_keys = set() + + old_keys = self.old_keys + new_keys = self.new_keys + new_value = self.new_value + old_value = self.old_value + if self._old_is_dict and self._new_is_dict: + for key in self.available_keys: + item = TrackChangesItem( + old_value.get(key), new_value.get(key) + ) + sub_items[key] = item + if item.changed or key not in old_keys or key not in new_keys: + changed_keys.add(key) + + elif self._old_is_dict: + old_keys = set(old_value.keys()) + available_keys = set(old_keys) + changed_keys = set(available_keys) + for key in available_keys: + # NOTE Use '_EMPTY_VALUE' because old value could be 'None' + # which would result in "unchanged" item + sub_items[key] = TrackChangesItem( + old_value.get(key), _EMPTY_VALUE + ) + + elif self._new_is_dict: + new_keys = set(new_value.keys()) + available_keys = set(new_keys) + changed_keys = set(available_keys) + for key in available_keys: + # NOTE Use '_EMPTY_VALUE' because new value could be 'None' + # which would result in "unchanged" item + sub_items[key] = TrackChangesItem( + _EMPTY_VALUE, new_value.get(key) + ) + + self._sub_items = sub_items + self._changed_keys = changed_keys class InstanceMember: @@ -895,7 +1078,7 @@ class CreatedInstance: def changes(self): """Calculate and return changes.""" - return ChangedItem(self.origin_data, self.data_to_store()) + return TrackChangesItem(self._orig_data, self.data_to_store()) def mark_as_stored(self): """Should be called when instance data are stored. @@ -1519,8 +1702,9 @@ class CreateContext: def context_data_changes(self): """Changes of attributes.""" - old_value = copy.deepcopy(self._original_context_data) - return ChangedItem(old_value, self.context_data_to_store()) + return TrackChangesItem( + self._original_context_data, self.context_data_to_store() + ) def creator_adds_instance(self, instance): """Creator adds new instance to context. From 6ca987fdd259981f05bfcf0e2717a6e91682680e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 11:56:51 +0100 Subject: [PATCH 180/356] modified houdini to use new changes object --- openpype/hosts/houdini/api/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 61127bda57..0b9940a993 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -225,12 +225,12 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): self._add_instance_to_context(created_instance) def update_instances(self, update_list): - for created_inst, _changes in update_list: + for created_inst, changes in update_list: instance_node = hou.node(created_inst.get("instance_node")) new_values = { - key: new_value - for key, (_old_value, new_value) in _changes.items() + key: changes[key].new_value + for key in changes } imprint( instance_node, From 0b65168688be847861d89b4e12547608f30c627c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 12:17:07 +0100 Subject: [PATCH 181/356] fix other places using changes --- openpype/hosts/aftereffects/plugins/create/create_render.py | 4 ++-- openpype/hosts/max/api/plugin.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 8d38288257..5427edb44b 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -47,10 +47,10 @@ class RenderCreator(Creator): for created_inst, _changes in update_list: api.get_stub().imprint(created_inst.get("instance_id"), created_inst.data_to_store()) - subset_change = _changes.get("subset") + subset_change = _changes["subset"] if subset_change: api.get_stub().rename_item(created_inst.data["members"][0], - subset_change[1]) + subset_change.new_value) def remove_instances(self, instances): for instance in instances: diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 4788bfd383..55603b26a0 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -78,12 +78,12 @@ class MaxCreator(Creator, MaxCreatorBase): self._add_instance_to_context(created_instance) def update_instances(self, update_list): - for created_inst, _changes in update_list: + for created_inst, changes in update_list: instance_node = created_inst.get("instance_node") new_values = { - key: new_value - for key, (_old_value, new_value) in _changes.items() + key: changes[key].new_value + for key in changes } imprint( instance_node, From e1f11abdf4d3beefedfffa56f21557d17cf1e953 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 12:41:33 +0100 Subject: [PATCH 182/356] use AVALON_TASK to get current task name --- openpype/host/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/host/host.py b/openpype/host/host.py index 28d0a21b34..d2335c0062 100644 --- a/openpype/host/host.py +++ b/openpype/host/host.py @@ -123,7 +123,7 @@ class HostBase(object): Union[str, None]: Current task name. """ - return os.environ.get("AVALON_ASSET") + return os.environ.get("AVALON_TASK") def get_current_context(self): """Get current context information. From b83f0ac79948a26f1b757c0b965e4c138c271a11 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 2 Feb 2023 12:46:49 +0100 Subject: [PATCH 183/356] restore file as it was originally --- openpype/pipeline/workfile/workfile_template_builder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index fd5ac16579..42d8cc82e1 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -525,7 +525,6 @@ class AbstractTemplateBuilder(object): placeholder.scene_identifier: placeholder for placeholder in placeholders } - all_processed = len(placeholders) == 0 # Counter is checked at the ned of a loop so the loop happens at least # once. @@ -566,7 +565,8 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() - # self.clear_shared_populate_data() + # Clear shared data before getting new placeholders + self.clear_shared_populate_data() iter_counter += 1 if iter_counter >= level_limit: @@ -1003,7 +1003,7 @@ class PlaceholderItem(object): return self._log def __repr__(self): - return "< {} {} >".format(self.__class__.__name__, self.data['family']) + return "< {} {} >".format(self.__class__.__name__, self.name) @property def order(self): From 77b55cca517705108e8d8ef7e0c0a577131309c3 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 2 Feb 2023 12:46:49 +0100 Subject: [PATCH 184/356] restore file as it was originally --- openpype/pipeline/workfile/workfile_template_builder.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index fd5ac16579..582657c735 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -525,7 +525,6 @@ class AbstractTemplateBuilder(object): placeholder.scene_identifier: placeholder for placeholder in placeholders } - all_processed = len(placeholders) == 0 # Counter is checked at the ned of a loop so the loop happens at least # once. @@ -566,14 +565,14 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() - # self.clear_shared_populate_data() + # Clear shared data before getting new placeholders + self.clear_shared_populate_data() iter_counter += 1 if iter_counter >= level_limit: break all_processed = True - collected_placeholders = self.get_placeholders() for placeholder in collected_placeholders: identifier = placeholder.scene_identifier @@ -1003,7 +1002,7 @@ class PlaceholderItem(object): return self._log def __repr__(self): - return "< {} {} >".format(self.__class__.__name__, self.data['family']) + return "< {} {} >".format(self.__class__.__name__, self.name) @property def order(self): From f7fd0a53041cfa9c5a789ededd6b94dc56ba8d72 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 13:01:24 +0100 Subject: [PATCH 185/356] OP-4513 - fix for DL on MacOS This works if DL Openpype plugin Installation Directories is set to level of app bundle (eg. '/Applications/OpenPype 3.15.0.app') --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 984590ddba..33d548d204 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -157,7 +157,7 @@ def get_openpype_version_from_path(path, build=True): # fix path for application bundle on macos if platform.system().lower() == "darwin": - path = os.path.join(path, "Contents", "MacOS", "lib", "Python") + path = os.path.join(path, "MacOS") version_file = os.path.join(path, "openpype", "version.py") if not os.path.isfile(version_file): From 638375c9db7caca607375ed8b3f29eba76e5349d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 13:47:15 +0100 Subject: [PATCH 186/356] add missing attribute usage --- openpype/hosts/houdini/api/plugin.py | 2 +- openpype/hosts/max/api/plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 0b9940a993..f0985973a6 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -230,7 +230,7 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): new_values = { key: changes[key].new_value - for key in changes + for key in changes.changed_keys } imprint( instance_node, diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 55603b26a0..c16d9e61ec 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -83,7 +83,7 @@ class MaxCreator(Creator, MaxCreatorBase): new_values = { key: changes[key].new_value - for key in changes + for key in changes.changed_keys } imprint( instance_node, From a9e139998ca49b7dc6135858333923f223a92c3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 2 Feb 2023 15:00:56 +0100 Subject: [PATCH 187/356] modified code example to also contain tests --- openpype/pipeline/create/context.py | 76 ++++++++++++++++------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 52e43f500e..f46b4eccdb 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -201,45 +201,53 @@ class TrackChangesItem(object): ``` # Example of possible usages - old_value = { - "key_1": "value_1", - "key_2": { - "key_sub_1": 1, - "key_sub_2": { - "enabled": True - } - }, - "key_3": "value_2" - } + >>> old_value = { + ... "key_1": "value_1", + ... "key_2": { + ... "key_sub_1": 1, + ... "key_sub_2": { + ... "enabled": True + ... } + ... }, + ... "key_3": "value_2" + ... } + >>> new_value = { + ... "key_1": "value_1", + ... "key_2": { + ... "key_sub_2": { + ... "enabled": False + ... }, + ... "key_sub_3": 3 + ... }, + ... "key_3": "value_3" + ... } - new_value = { - "key_1": "value_1", - "key_2": { - "key_sub_2": { - "enabled": False - }, - "key_sub_3": 3 - }, - "key_3": "value_3" - } + >>> changes = TrackChangesItem(old_value, new_value) + >>> changes.changed + True - changes = TrackChangesItem(old_value, new_value) - print(changes.changed) - >>> True - print(changes.changed_keys) - >>> {"key_2", "key_3"} - print(changes["key_2"]["key_sub_2"]["enabled"].changed) - >>> True - print(changes["key_2"].removed_keys) - >>> {"key_sub_1"} - print(changes["key_2"].available_keys) - >>> {"key_sub_1", "key_sub_2", "key_sub_3"} - print(changes.new_value == new_value) - >>> True + >>> changes["key_2"]["key_sub_1"].new_value is None + True + >>> list(sorted(changes.changed_keys)) + ['key_2', 'key_3'] + + >>> changes["key_2"]["key_sub_2"]["enabled"].changed + True + + >>> changes["key_2"].removed_keys + {'key_sub_1'} + + >>> list(sorted(changes["key_2"].available_keys)) + ['key_sub_1', 'key_sub_2', 'key_sub_3'] + + >>> changes.new_value == new_value + True + + # Get only changed values only_changed_new_values = { key: changes[key].new_value - for key in changes + for key in changes.changed_keys } ``` From 4a963e9aac1dc3ce8ada72e006f81fb5a4bf3f21 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 16:10:35 +0100 Subject: [PATCH 188/356] OP-4513 - fix valid logic 0 is false, None check is safer --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 33d548d204..a5e48361c3 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -35,7 +35,7 @@ class OpenPypeVersion: self.prerelease = prerelease is_valid = True - if not major or not minor or not patch: + if major is None or minor is None or patch is None: is_valid = False self.is_valid = is_valid From d1a8744e5b1e72cf5323b58e2489e323967de8cb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 16:11:06 +0100 Subject: [PATCH 189/356] OP-4513 - better logging --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index a5e48361c3..82865ed714 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -218,8 +218,8 @@ def get_requested_openpype_executable( requested_version_obj = OpenPypeVersion.from_string(requested_version) if not requested_version_obj: print(( - ">>> Requested version does not match version regex \"{}\"" - ).format(VERSION_REGEX)) + ">>> Requested version '{}' does not match version regex '{}'" + ).format(requested_version, VERSION_REGEX)) return None print(( From 63912a0772b7ef6c00e134f3bdaaba8508bcd444 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 17:18:10 +0100 Subject: [PATCH 190/356] OP-4513 - fix selection of openpype_console on Mac --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 82865ed714..38eb163306 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -272,7 +272,8 @@ def get_requested_openpype_executable( # Deadline decide. exe_list = [ os.path.join(version_dir, "openpype_console.exe"), - os.path.join(version_dir, "openpype_console") + os.path.join(version_dir, "openpype_console"), + os.path.join(version_dir, "MacOS", "openpype_console") ] return FileUtils.SearchFileList(";".join(exe_list)) From 707c165f4f252afe4693ba71a0ae2dc9a74a62ea Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 17:29:37 +0100 Subject: [PATCH 191/356] OP-4513 - fix copy and paste artifact on MacOS clipboarch escapes whitespace with \ on MacOS, so for safety clean it here. Hopefully nobody starts folder name with space on Windows. --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 38eb163306..108c418e7b 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -189,6 +189,12 @@ def get_openpype_executable(): exe_list = config.GetConfigEntryWithDefault("OpenPypeExecutable", "") dir_list = config.GetConfigEntryWithDefault( "OpenPypeInstallationDirs", "") + + # clean '\ ' for MacOS pasting + if exe_list: + exe_list = exe_list.replace("\\ ", " ") + if dir_list: + dir_list = dir_list.replace("\\ ", " ") return exe_list, dir_list From 27773fbbf16fa8c9fa48678f1ec438d5f65f3b8d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 18:23:51 +0100 Subject: [PATCH 192/356] OP-4822 - added profile to disble check to Settings --- .../defaults/project_settings/deadline.json | 3 +- .../schema_project_deadline.json | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index ceb0b2e39a..11bb1aee2e 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -33,7 +33,8 @@ "limit": [], "jobInfo": {}, "pluginInfo": {}, - "scene_patches": [] + "scene_patches": [], + "disable_strict_check_profiles": [] }, "NukeSubmitDeadline": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 08a505bd47..d38358773a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -195,6 +195,36 @@ ] } + }, + { + "type": "list", + "collapsible": true, + "key": "disable_strict_check_profiles", + "label": "Disable Strict Error Check profiles", + "use_label_wrap": true, + "docstring": "Set profile for disabling 'Strict Error Checking'", + "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" + } + ] + } } ] }, From bd77b30c5c3fc3f21d8732d9309ec6c8f5d776f4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 18:25:24 +0100 Subject: [PATCH 193/356] OP-4822 - added possibility to disable strict checking DL by default has Strict error checking, but some errors are not fatal. This allows to set profile based on Task and Subset values to temporarily disable Strict Error Checks --- .../plugins/publish/submit_maya_deadline.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 070d4eab18..a59738979c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -35,6 +35,7 @@ from openpype.pipeline import legacy_io from openpype.hosts.maya.api.lib_rendersettings import RenderSettings from openpype.hosts.maya.api.lib import get_attr_in_layer +from openpype.lib.profiles_filtering import filter_profiles from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.tests.lib import is_in_tests @@ -64,6 +65,7 @@ class MayaPluginInfo(object): # Include all lights flag RenderSetupIncludeLights = attr.ib( default="1", validator=_validate_deadline_bool_value) + StrictErrorChecking = attr.ib(default="1") @attr.s @@ -104,6 +106,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): jobInfo = {} pluginInfo = {} group = "none" + disable_strict_check_profiles = [] def get_job_info(self): job_info = DeadlineJobInfo(Plugin="MayaBatch") @@ -219,6 +222,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "renderSetupIncludeLights", default_rs_include_lights) if rs_include_lights not in {"1", "0", True, False}: rs_include_lights = default_rs_include_lights + strict_checking = self._get_strict_checking(instance) plugin_info = MayaPluginInfo( SceneFile=self.scene_path, Version=cmds.about(version=True), @@ -227,6 +231,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): RenderSetupIncludeLights=rs_include_lights, # noqa ProjectPath=context.data["workspaceDir"], UsingRenderLayers=True, + StrictErrorChecking=strict_checking ) plugin_payload = attr.asdict(plugin_info) @@ -748,6 +753,32 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for file in exp: yield file + def _get_strict_checking(self, plugin_info, instance): + """Find profile to disable Strict Error Checking + + Args: + instance (dict) + Returns: + (int) - 1 if Strict (DL default), 0 for disabled Strict + """ + strict_checking = 1 + if not self.disable_strict_check_profiles: + return strict_checking + + task_data = instance.data["anatomyData"].get("task", {}) + key_values = { + "task_names": task_data.get("name"), + "task_types": task_data.get("type"), + "subsets": instance.data["subset"] + } + profile = filter_profiles(self.profiles, key_values, + logger=self.log) + + if profile: + strict_checking = 0 + + return strict_checking + def _format_tiles( filename, index, tiles_x, tiles_y, From f29c46f7c1cea1b0a9f3cf5b889394ab8bf03afd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 18:44:00 +0100 Subject: [PATCH 194/356] OP-4822 - fix arguments --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 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 a59738979c..a2a80c319b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -753,7 +753,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for file in exp: yield file - def _get_strict_checking(self, plugin_info, instance): + def _get_strict_checking(self, instance): """Find profile to disable Strict Error Checking Args: From 8a2cf2e6d93d1d9ed0887a84315e13608e859060 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 18:44:24 +0100 Subject: [PATCH 195/356] OP-4822 - fix wrong variable --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 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 a2a80c319b..c353e0b109 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -771,7 +771,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "task_types": task_data.get("type"), "subsets": instance.data["subset"] } - profile = filter_profiles(self.profiles, key_values, + profile = filter_profiles(self.disable_strict_check_profiles, + key_values, logger=self.log) if profile: From 5df388912abba15c5a477aa47f06807cce0cf8fb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 Feb 2023 18:44:43 +0100 Subject: [PATCH 196/356] OP-4822 - added logging --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index c353e0b109..3d1b9bcbf6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -776,6 +776,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): logger=self.log) if profile: + self.log.debug("Disabling Strict Error Checking") strict_checking = 0 return strict_checking From 6a3d981c0fa7d081bee4fe89fd8a0df77c456bdc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Feb 2023 20:45:12 +0000 Subject: [PATCH 197/356] Fix updating VrayProxy --- openpype/hosts/maya/plugins/load/load_vrayproxy.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index 720a132aa7..64184f9e7b 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -81,10 +81,11 @@ class VRayProxyLoader(load.LoaderPlugin): c = colors.get(family) if c is not None: cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1) - cmds.setAttr("{0}.outlinerColor".format(group_node), - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + cmds.setAttr( + "{0}.outlinerColor".format(group_node), + (float(c[0]) / 255), + (float(c[1]) / 255), + (float(c[2]) / 255) ) return containerise( @@ -101,7 +102,7 @@ class VRayProxyLoader(load.LoaderPlugin): assert cmds.objExists(node), "Missing container" members = cmds.sets(node, query=True) or [] - vraymeshes = cmds.ls(members, type="VRayMesh") + vraymeshes = cmds.ls(members, type="VRayProxy") assert vraymeshes, "Cannot find VRayMesh in container" # get all representations for this version From c0439cae1aec3e7d60a9eb07267ac42b61309edc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Feb 2023 20:48:43 +0000 Subject: [PATCH 198/356] Validate vray plugin is loaded. Otherwise you can publish VrayProxy without needing to enable it and fail at extraction. --- .../maya/plugins/publish/validate_vray.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_vray.py diff --git a/openpype/hosts/maya/plugins/publish/validate_vray.py b/openpype/hosts/maya/plugins/publish/validate_vray.py new file mode 100644 index 0000000000..045ac258a1 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_vray.py @@ -0,0 +1,18 @@ +from maya import cmds + +import pyblish.api +from openpype.pipeline import PublishValidationError + + +class ValidateVray(pyblish.api.InstancePlugin): + """Validate general Vray setup.""" + + order = pyblish.api.ValidatorOrder + label = 'VRay' + hosts = ["maya"] + families = ["vrayproxy"] + + def process(self, instance): + # Validate vray plugin is loaded. + if not cmds.pluginInfo("vrayformaya", query=True, loaded=True): + raise PublishValidationError("Vray plugin is not loaded.") From b00aab1eed7bf92347b64ed50e44340476b671a6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Feb 2023 23:35:22 +0000 Subject: [PATCH 199/356] Fix rounding --- openpype/hosts/maya/api/lib.py | 149 +++++++++++++----- .../plugins/publish/validate_maya_units.py | 11 +- 2 files changed, 115 insertions(+), 45 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index e5fa883c99..887c04d257 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1969,8 +1969,6 @@ def get_id_from_sibling(node, history_only=True): return first_id - -# Project settings def set_scene_fps(fps, update=True): """Set FPS from project configuration @@ -1983,28 +1981,21 @@ def set_scene_fps(fps, update=True): """ - fps_mapping = {'15': 'game', - '24': 'film', - '25': 'pal', - '30': 'ntsc', - '48': 'show', - '50': 'palf', - '60': 'ntscf', - '23.98': '23.976fps', - '23.976': '23.976fps', - '29.97': '29.97fps', - '47.952': '47.952fps', - '47.95': '47.952fps', - '59.94': '59.94fps', - '44100': '44100fps', - '48000': '48000fps'} - - # pull from mapping - # this should convert float string to float and int to int - # so 25.0 is converted to 25, but 23.98 will be still float. - dec, ipart = math.modf(fps) - if dec == 0.0: - fps = int(ipart) + fps_mapping = { + '15': 'game', + '24': 'film', + '25': 'pal', + '30': 'ntsc', + '48': 'show', + '50': 'palf', + '60': 'ntscf', + '23.976023976023978': '23.976fps', + '29.97002997002997': '29.97fps', + '47.952047952047955': '47.952fps', + '59.94005994005994': '59.94fps', + '44100': '44100fps', + '48000': '48000fps' + } unit = fps_mapping.get(str(fps), None) if unit is None: @@ -2124,7 +2115,9 @@ def set_context_settings(): asset_data = asset_doc.get("data", {}) # Set project fps - fps = asset_data.get("fps", project_data.get("fps", 25)) + fps = convert_to_maya_fps( + asset_data.get("fps", project_data.get("fps", 25)) + ) legacy_io.Session["AVALON_FPS"] = str(fps) set_scene_fps(fps) @@ -2146,15 +2139,12 @@ def validate_fps(): """ - fps = get_current_project_asset(fields=["data.fps"])["data"]["fps"] - # TODO(antirotor): This is hack as for framerates having multiple - # decimal places. FTrack is ceiling decimal values on - # fps to two decimal places but Maya 2019+ is reporting those fps - # with much higher resolution. As we currently cannot fix Ftrack - # rounding, we have to round those numbers coming from Maya. - current_fps = float_round(mel.eval('currentTimeUnitToFPS()'), 2) + expected_fps = convert_to_maya_fps( + get_current_project_asset(fields=["data.fps"])["data"]["fps"] + ) + current_fps = mel.eval('currentTimeUnitToFPS()') - fps_match = current_fps == fps + fps_match = current_fps == expected_fps if not fps_match and not IS_HEADLESS: from openpype.widgets import popup @@ -2163,14 +2153,19 @@ def validate_fps(): dialog = popup.PopupUpdateKeys(parent=parent) dialog.setModal(True) dialog.setWindowTitle("Maya scene does not match project FPS") - dialog.setMessage("Scene %i FPS does not match project %i FPS" % - (current_fps, fps)) + dialog.setMessage( + "Scene {} FPS does not match project {} FPS".format( + current_fps, expected_fps + ) + ) dialog.setButtonText("Fix") # Set new text for button (add optional argument for the popup?) toggle = dialog.widgets["toggle"] update = toggle.isChecked() - dialog.on_clicked_state.connect(lambda: set_scene_fps(fps, update)) + dialog.on_clicked_state.connect( + lambda: set_scene_fps(expected_fps, update) + ) dialog.show() @@ -3353,3 +3348,85 @@ def iter_visible_nodes_in_range(nodes, start, end): def get_attribute_input(attr): connections = cmds.listConnections(attr, plugs=True, destination=False) return connections[0] if connections else None + + +def convert_to_maya_fps(fps): + """Convert any fps to supported Maya framerates.""" + float_framerates = [ + 23.976023976023978, + # WTF is 29.97 df vs fps? + 29.97002997002997, + 47.952047952047955, + 59.94005994005994 + ] + # 44100 fps evaluates as 41000.0. Why? Omitting for now. + int_framerates = [ + 2, + 3, + 4, + 5, + 6, + 8, + 10, + 12, + 15, + 16, + 20, + 24, + 25, + 30, + 40, + 48, + 50, + 60, + 75, + 80, + 90, + 100, + 120, + 125, + 150, + 200, + 240, + 250, + 300, + 375, + 400, + 500, + 600, + 750, + 1200, + 1500, + 2000, + 3000, + 6000, + 48000 + ] + + # If input fps is a whole number we'll return. + if float(fps).is_integer(): + # Validate fps is part of Maya's fps selection. + if fps not in int_framerates: + raise ValueError( + "Framerate \"{}\" is not supported in Maya".format(fps) + ) + return fps + else: + # Differences to supported float frame rates. + differences = [] + for i in float_framerates: + differences.append(abs(i - fps)) + + # Validate difference does not stray too far from supported framerates. + min_difference = min(differences) + min_index = differences.index(min_difference) + supported_framerate = float_framerates[min_index] + if round(min_difference) != 0: + raise ValueError( + "Framerate \"{}\" strays too far from any supported framerate" + " in Maya. Closest supported framerate is \"{}\"".format( + fps, supported_framerate + ) + ) + + return supported_framerate diff --git a/openpype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py index e6fabb1712..ad256b6a72 100644 --- a/openpype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -33,18 +33,11 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): linearunits = context.data.get('linearUnits') angularunits = context.data.get('angularUnits') - # TODO(antirotor): This is hack as for framerates having multiple - # decimal places. FTrack is ceiling decimal values on - # fps to two decimal places but Maya 2019+ is reporting those fps - # with much higher resolution. As we currently cannot fix Ftrack - # rounding, we have to round those numbers coming from Maya. - # NOTE: this must be revisited yet again as it seems that Ftrack is - # now flooring the value? - fps = mayalib.float_round(context.data.get('fps'), 2, ceil) + fps = context.data.get('fps') # TODO repace query with using 'context.data["assetEntity"]' asset_doc = get_current_project_asset() - asset_fps = asset_doc["data"]["fps"] + asset_fps = mayalib.convert_to_maya_fps(asset_doc["data"]["fps"]) self.log.info('Units (linear): {0}'.format(linearunits)) self.log.info('Units (angular): {0}'.format(angularunits)) From 2c410e3d5435d661861acab1ea4f93c7de3387aa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Feb 2023 12:08:08 +0100 Subject: [PATCH 200/356] gracefully skip if label is not set --- openpype/hosts/houdini/api/shelves.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 3ccab964cd..3a6534d0eb 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -152,9 +152,13 @@ def get_or_create_tool(tool_definition, shelf): Returns: hou.Tool: The tool updated or the new one """ - existing_tools = shelf.tools() - tool_label = tool_definition.get('label') + tool_label = tool_definition.get("label") + if not tool_label: + log.warning("Skipped shelf without label") + return + + existing_tools = shelf.tools() existing_tool = next( (tool for tool in existing_tools if tool.label() == tool_label), None From 2c17a9bb4e866c3e85127acba40ade4f34eafdf6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Feb 2023 12:08:20 +0100 Subject: [PATCH 201/356] fix mandatory keys --- openpype/hosts/houdini/api/shelves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 3a6534d0eb..efbc0f5b04 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -66,7 +66,7 @@ def generate_shelves(): ) continue - mandatory_attributes = {'name', 'script'} + mandatory_attributes = {'label', 'script'} for tool_definition in shelf_definition.get('tools_list'): # We verify that the name and script attibutes of the tool # are set From 79e7b0e0d5052d5cb180b847fa20e2f2f04a6e3c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Feb 2023 12:08:47 +0100 Subject: [PATCH 202/356] check script path and gracefully skip if was not found or not set --- openpype/hosts/houdini/api/shelves.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index efbc0f5b04..f6295ddfe7 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -158,6 +158,11 @@ def get_or_create_tool(tool_definition, shelf): log.warning("Skipped shelf without label") return + script_path = tool_definition["script"] + if not script_path or not os.path.exists(script_path): + log.warning("This path doesn't exist - {}".format(script_path)) + return + existing_tools = shelf.tools() existing_tool = next( (tool for tool in existing_tools if tool.label() == tool_label), From 4c502ffe85af5d66ea2638d839b4487d0dfb2a42 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 3 Feb 2023 12:14:42 +0100 Subject: [PATCH 203/356] change how shelves are created --- openpype/hosts/houdini/api/shelves.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index f6295ddfe7..ebd668e9e4 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,4 +1,5 @@ import os +import re import logging import platform @@ -168,24 +169,16 @@ def get_or_create_tool(tool_definition, shelf): (tool for tool in existing_tools if tool.label() == tool_label), None ) + + with open(script_path) as stream: + script = stream.read() + + tool_definition["script"] = script + if existing_tool: - tool_definition.pop('name', None) - tool_definition.pop('label', None) + tool_definition.pop("label", None) existing_tool.setData(**tool_definition) return existing_tool - tool_name = tool_label.replace(' ', '_').lower() - - if not os.path.exists(tool_definition['script']): - log.warning( - "This path doesn't exist - {}".format(tool_definition['script']) - ) - return - - with open(tool_definition['script']) as f: - script = f.read() - tool_definition.update({'script': script}) - - new_tool = hou.shelves.newTool(name=tool_name, **tool_definition) - - return new_tool + tool_name = re.sub(r"[^\w\d]+", "_", tool_label).lower() + return hou.shelves.newTool(name=tool_name, **tool_definition) From 1ac6d9eadb892ca24a7ab2e5c556ae23970c4da9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 16:39:25 +0100 Subject: [PATCH 204/356] OP-4653 - added explicit 'use_layer_name' toggl Similar toggle is in AE, to keep same approach. This allow to create only single instance with layer name (previously only if multiple selected). --- .../hosts/photoshop/plugins/create/create_image.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index ca3bbfd27c..036a1127c1 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -73,13 +73,16 @@ class ImageCreator(Creator): groups_to_create.append(group) layer_name = '' - creating_multiple_groups = len(groups_to_create) > 1 + # use artist chosen option OR force layer if more subsets are created + # to differentiate them + use_layer_name = (pre_create_data.get("use_layer_name") or + len(groups_to_create) > 1) for group in groups_to_create: subset_name = subset_name_from_ui # reset to name from creator UI layer_names_in_hierarchy = [] created_group_name = self._clean_highlights(stub, group.name) - if creating_multiple_groups: + if use_layer_name: layer_name = re.sub( "[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), "", @@ -137,7 +140,10 @@ class ImageCreator(Creator): label="Create only for selected"), BoolDef("create_multiple", default=True, - label="Create separate instance for each selected") + label="Create separate instance for each selected"), + BoolDef("use_layer_name", + default=False, + label="Use layer name in subset") ] return output From f0310e99f1799ec06c38e161ecd620cec6a1cbd3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 16:40:49 +0100 Subject: [PATCH 205/356] OP-4653 - updated description, fixed typos --- .../photoshop/plugins/create/create_image.py | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index 036a1127c1..d15d7b2ab5 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -13,7 +13,11 @@ from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances class ImageCreator(Creator): - """Creates image instance for publishing.""" + """Creates image instance for publishing. + + Result of 'image' instance is image of all visible layers, or image(s) of + selected layers. + """ identifier = "image" label = "Image" family = "image" @@ -59,9 +63,10 @@ class ImageCreator(Creator): try: group = stub.group_selected_layers(subset_name_from_ui) except: - raise ValueError("Cannot group locked Bakcground layer!") + raise ValueError("Cannot group locked Background layer!") groups_to_create.append(group) + # create empty group if nothing selected if not groups_to_create and not top_layers_to_wrap: group = stub.create_group(subset_name_from_ui) groups_to_create.append(group) @@ -148,7 +153,34 @@ class ImageCreator(Creator): return output def get_detail_description(self): - return """Creator for Image instances""" + return """Creator for Image instances + + Main publishable item in Photoshop will be of `image` family. Result of + this item (instance) is picture that could be loaded and used + in another DCCs (for example as single layer in composition in + AfterEffects, reference in Maya etc). + + There are couple of options what to publish: + - separate image per selected layer (or group of layers) + - one image for all selected layers + - all visible layers (groups) flattened into single image + + In most cases you would like to keep `Create only for selected` + toggled on and select what you would like to publish. + Toggling this option off will allow you to create instance for all + visible layers without a need to select them explicitly. + + Use 'Create separate instance for each selected' to create separate + images per selected layer (group of layers). + + 'Use layer name in subset' will explicitly add layer name into subset + name. Position of this name is configurable in + `project_settings/global/tools/creator/subset_name_profiles`. + If layer placeholder ({layer}) is not used in `subset_name_profiles` + but layer name should be used (set explicitly in UI or implicitly if + multiple images should be created), it is added in capitalized form + as a suffix to subset name. + """ def _handle_legacy(self, instance_data): """Converts old instances to new format.""" From 53c1c842f573622d3c5704bfc06a2c6d56cdde19 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:02:33 +0100 Subject: [PATCH 206/356] OP-4653 - standardize use_composition_name Follow more closely login in PS, eg. if {composition} placeholder not present in subset template and should be used, add capitalized. Clean composition name --- .../aftereffects/plugins/create/create_render.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 8d38288257..4ed9192964 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -11,6 +11,7 @@ from openpype.pipeline import ( ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances from openpype.lib import prepare_template_data +from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS class RenderCreator(Creator): @@ -82,10 +83,19 @@ class RenderCreator(Creator): "if 'useSelection' or create at least " "one composition." ) - + use_composition_name = (pre_create_data.get("use_composition_name") or + len(comps) > 1) for comp in comps: - if pre_create_data.get("use_composition_name"): - composition_name = comp.name + if use_composition_name: + if "{composition}" not in subset_name.lower(): + subset_name += "{Composition}" + + composition_name = re.sub( + "[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), + "", + comp.name + ) + dynamic_fill = prepare_template_data({"composition": composition_name}) subset_name = subset_name_from_ui.format(**dynamic_fill) From f2930ed156aa1e877fe0478e5539e4dd16aaff62 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:04:06 +0100 Subject: [PATCH 207/356] OP-4653 - updated description in AE creator --- .../plugins/create/create_render.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 4ed9192964..25dadbecfd 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -15,6 +15,11 @@ from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS class RenderCreator(Creator): + """Creates 'render' instance for publishing. + + Result of 'render' instance is video or sequence of images for particular + composition based of configuration in its RenderQueue. + """ identifier = "render" label = "Render" family = "render" @@ -140,7 +145,32 @@ class RenderCreator(Creator): return output def get_detail_description(self): - return """Creator for Render instances""" + return """Creator for Render instances + + Main publishable item in AfterEffects will be of `render` family. + Result of this item (instance) is picture sequence or video that could + be a final delivery product or loaded and used in another DCCs. + + Select single composition and create instance of 'render' family or + turn off 'Use selection' to create instance for all compositions. + + 'Use composition name in subset' allows to explicitly add composition + name into created subset name. + + Position of composition name could be set in + `project_settings/global/tools/creator/subset_name_profiles` with some + form of '{composition}' placeholder. + + Composition name will be used implicitly if multiple composition should + be handled at same time. + + If {composition} placeholder is not us 'subset_name_profiles' + composition name will be capitalized and set at the end of subset name + if necessary. + + If composition name should be used, it will be cleaned up of characters + that would cause an issue in published file names. + """ def get_dynamic_data(self, variant, task_name, asset_doc, project_name, host_name, instance): From 993145e6f8bf4b829401a89f428d8821108e54a6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:16:55 +0100 Subject: [PATCH 208/356] OP-4653 - fix wrong name --- openpype/hosts/aftereffects/plugins/create/create_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 25dadbecfd..c95270a3bb 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -92,8 +92,8 @@ class RenderCreator(Creator): len(comps) > 1) for comp in comps: if use_composition_name: - if "{composition}" not in subset_name.lower(): - subset_name += "{Composition}" + if "{composition}" not in subset_name_from_ui.lower(): + subset_name_from_ui += "{Composition}" composition_name = re.sub( "[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), From 31b137fa1d6257d97e170f475cd6d11fbdbfa8f4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:17:27 +0100 Subject: [PATCH 209/356] OP-4653 - Hound --- .../plugins/create/create_render.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index c95270a3bb..a5f8dedace 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -146,28 +146,28 @@ class RenderCreator(Creator): def get_detail_description(self): return """Creator for Render instances - - Main publishable item in AfterEffects will be of `render` family. - Result of this item (instance) is picture sequence or video that could + + Main publishable item in AfterEffects will be of `render` family. + Result of this item (instance) is picture sequence or video that could be a final delivery product or loaded and used in another DCCs. - - Select single composition and create instance of 'render' family or + + Select single composition and create instance of 'render' family or turn off 'Use selection' to create instance for all compositions. - + 'Use composition name in subset' allows to explicitly add composition name into created subset name. - - Position of composition name could be set in + + Position of composition name could be set in `project_settings/global/tools/creator/subset_name_profiles` with some form of '{composition}' placeholder. - + Composition name will be used implicitly if multiple composition should be handled at same time. - - If {composition} placeholder is not us 'subset_name_profiles' + + If {composition} placeholder is not us 'subset_name_profiles' composition name will be capitalized and set at the end of subset name if necessary. - + If composition name should be used, it will be cleaned up of characters that would cause an issue in published file names. """ From 59010eb9a6c3a8abbabdd48dbb6c2a8563d9ac27 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:19:02 +0100 Subject: [PATCH 210/356] OP-4653 - refactor - move most important method higher --- .../plugins/create/create_render.py | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index a5f8dedace..10ded8b912 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -34,45 +34,6 @@ class RenderCreator(Creator): ["RenderCreator"] ["defaults"]) - def get_icon(self): - return resources.get_openpype_splash_filepath() - - def collect_instances(self): - for instance_data in cache_and_get_instances(self): - # legacy instances have family=='render' or 'renderLocal', use them - creator_id = (instance_data.get("creator_identifier") or - instance_data.get("family", '').replace("Local", '')) - if creator_id == self.identifier: - instance_data = self._handle_legacy(instance_data) - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - - def update_instances(self, update_list): - for created_inst, _changes in update_list: - api.get_stub().imprint(created_inst.get("instance_id"), - created_inst.data_to_store()) - subset_change = _changes.get("subset") - if subset_change: - api.get_stub().rename_item(created_inst.data["members"][0], - subset_change[1]) - - def remove_instances(self, instances): - for instance in instances: - self._remove_instance_from_context(instance) - self.host.remove_instance(instance) - - subset = instance.data["subset"] - comp_id = instance.data["members"][0] - comp = api.get_stub().get_item(comp_id) - if comp: - new_comp_name = comp.name.replace(subset, '') - if not new_comp_name: - new_comp_name = "dummyCompName" - api.get_stub().rename_item(comp_id, - new_comp_name) - def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up if pre_create_data.get("use_selection"): @@ -144,6 +105,45 @@ class RenderCreator(Creator): ] return output + def get_icon(self): + return resources.get_openpype_splash_filepath() + + def collect_instances(self): + for instance_data in cache_and_get_instances(self): + # legacy instances have family=='render' or 'renderLocal', use them + creator_id = (instance_data.get("creator_identifier") or + instance_data.get("family", '').replace("Local", '')) + if creator_id == self.identifier: + instance_data = self._handle_legacy(instance_data) + instance = CreatedInstance.from_existing( + instance_data, self + ) + self._add_instance_to_context(instance) + + def update_instances(self, update_list): + for created_inst, _changes in update_list: + api.get_stub().imprint(created_inst.get("instance_id"), + created_inst.data_to_store()) + subset_change = _changes.get("subset") + if subset_change: + api.get_stub().rename_item(created_inst.data["members"][0], + subset_change[1]) + + def remove_instances(self, instances): + for instance in instances: + self._remove_instance_from_context(instance) + self.host.remove_instance(instance) + + subset = instance.data["subset"] + comp_id = instance.data["members"][0] + comp = api.get_stub().get_item(comp_id) + if comp: + new_comp_name = comp.name.replace(subset, '') + if not new_comp_name: + new_comp_name = "dummyCompName" + api.get_stub().rename_item(comp_id, + new_comp_name) + def get_detail_description(self): return """Creator for Render instances From 0dd1a376b0463622dfe6aaab08057ce670524ee0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 17:19:52 +0100 Subject: [PATCH 211/356] OP-4653 - refactor - move most important method higher --- .../photoshop/plugins/create/create_image.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index d15d7b2ab5..8a103ea6c2 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -23,21 +23,6 @@ class ImageCreator(Creator): family = "image" description = "Image creator" - def collect_instances(self): - for instance_data in cache_and_get_instances(self): - # legacy instances have family=='image' - creator_id = (instance_data.get("creator_identifier") or - instance_data.get("family")) - - if creator_id == self.identifier: - instance_data = self._handle_legacy(instance_data) - layer = api.stub().get_layer(instance_data["members"][0]) - instance_data["layer"] = layer - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - def create(self, subset_name_from_ui, data, pre_create_data): groups_to_create = [] top_layers_to_wrap = [] @@ -120,6 +105,21 @@ class ImageCreator(Creator): stub.rename_layer(group.id, stub.PUBLISH_ICON + created_group_name) + def collect_instances(self): + for instance_data in cache_and_get_instances(self): + # legacy instances have family=='image' + creator_id = (instance_data.get("creator_identifier") or + instance_data.get("family")) + + if creator_id == self.identifier: + instance_data = self._handle_legacy(instance_data) + layer = api.stub().get_layer(instance_data["members"][0]) + instance_data["layer"] = layer + instance = CreatedInstance.from_existing( + instance_data, self + ) + self._add_instance_to_context(instance) + def update_instances(self, update_list): self.log.debug("update_list:: {}".format(update_list)) for created_inst, _changes in update_list: From 3a8c36e0570e5554d0d9ae7afd073fdde43294ff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 18:28:29 +0100 Subject: [PATCH 212/356] OP-4822 - updated settings After discussion profile is too complicated, removed, replaced with simple boolean --- .../defaults/project_settings/deadline.json | 2 +- .../schema_project_deadline.json | 32 +++---------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 11bb1aee2e..0a4318a659 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -34,7 +34,7 @@ "jobInfo": {}, "pluginInfo": {}, "scene_patches": [], - "disable_strict_check_profiles": [] + "strict_error_checking": true }, "NukeSubmitDeadline": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index d38358773a..03f6489a41 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -197,34 +197,10 @@ } }, { - "type": "list", - "collapsible": true, - "key": "disable_strict_check_profiles", - "label": "Disable Strict Error Check profiles", - "use_label_wrap": true, - "docstring": "Set profile for disabling 'Strict Error Checking'", - "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": "boolean", + "key": "strict_error_checking", + "label": "Strict Error Checking", + "default": true } ] }, From 214ef5104dfe17619c5f04e466d2bb70a9fbd195 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 18:33:40 +0100 Subject: [PATCH 213/356] OP-4822 - changed strict error checking to boolean flag Added boolean flag to render instance to allow artist to change it. --- .../maya/plugins/create/create_render.py | 4 +++ .../plugins/publish/submit_maya_deadline.py | 35 +++---------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 8375149442..387b7321b9 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -54,6 +54,7 @@ class CreateRender(plugin.Creator): tileRendering (bool): Instance is set to tile rendering mode. We won't submit actual render, but we'll make publish job to wait for Tile Assembly job done and then publish. + strict_error_checking (bool): Enable/disable error checking on DL See Also: https://pype.club/docs/artist_hosts_maya#creating-basic-render-setup @@ -271,6 +272,9 @@ class CreateRender(plugin.Creator): secondary_pool = pool_setting["secondary_pool"] self.data["secondaryPool"] = self._set_default_pool(pool_names, secondary_pool) + strict_error_checking = maya_submit_dl.get("strict_error_checking", + True) + self.data["strict_error_checking"] = strict_error_checking if muster_enabled: self.log.info(">>> Loading Muster credentials ...") diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3d1b9bcbf6..9fef816e1c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -65,7 +65,7 @@ class MayaPluginInfo(object): # Include all lights flag RenderSetupIncludeLights = attr.ib( default="1", validator=_validate_deadline_bool_value) - StrictErrorChecking = attr.ib(default="1") + StrictErrorChecking = attr.ib(default=True) @attr.s @@ -222,7 +222,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "renderSetupIncludeLights", default_rs_include_lights) if rs_include_lights not in {"1", "0", True, False}: rs_include_lights = default_rs_include_lights - strict_checking = self._get_strict_checking(instance) + strict_error_checking = instance.data.get("strict_error_checking", + True) plugin_info = MayaPluginInfo( SceneFile=self.scene_path, Version=cmds.about(version=True), @@ -231,7 +232,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): RenderSetupIncludeLights=rs_include_lights, # noqa ProjectPath=context.data["workspaceDir"], UsingRenderLayers=True, - StrictErrorChecking=strict_checking + StrictErrorChecking=strict_error_checking ) plugin_payload = attr.asdict(plugin_info) @@ -753,34 +754,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): for file in exp: yield file - def _get_strict_checking(self, instance): - """Find profile to disable Strict Error Checking - - Args: - instance (dict) - Returns: - (int) - 1 if Strict (DL default), 0 for disabled Strict - """ - strict_checking = 1 - if not self.disable_strict_check_profiles: - return strict_checking - - task_data = instance.data["anatomyData"].get("task", {}) - key_values = { - "task_names": task_data.get("name"), - "task_types": task_data.get("type"), - "subsets": instance.data["subset"] - } - profile = filter_profiles(self.disable_strict_check_profiles, - key_values, - logger=self.log) - - if profile: - self.log.debug("Disabling Strict Error Checking") - strict_checking = 0 - - return strict_checking - def _format_tiles( filename, index, tiles_x, tiles_y, From 463f770f2d19dcb1bfb184412d1664a8134b7502 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Feb 2023 18:40:53 +0100 Subject: [PATCH 214/356] OP-4822 - Hound --- .../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 9fef816e1c..e7c1899513 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -35,7 +35,6 @@ from openpype.pipeline import legacy_io from openpype.hosts.maya.api.lib_rendersettings import RenderSettings from openpype.hosts.maya.api.lib import get_attr_in_layer -from openpype.lib.profiles_filtering import filter_profiles from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.tests.lib import is_in_tests From 31173abd4fc75c6caca0be712f50faf06a24ed56 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 6 Feb 2023 13:39:04 +0100 Subject: [PATCH 215/356] OP-4702 - fix dirmap remote_site_dir started to return platform dict --- openpype/host/dirmap.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/host/dirmap.py b/openpype/host/dirmap.py index 88d68f27bf..347c5fbf85 100644 --- a/openpype/host/dirmap.py +++ b/openpype/host/dirmap.py @@ -8,6 +8,7 @@ exists is used. import os from abc import ABCMeta, abstractmethod +import platform import six @@ -187,11 +188,19 @@ class HostDirmap(object): self.log.debug("local overrides {}".format(active_overrides)) self.log.debug("remote overrides {}".format(remote_overrides)) + current_platform = platform.system().lower() for root_name, active_site_dir in active_overrides.items(): remote_site_dir = ( remote_overrides.get(root_name) or sync_settings["sites"][remote_site]["root"][root_name] ) + + if isinstance(remote_site_dir, dict): + remote_site_dir = remote_site_dir.get(current_platform) + + if not remote_site_dir: + continue + if os.path.isdir(active_site_dir): if "destination-path" not in mapping: mapping["destination-path"] = [] From 9b6bd7954d99640db5391cce39e87d02dc0e557a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 6 Feb 2023 15:44:34 +0000 Subject: [PATCH 216/356] Working AssStandinLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 119 +++++++++---------- 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 5db6fc3dfa..6317c0a7ce 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -1,6 +1,9 @@ import os import clique +import maya.cmds as cmds +import mtoa.ui.arnoldmenu + from openpype.settings import get_project_settings from openpype.pipeline import ( load, @@ -15,6 +18,15 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api.pipeline import containerise +def is_sequence(files): + sequence = False + collections, remainder = clique.assemble(files) + if collections: + sequence = True + + return sequence + + class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load Arnold Proxy as reference""" @@ -27,16 +39,12 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): color = "orange" def process_reference(self, context, name, namespace, options): - - import maya.cmds as cmds - import pymel.core as pm - version = context['version'] version_data = version.get("data", {}) self.log.info("version_data: {}\n".format(version_data)) - frameStart = version_data.get("frameStart", None) + frame_start = version_data.get("frame_start", None) try: family = context["representation"]["context"]["family"] @@ -49,7 +57,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = self.fname proxyPath_base = os.path.splitext(path)[0] - if frameStart is not None: + if frame_start is not None: proxyPath_base = os.path.splitext(proxyPath_base)[0] publish_folder = os.path.split(path)[0] @@ -63,11 +71,13 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) - proxyPath = proxyPath_base + ".ma" + proxyPath = proxyPath_base + ".ass" project_name = context["project"]["name"] - file_url = self.prepare_root_value(proxyPath, - project_name) + file_url = self.prepare_root_value( + proxyPath, project_name + ) + self.log.info(file_url) nodes = cmds.file(file_url, namespace=namespace, @@ -80,7 +90,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): translate=True, scale=True) # Set attributes - proxyShape = pm.ls(nodes, type="mesh")[0] + proxyShape = cmds.ls(nodes, type="mesh")[0] proxyShape.aiTranslator.set('procedural') proxyShape.dso.set(path) @@ -92,10 +102,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): c = colors.get(family) 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( + groupName + ".outlinerColor", + (float(c[0]) / 255), + (float(c[1]) / 255), + (float(c[2]) / 255) ) self[:] = nodes @@ -106,18 +117,11 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): - from maya import cmds - import pymel.core as pm - node = container["objectName"] representation["context"].pop("frame", None) path = get_representation_path(representation) - print(path) - # path = self.fname - print(self.fname) proxyPath = os.path.splitext(path)[0] + ".ma" - print(proxyPath) # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) @@ -186,18 +190,11 @@ class AssStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): - - import maya.cmds as cmds - import mtoa.ui.arnoldmenu - import pymel.core as pm - version = context['version'] version_data = version.get("data", {}) self.log.info("version_data: {}\n".format(version_data)) - frameStart = version_data.get("frameStart", None) - asset = context['asset']['name'] namespace = namespace or unique_namespace( asset + "_", @@ -205,36 +202,34 @@ class AssStandinLoader(load.LoaderPlugin): suffix="_", ) - # cmds.loadPlugin("gpuCache", quiet=True) - # Root group label = "{}:{}".format(namespace, name) - root = pm.group(name=label, empty=True) + root = cmds.group(name=label, empty=True) settings = get_project_settings(os.environ['AVALON_PROJECT']) colors = settings['maya']['load']['colors'] - c = colors.get('ass') - if c is not None: - cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - c[0], c[1], c[2]) + color = colors.get('ass') + if color is not None: + cmds.setAttr(root + ".useOutlinerColor", True) + cmds.setAttr( + root + ".outlinerColor", color[0], color[1], color[2] + ) # Create transform with shape transform_name = label + "_ASS" - # transform = pm.createNode("transform", name=transform_name, - # parent=root) - standinShape = pm.PyNode(mtoa.ui.arnoldmenu.createStandIn()) - standin = standinShape.getParent() - standin.rename(transform_name) + standinShape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standinShape, parent=True)[0] + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, shapes=True)[0] - pm.parent(standin, root) + cmds.parent(standin, root) # Set the standin filepath - standinShape.dso.set(self.fname) - if frameStart is not None: - standinShape.useFrameExtension.set(1) + cmds.setAttr(standinShape + ".dso", self.fname, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) + cmds.setAttr(standinShape + ".useFrameExtension", sequence) nodes = [root, standin] self[:] = nodes @@ -247,31 +242,27 @@ class AssStandinLoader(load.LoaderPlugin): loader=self.__class__.__name__) def update(self, container, representation): - - import pymel.core as pm - - path = get_representation_path(representation) - - files_in_path = os.listdir(os.path.split(path)[0]) - sequence = 0 - collections, remainder = clique.assemble(files_in_path) - if collections: - sequence = 1 - # Update the standin standins = list() - members = pm.sets(container['objectName'], query=True) + members = cmds.sets(container['objectName'], query=True) for member in members: - shape = member.getShape() - if (shape and shape.type() == "aiStandIn"): - standins.append(shape) + shapes = cmds.listRelatives(member, shapes=True) + if not shapes: + continue + if cmds.nodeType(shapes[0]) == "aiStandIn": + standins.append(shapes[0]) + path = get_representation_path(representation) + sequence = is_sequence(os.listdir(os.path.dirname(path))) for standin in standins: - standin.dso.set(path) - standin.useFrameExtension.set(sequence) + cmds.setAttr(standin + ".dso", path, type="string") + cmds.setAttr(standin + ".useFrameExtension", sequence) - container = pm.PyNode(container["objectName"]) - container.representation.set(str(representation["_id"])) + cmds.setAttr( + container["objectName"] + ".representation", + str(representation["_id"]), + type="string" + ) def switch(self, container, representation): self.update(container, representation) From 23987420a375c199b095b295282728e913bde75a Mon Sep 17 00:00:00 2001 From: Seyedmohammadreza Hashemizadeh Date: Mon, 6 Feb 2023 19:03:06 +0100 Subject: [PATCH 217/356] update asset info of imported sets --- .../maya/api/workfile_template_builder.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index ef043ed0f4..56a53c070c 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -2,7 +2,7 @@ import json from maya import cmds -from openpype.pipeline import registered_host +from openpype.pipeline import registered_host, legacy_io from openpype.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, AbstractTemplateBuilder, @@ -41,10 +41,26 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): )) cmds.sets(name=PLACEHOLDER_SET, empty=True) - cmds.file(path, i=True, returnNewNodes=True) + new_nodes = cmds.file(path, i=True, returnNewNodes=True) cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True) + imported_sets = cmds.ls(new_nodes, set=True) + if not imported_sets: + return True + + # update imported sets information + for node in imported_sets: + if not cmds.attributeQuery("id", node=node, exists=True): + continue + if cmds.getAttr("{}.id".format(node)) != "pyblish.avalon.instance": + continue + if not cmds.attributeQuery("asset", node=node, exists=True): + continue + asset = legacy_io.Session["AVALON_ASSET"] + + cmds.setAttr("{}.asset".format(node), asset, type="string") + return True From 3e25f2cddac284ed4583f751687525f1e2471e4a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 6 Feb 2023 18:08:25 +0000 Subject: [PATCH 218/356] Working AssProxyLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 97 +++++++++----------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 6317c0a7ce..ada65998a5 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -27,6 +27,18 @@ def is_sequence(files): return sequence +def set_color(node, context): + project_name = context["project"]["name"] + settings = get_project_settings(project_name) + colors = settings['maya']['load']['colors'] + color = colors.get('ass') + if color is not None: + cmds.setAttr(node + ".useOutlinerColor", True) + cmds.setAttr( + node + ".outlinerColor", color[0], color[1], color[2] + ) + + class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load Arnold Proxy as reference""" @@ -46,19 +58,14 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): frame_start = version_data.get("frame_start", None) - try: - family = context["representation"]["context"]["family"] - except ValueError: - family = "ass" - with maintained_selection(): groupName = "{}:{}".format(namespace, name) path = self.fname - proxyPath_base = os.path.splitext(path)[0] + proxy_path_base = os.path.splitext(path)[0] if frame_start is not None: - proxyPath_base = os.path.splitext(proxyPath_base)[0] + proxy_path_base = os.path.splitext(proxy_path_base)[0] publish_folder = os.path.split(path)[0] files_in_folder = os.listdir(publish_folder) @@ -71,43 +78,33 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) - proxyPath = proxyPath_base + ".ass" + proxy_path = proxy_path_base + ".ma" + msg = proxy_path + " does not exist." + assert os.path.exists(proxy_path), msg - project_name = context["project"]["name"] - file_url = self.prepare_root_value( - proxyPath, project_name + nodes = cmds.file( + proxy_path, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=groupName ) - self.log.info(file_url) - nodes = cmds.file(file_url, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName=groupName) - - cmds.makeIdentity(groupName, apply=False, rotate=True, - translate=True, scale=True) + cmds.makeIdentity( + groupName, apply=False, rotate=True, translate=True, scale=True + ) # Set attributes - proxyShape = cmds.ls(nodes, type="mesh")[0] + proxy_shape = cmds.ls(nodes, type="mesh")[0] - proxyShape.aiTranslator.set('procedural') - proxyShape.dso.set(path) - proxyShape.aiOverrideShaders.set(0) + cmds.setAttr( + proxy_shape + ".aiTranslator", "procedural", type="string" + ) + cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") + cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - 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) - ) + set_color(groupName, context) self[:] = nodes @@ -121,16 +118,16 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): representation["context"].pop("frame", None) path = get_representation_path(representation) - proxyPath = os.path.splitext(path)[0] + ".ma" + proxy_path = os.path.splitext(path)[0] + ".ma" # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) reference_node = get_reference_node(members) - assert os.path.exists(proxyPath), "%s does not exist." % proxyPath + assert os.path.exists(proxy_path), "%s does not exist." % proxy_path try: - file_url = self.prepare_root_value(proxyPath, + file_url = self.prepare_root_value(proxy_path, representation["context"] ["project"] ["name"]) @@ -140,11 +137,13 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): returnNewNodes=True) # Set attributes - proxyShape = pm.ls(content, type="mesh")[0] + proxy_shape = cmds.ls(content, type="mesh")[0] - proxyShape.aiTranslator.set('procedural') - proxyShape.dso.set(path) - proxyShape.aiOverrideShaders.set(0) + cmds.setAttr( + proxy_shape + ".aiTranslator", "procedural", type="string" + ) + cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") + cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) except RuntimeError as exc: # When changing a reference to a file that has load errors the @@ -206,15 +205,7 @@ class AssStandinLoader(load.LoaderPlugin): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - settings = get_project_settings(os.environ['AVALON_PROJECT']) - colors = settings['maya']['load']['colors'] - - color = colors.get('ass') - if color is not None: - cmds.setAttr(root + ".useOutlinerColor", True) - cmds.setAttr( - root + ".outlinerColor", color[0], color[1], color[2] - ) + set_color(root, context) # Create transform with shape transform_name = label + "_ASS" From ecef4cbb4691ff7ea8320fda55e3ac4a8d70c4f2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 08:57:10 +0000 Subject: [PATCH 219/356] More information about issues with publishing. --- openpype/hosts/maya/plugins/load/load_ass.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index ada65998a5..e4e0b0da84 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -79,7 +79,10 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): path = os.path.join(publish_folder, filename) proxy_path = proxy_path_base + ".ma" - msg = proxy_path + " does not exist." + msg = ( + proxy_path + " does not exist.\nThere are most likely no " + + "proxy shapes in the \"proxy_SET\" when publishing." + ) assert os.path.exists(proxy_path), msg nodes = cmds.file( From 662d68daec5c0b786489e7035bd75be77a2cdd48 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 10:06:56 +0000 Subject: [PATCH 220/356] Support for multiple proxy meshes. --- openpype/hosts/maya/plugins/load/load_ass.py | 39 +++++++++++++++++-- .../hosts/maya/plugins/publish/collect_ass.py | 4 -- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index e4e0b0da84..58abfa964e 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -60,7 +60,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): with maintained_selection(): - groupName = "{}:{}".format(namespace, name) + group_name = "{}:{}".format(namespace, name) path = self.fname proxy_path_base = os.path.splitext(path)[0] @@ -91,13 +91,36 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): reference=True, returnNewNodes=True, groupReference=True, - groupName=groupName + groupName=group_name ) + # Reset group to world zero. + transform_data = {} + for node in nodes: + if cmds.nodeType(node) != "transform": + continue + + transform_data[node] = {} + attrs = ["translate", "rotate", "scale"] + parameters = ["X", "Y", "Z"] + for attr in attrs: + for parameter in parameters: + transform_data[node][attr + parameter] = cmds.getAttr( + "{}.{}{}".format(node, attr, parameter) + ) + cmds.makeIdentity( - groupName, apply=False, rotate=True, translate=True, scale=True + group_name, + apply=False, + rotate=True, + translate=True, + scale=True ) + for node, data in transform_data.items(): + for attr, value in data.items(): + cmds.setAttr("{}.{}".format(node, attr), value) + # Set attributes proxy_shape = cmds.ls(nodes, type="mesh")[0] @@ -107,7 +130,15 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - set_color(groupName, context) + # Hides all other meshes at render time. + remaining_meshes = cmds.ls(nodes, type="mesh") + remaining_meshes.remove(proxy_shape) + for node in remaining_meshes: + cmds.setAttr( + node + ".aiTranslator", "procedural", type="string" + ) + + set_color(group_name, context) self[:] = nodes diff --git a/openpype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_ass.py index b5e05d6665..7b5d1a00c7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_ass.py +++ b/openpype/hosts/maya/plugins/publish/collect_ass.py @@ -1,5 +1,4 @@ from maya import cmds -from openpype.pipeline.publish import KnownPublishError import pyblish.api @@ -25,9 +24,6 @@ class CollectAssData(pyblish.api.InstancePlugin): instance.data['setMembers'] = members self.log.debug('content members: {}'.format(members)) elif objset.startswith("proxy_SET"): - if len(members) != 1: - msg = "You have multiple proxy meshes, please only use one" - raise KnownPublishError(msg) instance.data['proxy'] = members self.log.debug('proxy members: {}'.format(members)) From 77a6139c777d25b7bfd05eff55af94184de716ab Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 10:13:22 +0000 Subject: [PATCH 221/356] Fix updating proxy --- openpype/hosts/maya/plugins/load/load_ass.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 58abfa964e..59cfae7cdb 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -148,14 +148,14 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): - node = container["objectName"] + container_node = container["objectName"] representation["context"].pop("frame", None) path = get_representation_path(representation) proxy_path = os.path.splitext(path)[0] + ".ma" # Get reference node from container members - members = cmds.sets(node, query=True, nodesOnly=True) + members = cmds.sets(container_node, query=True, nodesOnly=True) reference_node = get_reference_node(members) assert os.path.exists(proxy_path), "%s does not exist." % proxy_path @@ -195,18 +195,26 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.log.warning("Ignoring file read error:\n%s", exc) + # Hides all other meshes at render time. + remaining_meshes = cmds.ls(content, type="mesh") + remaining_meshes.remove(proxy_shape) + for node in remaining_meshes: + cmds.setAttr( + node + ".aiTranslator", "procedural", type="string" + ) + # Add new nodes of the reference to the container - cmds.sets(content, forceElement=node) + cmds.sets(content, forceElement=container_node) # Remove any placeHolderList attribute entries from the set that # are remaining from nodes being removed from the referenced file. - members = cmds.sets(node, query=True) + members = cmds.sets(container_node, query=True) invalid = [x for x in members if ".placeHolderList" in x] if invalid: - cmds.sets(invalid, remove=node) + cmds.sets(invalid, remove=container_node) # Update metadata - cmds.setAttr("{}.representation".format(node), + cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), type="string") From 9733b07f6dd383a63c00d51312bf700a475aa5d8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 15:21:58 +0000 Subject: [PATCH 222/356] Remove AssProxyLoader --- openpype/hosts/maya/plugins/load/load_ass.py | 195 +------------------ 1 file changed, 5 insertions(+), 190 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 59cfae7cdb..50b72d87e8 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -9,12 +9,7 @@ from openpype.pipeline import ( load, get_representation_path ) -import openpype.hosts.maya.api.plugin -from openpype.hosts.maya.api.plugin import get_reference_node -from openpype.hosts.maya.api.lib import ( - maintained_selection, - unique_namespace -) +from openpype.hosts.maya.api.lib import unique_namespace from openpype.hosts.maya.api.pipeline import containerise @@ -39,193 +34,13 @@ def set_color(node, context): ) -class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): - """Load Arnold Proxy as reference""" +class ArnoldStandinLoader(load.LoaderPlugin): + """Load file as Arnold standin""" families = ["ass"] representations = ["ass"] - label = "Reference .ASS standin with Proxy" - order = -10 - icon = "code-fork" - color = "orange" - - def process_reference(self, context, name, namespace, options): - version = context['version'] - version_data = version.get("data", {}) - - self.log.info("version_data: {}\n".format(version_data)) - - frame_start = version_data.get("frame_start", None) - - with maintained_selection(): - - group_name = "{}:{}".format(namespace, name) - path = self.fname - proxy_path_base = os.path.splitext(path)[0] - - if frame_start is not None: - proxy_path_base = os.path.splitext(proxy_path_base)[0] - - publish_folder = os.path.split(path)[0] - files_in_folder = os.listdir(publish_folder) - collections, remainder = clique.assemble(files_in_folder) - - if collections: - hashes = collections[0].padding * '#' - coll = collections[0].format('{head}[index]{tail}') - filename = coll.replace('[index]', hashes) - - path = os.path.join(publish_folder, filename) - - proxy_path = proxy_path_base + ".ma" - msg = ( - proxy_path + " does not exist.\nThere are most likely no " + - "proxy shapes in the \"proxy_SET\" when publishing." - ) - assert os.path.exists(proxy_path), msg - - nodes = cmds.file( - proxy_path, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName=group_name - ) - - # Reset group to world zero. - transform_data = {} - for node in nodes: - if cmds.nodeType(node) != "transform": - continue - - transform_data[node] = {} - attrs = ["translate", "rotate", "scale"] - parameters = ["X", "Y", "Z"] - for attr in attrs: - for parameter in parameters: - transform_data[node][attr + parameter] = cmds.getAttr( - "{}.{}{}".format(node, attr, parameter) - ) - - cmds.makeIdentity( - group_name, - apply=False, - rotate=True, - translate=True, - scale=True - ) - - for node, data in transform_data.items(): - for attr, value in data.items(): - cmds.setAttr("{}.{}".format(node, attr), value) - - # Set attributes - proxy_shape = cmds.ls(nodes, type="mesh")[0] - - cmds.setAttr( - proxy_shape + ".aiTranslator", "procedural", type="string" - ) - cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") - cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - - # Hides all other meshes at render time. - remaining_meshes = cmds.ls(nodes, type="mesh") - remaining_meshes.remove(proxy_shape) - for node in remaining_meshes: - cmds.setAttr( - node + ".aiTranslator", "procedural", type="string" - ) - - set_color(group_name, context) - - self[:] = nodes - - return nodes - - def switch(self, container, representation): - self.update(container, representation) - - def update(self, container, representation): - container_node = container["objectName"] - - representation["context"].pop("frame", None) - path = get_representation_path(representation) - proxy_path = os.path.splitext(path)[0] + ".ma" - - # Get reference node from container members - members = cmds.sets(container_node, query=True, nodesOnly=True) - reference_node = get_reference_node(members) - - assert os.path.exists(proxy_path), "%s does not exist." % proxy_path - - try: - file_url = self.prepare_root_value(proxy_path, - representation["context"] - ["project"] - ["name"]) - content = cmds.file(file_url, - loadReference=reference_node, - type="mayaAscii", - returnNewNodes=True) - - # Set attributes - proxy_shape = cmds.ls(content, type="mesh")[0] - - cmds.setAttr( - proxy_shape + ".aiTranslator", "procedural", type="string" - ) - cmds.setAttr(proxy_shape + ".dso", self.fname, type="string") - cmds.setAttr(proxy_shape + ".aiOverrideShaders", 0) - - except RuntimeError as exc: - # When changing a reference to a file that has load errors the - # command will raise an error even if the file is still loaded - # correctly (e.g. when raising errors on Arnold attributes) - # When the file is loaded and has content, we consider it's fine. - if not cmds.referenceQuery(reference_node, isLoaded=True): - raise - - content = cmds.referenceQuery(reference_node, - nodes=True, - dagPath=True) - if not content: - raise - - self.log.warning("Ignoring file read error:\n%s", exc) - - # Hides all other meshes at render time. - remaining_meshes = cmds.ls(content, type="mesh") - remaining_meshes.remove(proxy_shape) - for node in remaining_meshes: - cmds.setAttr( - node + ".aiTranslator", "procedural", type="string" - ) - - # Add new nodes of the reference to the container - cmds.sets(content, forceElement=container_node) - - # Remove any placeHolderList attribute entries from the set that - # are remaining from nodes being removed from the referenced file. - members = cmds.sets(container_node, query=True) - invalid = [x for x in members if ".placeHolderList" in x] - if invalid: - cmds.sets(invalid, remove=container_node) - - # Update metadata - cmds.setAttr("{}.representation".format(container_node), - str(representation["_id"]), - type="string") - - -class AssStandinLoader(load.LoaderPlugin): - """Load .ASS file as standin""" - - families = ["ass"] - representations = ["ass"] - - label = "Load .ASS file as standin" + label = "Load file as Arnold standin" order = -5 icon = "code-fork" color = "orange" @@ -250,7 +65,7 @@ class AssStandinLoader(load.LoaderPlugin): set_color(root, context) # Create transform with shape - transform_name = label + "_ASS" + transform_name = label + "_standin" standinShape = mtoa.ui.arnoldmenu.createStandIn() standin = cmds.listRelatives(standinShape, parent=True)[0] From 01d763fe991f1ecdc83b1ce8b6ee002beead7dea Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 16:29:02 +0000 Subject: [PATCH 223/356] Extract Ass proxy. --- .../hosts/maya/plugins/publish/extract_ass.py | 87 ++++++++++++++----- .../maya/plugins/publish/extract_assproxy.py | 81 ----------------- 2 files changed, 63 insertions(+), 105 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_assproxy.py diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 049f256a7a..4cff9d0183 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,16 +1,18 @@ import os +import copy from maya import cmds import arnold from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.lib import StringTemplate -class ExtractAssStandin(publish.Extractor): - """Extract the content of the instance to a ass file""" +class ExtractArnoldSceneSource(publish.Extractor): + """Extract the content of the instance to an Arnold Scene Source file.""" - label = "Arnold Scene Source (.ass)" + label = "Arnold Scene Source" hosts = ["maya"] families = ["ass"] asciiAss = False @@ -18,7 +20,6 @@ class ExtractAssStandin(publish.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) filename = "{}.ass".format(instance.name) - filenames = [] file_path = os.path.join(staging_dir, filename) # Mask @@ -42,7 +43,7 @@ class ExtractAssStandin(publish.Extractor): mask = mask ^ node_types[key] # Motion blur - values = { + attribute_data = { "defaultArnoldRenderOptions.motion_blur_enable": instance.data.get( "motionBlur", True ), @@ -70,13 +71,65 @@ class ExtractAssStandin(publish.Extractor): "mask": mask } - self.log.info("Writing: '%s'" % file_path) - with attribute_values(values): + filenames = self._extract( + instance.data["setMembers"], attribute_data, kwargs + ) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "ass", + "ext": "ass", + "files": filenames if len(filenames) > 1 else filenames[0], + "stagingDir": staging_dir, + "frameStart": kwargs["startFrame"] + } + + instance.data["representations"].append(representation) + + self.log.info( + "Extracted instance {} to: {}".format(instance.name, staging_dir) + ) + + # Extract proxy. + kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") + filenames = self._extract( + instance.data["proxy"], attribute_data, kwargs + ) + + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "ass"}) + templates = instance.context.data["anatomy"].templates["publish"] + published_filename_without_extension = StringTemplate( + templates["file"] + ).format(template_data).replace(".ass", "_proxy") + transfers = [] + for filename in filenames: + source = os.path.join(staging_dir, filename) + destination = os.path.join( + instance.data["resourcesDir"], + filename.replace( + filename.split(".")[0], + published_filename_without_extension + ) + ) + transfers.append((source, destination)) + + for source, destination in transfers: + self.log.debug("Transfer: {} > {}".format(source, destination)) + + instance.data["transfers"] = transfers + + def _extract(self, nodes, attribute_data, kwargs): + self.log.info("Writing: " + kwargs["filename"]) + filenames = [] + with attribute_values(attribute_data): with maintained_selection(): self.log.info( - "Writing: {}".format(instance.data["setMembers"]) + "Writing: {}".format(nodes) ) - cmds.select(instance.data["setMembers"], noExpand=True) + cmds.select(nodes, noExpand=True) self.log.info( "Extracting ass sequence with: {}".format(kwargs) @@ -89,18 +142,4 @@ class ExtractAssStandin(publish.Extractor): self.log.info("Exported: {}".format(filenames)) - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'ass', - 'ext': 'ass', - 'files': filenames if len(filenames) > 1 else filenames[0], - "stagingDir": staging_dir, - 'frameStart': kwargs["startFrame"] - } - - instance.data["representations"].append(representation) - - self.log.info("Extracted instance '%s' to: %s" - % (instance.name, staging_dir)) + return filenames diff --git a/openpype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py deleted file mode 100644 index 4937a28a9e..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_assproxy.py +++ /dev/null @@ -1,81 +0,0 @@ -import os -import contextlib - -from maya import cmds - -from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection - - -class ExtractAssProxy(publish.Extractor): - """Extract proxy model as Maya Ascii to use as arnold standin - - - """ - - order = publish.Extractor.order + 0.2 - label = "Ass Proxy (Maya ASCII)" - hosts = ["maya"] - families = ["ass"] - - def process(self, instance): - - @contextlib.contextmanager - def unparent(root): - """Temporarily unparent `root`""" - parent = cmds.listRelatives(root, parent=True) - if parent: - cmds.parent(root, world=True) - yield - self.log.info("{} - {}".format(root, parent)) - cmds.parent(root, parent) - else: - yield - - # Define extract output file path - stagingdir = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(stagingdir, filename) - - # Perform extraction - self.log.info("Performing extraction..") - - # Get only the shape contents we need in such a way that we avoid - # taking along intermediateObjects - proxy = instance.data.get('proxy', None) - - if not proxy: - self.log.info("no proxy mesh") - return - - members = cmds.ls(proxy, - dag=True, - transforms=True, - noIntermediate=True) - self.log.info(members) - - with maintained_selection(): - with unparent(members[0]): - cmds.select(members, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=False, - constraints=False, - expressions=False, - constructionHistory=False) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'ma', - 'ext': 'ma', - 'files': filename, - "stagingDir": stagingdir - } - instance.data["representations"].append(representation) - - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) From 1d6206d41456df1dec2f60617ba54390d1857dfd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 16:39:57 +0000 Subject: [PATCH 224/356] Rename plugin --- .../maya/plugins/load/{load_ass.py => load_arnold_standin.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/maya/plugins/load/{load_ass.py => load_arnold_standin.py} (100%) diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py similarity index 100% rename from openpype/hosts/maya/plugins/load/load_ass.py rename to openpype/hosts/maya/plugins/load/load_arnold_standin.py From 9110f7055415995637039c2da8ac2c1afe2bf9c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:02:09 +0100 Subject: [PATCH 225/356] use safe access to "subset" in AE --- openpype/hosts/aftereffects/plugins/create/create_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 5427edb44b..f661b3bfd7 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -47,7 +47,7 @@ class RenderCreator(Creator): for created_inst, _changes in update_list: api.get_stub().imprint(created_inst.get("instance_id"), created_inst.data_to_store()) - subset_change = _changes["subset"] + subset_change = _changes.get("subset") if subset_change: api.get_stub().rename_item(created_inst.data["members"][0], subset_change.new_value) From 22e4b9862b7aaf1e30b1d1d86fcccff2d2ec6799 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:05:27 +0100 Subject: [PATCH 226/356] added 'order' attribute to creators which is used to process them in specific order --- openpype/pipeline/create/creator_plugins.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 1f92056b23..1aba3f8770 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -107,7 +107,11 @@ class SubsetConvertorPlugin(object): @property def create_context(self): - """Quick access to create context.""" + """Quick access to create context. + + Returns: + CreateContext: Context which initialized the plugin. + """ return self._create_context @@ -157,6 +161,10 @@ class BaseCreator: # Cached group label after first call 'get_group_label' _cached_group_label = None + # Order in which will be plugin executed (collect & update instances) + # less == earlier -> Order '90' will be processed before '100' + order = 100 + # Variable to store logger _log = None From fd31d7815a69b51cfe7bc3ecc0d6a48f13c6e817 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:05:46 +0100 Subject: [PATCH 227/356] added helper methods to get sorted creators --- openpype/pipeline/create/context.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f260e483d9..04db1df790 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1237,6 +1237,37 @@ class CreateContext: """Access to global publish attributes.""" return self._publish_attributes + def get_sorted_creators(self, identifiers=None): + """Sorted creators by 'order' attribute. + + Returns: + List[BaseCreator]: Sorted creator plugins by 'order' value. + """ + + if identifiers is not None: + identifiers = set(identifiers) + creators = [ + creator + for identifier, creator in self.creators.items() + if identifier in identifiers + ] + else: + creators = self.creators.values() + + return sorted( + creators, key=lambda creator: creator.order + ) + + @property + def sorted_creators(self): + return self.get_sorted_creators() + + @property + def sorted_autocreators(self): + return sorted( + self.autocreators.values(), key=lambda creator: creator.order + ) + @classmethod def get_host_misssing_methods(cls, host): """Collect missing methods from host. From 101bbef42d0532dcca235e145cfdae7dc7d22436 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:07:16 +0100 Subject: [PATCH 228/356] added helper method to remove instance --- openpype/pipeline/create/context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 04db1df790..9d8bb63804 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1630,6 +1630,9 @@ class CreateContext: ) ]) + def _remove_instance(self, instance): + self._instances_by_id.pop(instance.id, None) + def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. @@ -1641,7 +1644,7 @@ class CreateContext: from scene metadata. """ - self._instances_by_id.pop(instance.id, None) + self._remove_instance(instance) def add_convertor_item(self, convertor_identifier, label): self.convertor_items_by_id[convertor_identifier] = ConvertorItem( From 3316417173b1f0bdd9a49f91cd210ca4f85236ef Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:07:50 +0100 Subject: [PATCH 229/356] process creators in their order --- openpype/pipeline/create/context.py | 56 ++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9d8bb63804..5682fb3115 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1688,7 +1688,7 @@ class CreateContext: # Collect instances error_message = "Collection of instances for creator {} failed. {}" failed_info = [] - for creator in self.creators.values(): + for creator in self.sorted_creators: label = creator.label identifier = creator.identifier failed = False @@ -1760,7 +1760,8 @@ class CreateContext: error_message = "Failed to run AutoCreator with identifier \"{}\". {}" failed_info = [] - for identifier, creator in self.autocreators.items(): + for creator in self.sorted_autocreators: + identifier = creator.identifier label = creator.label failed = False add_traceback = False @@ -1865,19 +1866,26 @@ class CreateContext: """Save instance specific values.""" instances_by_identifier = collections.defaultdict(list) for instance in self._instances_by_id.values(): + instance_changes = instance.changes() + if not instance_changes: + continue + identifier = instance.creator_identifier - instances_by_identifier[identifier].append(instance) + instances_by_identifier[identifier].append( + UpdateData(instance, instance_changes) + ) + + if not instances_by_identifier: + return error_message = "Instances update of creator \"{}\" failed. {}" failed_info = [] - for identifier, creator_instances in instances_by_identifier.items(): - update_list = [] - for instance in creator_instances: - instance_changes = instance.changes() - if instance_changes: - update_list.append(UpdateData(instance, instance_changes)) - creator = self.creators[identifier] + for creator in self.get_sorted_creators( + instances_by_identifier.keys() + ): + identifier = creator.identifier + update_list = instances_by_identifier[identifier] if not update_list: continue @@ -1913,9 +1921,13 @@ class CreateContext: def remove_instances(self, instances): """Remove instances from context. + All instances that don't have creator identifier leading to existing + creator are just removed from context. + Args: - instances(list): Instances that should be removed - from context. + instances(List[CreatedInstance]): Instances that should be removed. + Remove logic is done using creator, which may require to + do other cleanup than just remove instance from context. """ instances_by_identifier = collections.defaultdict(list) @@ -1925,8 +1937,13 @@ class CreateContext: error_message = "Instances removement of creator \"{}\" failed. {}" failed_info = [] - for identifier, creator_instances in instances_by_identifier.items(): - creator = self.creators.get(identifier) + # Remove instances by creator plugin order + for creator in self.get_sorted_creators( + instances_by_identifier.keys() + ): + identifier = creator.identifier + creator_instances = instances_by_identifier[identifier] + label = creator.label failed = False add_traceback = False @@ -1969,6 +1986,7 @@ class CreateContext: family(str): Instance family for which should be attribute definitions returned. """ + if family not in self._attr_plugins_by_family: import pyblish.logic @@ -1984,7 +2002,13 @@ class CreateContext: return self._attr_plugins_by_family[family] def _get_publish_plugins_with_attr_for_context(self): - """Publish plugins attributes for Context plugins.""" + """Publish plugins attributes for Context plugins. + + Returns: + List[pyblish.api.Plugin]: Publish plugins that have attribute + definitions for context. + """ + plugins = [] for plugin in self.plugins_with_defs: if not plugin.__instanceEnabled__: @@ -2009,7 +2033,7 @@ class CreateContext: return self._collection_shared_data def run_convertor(self, convertor_identifier): - """Run convertor plugin by it's idenfitifier. + """Run convertor plugin by identifier. Conversion is skipped if convertor is not available. From c5be74156652323a43f05e1d486ed77dfd0f0987 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:08:07 +0100 Subject: [PATCH 230/356] handle instances without available creator --- openpype/pipeline/create/context.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 5682fb3115..c978fcc2e1 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1935,6 +1935,12 @@ class CreateContext: identifier = instance.creator_identifier instances_by_identifier[identifier].append(instance) + # Just remove instances from context if creator is not available + missing_creators = set(instances_by_identifier) - set(self.creators) + for identifier in missing_creators: + for instance in instances_by_identifier[identifier]: + self._remove_instance(instance) + error_message = "Instances removement of creator \"{}\" failed. {}" failed_info = [] # Remove instances by creator plugin order @@ -2046,7 +2052,7 @@ class CreateContext: convertor.convert() def run_convertors(self, convertor_identifiers): - """Run convertor plugins by idenfitifiers. + """Run convertor plugins by identifiers. Conversion is skipped if convertor is not available. It is recommended to trigger reset after conversion to reload instances. From c7f051db20c45ab9ba8b835fea3f858051a93df3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:08:42 +0100 Subject: [PATCH 231/356] added 'show_order' attribute to 'Creator' so show order can be different than processing --- openpype/pipeline/create/creator_plugins.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 1aba3f8770..53acb618ed 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -497,6 +497,17 @@ class Creator(BaseCreator): # - similar to instance attribute definitions pre_create_attr_defs = [] + @property + def show_order(self): + """Order in which is creator shown in UI. + + Returns: + int: Order in which is creator shown (less == earlier). By default + is using Creator's 'order' or processing. + """ + + return self.order + @abstractmethod def create(self, subset_name, instance_data, pre_create_data): """Create new instance and store it. From b67181c4e012ac370a1ca284e2112e92589772d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:09:32 +0100 Subject: [PATCH 232/356] added show order to publisher UI --- openpype/tools/publisher/constants.py | 2 ++ openpype/tools/publisher/control.py | 10 ++++++++-- openpype/tools/publisher/widgets/create_widget.py | 10 +++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index e9fdd4774a..b2bfd7dd5c 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -24,6 +24,7 @@ CREATOR_THUMBNAIL_ENABLED_ROLE = QtCore.Qt.UserRole + 5 FAMILY_ROLE = QtCore.Qt.UserRole + 6 GROUP_ROLE = QtCore.Qt.UserRole + 7 CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8 +CREATOR_SORT_ROLE = QtCore.Qt.UserRole + 9 __all__ = ( @@ -36,6 +37,7 @@ __all__ = ( "IS_GROUP_ROLE", "CREATOR_IDENTIFIER_ROLE", "CREATOR_THUMBNAIL_ENABLED_ROLE", + "CREATOR_SORT_ROLE", "FAMILY_ROLE", "GROUP_ROLE", "CONVERTER_IDENTIFIER_ROLE", diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 7c8da66744..d1ee3ea8aa 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -832,7 +832,8 @@ class CreatorItem: default_variants, create_allow_context_change, create_allow_thumbnail, - pre_create_attributes_defs + show_order, + pre_create_attributes_defs, ): self.identifier = identifier self.creator_type = creator_type @@ -846,6 +847,7 @@ class CreatorItem: self.default_variants = default_variants self.create_allow_context_change = create_allow_context_change self.create_allow_thumbnail = create_allow_thumbnail + self.show_order = show_order self.pre_create_attributes_defs = pre_create_attributes_defs def get_group_label(self): @@ -869,6 +871,7 @@ class CreatorItem: pre_create_attr_defs = None create_allow_context_change = None create_allow_thumbnail = None + show_order = creator.order if creator_type is CreatorTypes.artist: description = creator.get_description() detail_description = creator.get_detail_description() @@ -877,6 +880,7 @@ class CreatorItem: pre_create_attr_defs = creator.get_pre_create_attr_defs() create_allow_context_change = creator.create_allow_context_change create_allow_thumbnail = creator.create_allow_thumbnail + show_order = creator.show_order identifier = creator.identifier return cls( @@ -892,7 +896,8 @@ class CreatorItem: default_variants, create_allow_context_change, create_allow_thumbnail, - pre_create_attr_defs + show_order, + pre_create_attr_defs, ) def to_data(self): @@ -915,6 +920,7 @@ class CreatorItem: "default_variants": self.default_variants, "create_allow_context_change": self.create_allow_context_change, "create_allow_thumbnail": self.create_allow_thumbnail, + "show_order": self.show_order, "pre_create_attributes_defs": pre_create_attributes_defs, } diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 07b124f616..994b9ac912 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -18,9 +18,10 @@ from .tasks_widget import CreateWidgetTasksWidget from .precreate_widget import PreCreateWidget from ..constants import ( VARIANT_TOOLTIP, - CREATOR_IDENTIFIER_ROLE, FAMILY_ROLE, + CREATOR_IDENTIFIER_ROLE, CREATOR_THUMBNAIL_ENABLED_ROLE, + CREATOR_SORT_ROLE, ) SEPARATORS = ("---separator---", "---") @@ -441,7 +442,8 @@ class CreateWidget(QtWidgets.QWidget): # Add new families new_creators = set() - for identifier, creator_item in self._controller.creator_items.items(): + creator_items_by_identifier = self._controller.creator_items + for identifier, creator_item in creator_items_by_identifier.items(): if creator_item.creator_type != "artist": continue @@ -457,6 +459,7 @@ class CreateWidget(QtWidgets.QWidget): self._creators_model.appendRow(item) item.setData(creator_item.label, QtCore.Qt.DisplayRole) + item.setData(creator_item.show_order, CREATOR_SORT_ROLE) item.setData(identifier, CREATOR_IDENTIFIER_ROLE) item.setData( creator_item.create_allow_thumbnail, @@ -482,8 +485,9 @@ class CreateWidget(QtWidgets.QWidget): index = indexes[0] identifier = index.data(CREATOR_IDENTIFIER_ROLE) + create_item = creator_items_by_identifier.get(identifier) - self._set_creator_by_identifier(identifier) + self._set_creator(create_item) def _on_plugins_refresh(self): # Trigger refresh only if is visible From 7e1450e95d1cbc2d3df206235a13f280c3c642cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:09:53 +0100 Subject: [PATCH 233/356] modified proxy filter for sorting of creators --- openpype/tools/publisher/widgets/create_widget.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 994b9ac912..bc1f1ec4f7 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -91,6 +91,15 @@ class CreatorShortDescWidget(QtWidgets.QWidget): self._description_label.setText(description) +class CreatorsProxyModel(QtCore.QSortFilterProxyModel): + def lessThan(self, left, right): + l_show_order = left.data(CREATOR_SORT_ROLE) + r_show_order = right.data(CREATOR_SORT_ROLE) + if l_show_order == r_show_order: + return super(CreatorsProxyModel, self).lessThan(left, right) + return l_show_order < r_show_order + + class CreateWidget(QtWidgets.QWidget): def __init__(self, controller, parent=None): super(CreateWidget, self).__init__(parent) @@ -142,7 +151,7 @@ class CreateWidget(QtWidgets.QWidget): creators_view = QtWidgets.QListView(creators_view_widget) creators_model = QtGui.QStandardItemModel() - creators_sort_model = QtCore.QSortFilterProxyModel() + creators_sort_model = CreatorsProxyModel() creators_sort_model.setSourceModel(creators_model) creators_view.setModel(creators_sort_model) From dfb718741355d5b0c4cad8a9a4947d2dce2eb606 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:10:07 +0100 Subject: [PATCH 234/356] removed window title from creator widget --- openpype/tools/publisher/widgets/create_widget.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index bc1f1ec4f7..dbf075c216 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -104,8 +104,6 @@ class CreateWidget(QtWidgets.QWidget): def __init__(self, controller, parent=None): super(CreateWidget, self).__init__(parent) - self.setWindowTitle("Create new instance") - self._controller = controller self._asset_name = None From 310456ec137c621554b91d2a831f875489059106 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:10:24 +0100 Subject: [PATCH 235/356] fix creator_items cache cleanup --- openpype/tools/publisher/control.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index d1ee3ea8aa..435db5fcb3 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -1508,9 +1508,6 @@ class BasePublisherController(AbstractPublisherController): def _reset_attributes(self): """Reset most of attributes that can be reset.""" - # Reset creator items - self._creator_items = None - self.publish_is_running = False self.publish_has_validated = False self.publish_has_crashed = False @@ -1766,6 +1763,8 @@ class PublisherController(BasePublisherController): self._resetting_plugins = True self._create_context.reset_plugins() + # Reset creator items + self._creator_items = None self._resetting_plugins = False From e3c58662b9cf05211d35c5282e5d25a2fb5b46f0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:13:25 +0000 Subject: [PATCH 236/356] Working loading proxy --- .../maya/plugins/load/load_arnold_standin.py | 117 ++++++++++++++---- 1 file changed, 91 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 50b72d87e8..57e1d8a6e0 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -9,7 +9,9 @@ from openpype.pipeline import ( load, get_representation_path ) -from openpype.hosts.maya.api.lib import unique_namespace +from openpype.hosts.maya.api.lib import ( + unique_namespace, get_attribute_input, maintained_selection +) from openpype.hosts.maya.api.pipeline import containerise @@ -22,18 +24,6 @@ def is_sequence(files): return sequence -def set_color(node, context): - project_name = context["project"]["name"] - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - color = colors.get('ass') - if color is not None: - cmds.setAttr(node + ".useOutlinerColor", True) - cmds.setAttr( - node + ".outlinerColor", color[0], color[1], color[2] - ) - - class ArnoldStandinLoader(load.LoaderPlugin): """Load file as Arnold standin""" @@ -62,24 +52,35 @@ class ArnoldStandinLoader(load.LoaderPlugin): label = "{}:{}".format(namespace, name) root = cmds.group(name=label, empty=True) - set_color(root, context) + # Set color. + project_name = context["project"]["name"] + settings = get_project_settings(project_name) + colors = settings['maya']['load']['colors'] + color = colors.get('ass') + if color is not None: + cmds.setAttr(root + ".useOutlinerColor", True) + cmds.setAttr( + root + ".outlinerColor", color[0], color[1], color[2] + ) - # Create transform with shape - transform_name = label + "_standin" + with maintained_selection(): + # Create transform with shape + transform_name = label + "_standin" - standinShape = mtoa.ui.arnoldmenu.createStandIn() - standin = cmds.listRelatives(standinShape, parent=True)[0] - standin = cmds.rename(standin, transform_name) - standinShape = cmds.listRelatives(standin, shapes=True)[0] + standinShape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standinShape, parent=True)[0] + standin = cmds.rename(standin, transform_name) + standinShape = cmds.listRelatives(standin, shapes=True)[0] - cmds.parent(standin, root) + cmds.parent(standin, root) - # Set the standin filepath - cmds.setAttr(standinShape + ".dso", self.fname, type="string") - sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) - cmds.setAttr(standinShape + ".useFrameExtension", sequence) + # Set the standin filepath + dso_path, operator = self._setup_proxy(standinShape, self.fname) + cmds.setAttr(standinShape + ".dso", dso_path, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) + cmds.setAttr(standinShape + ".useFrameExtension", sequence) - nodes = [root, standin] + nodes = [root, standin, operator] self[:] = nodes return containerise( @@ -89,6 +90,70 @@ class ArnoldStandinLoader(load.LoaderPlugin): context=context, loader=self.__class__.__name__) + def get_next_free_multi_index(self, attr_name): + """Find the next unconnected multi index at the input attribute.""" + + start_index = 0 + # Assume a max of 10 million connections + while start_index < 10000000: + connection_info = cmds.connectionInfo( + "{}[{}]".format(attr_name, start_index), + sourceFromDestination=True + ) + if len(connection_info or []) == 0: + return start_index + start_index += 1 + + def _setup_proxy(self, shape, path): + basename_split = os.path.basename(path).split(".") + proxy_basename = ( + basename_split[0] + "_proxy." + ".".join(basename_split[1:]) + ) + proxy_path = "/".join( + [os.path.dirname(path), "resources", proxy_basename] + ) + + if not os.path.exists(proxy_path): + self.log.error("Proxy files do not exist. Skipping proxy setup.") + return path + + options_node = "defaultArnoldRenderOptions" + merge_operator = get_attribute_input(options_node + ".operator") + if merge_operator is None: + merge_operator = cmds.createNode("aiMerge") + cmds.connectAttr( + merge_operator + ".message", options_node + ".operator" + ) + + merge_operator = merge_operator.split(".")[0] + + string_replace_operator = cmds.createNode("aiStringReplace") + cmds.setAttr( + string_replace_operator + ".selection", + "*.(@node=='procedural')", + type="string" + ) + cmds.setAttr( + string_replace_operator + ".match", + "resources/" + proxy_basename, + type="string" + ) + cmds.setAttr( + string_replace_operator + ".replace", + os.path.basename(path), + type="string" + ) + + cmds.connectAttr( + string_replace_operator + ".out", + "{}.inputs[{}]".format( + merge_operator, + self.get_next_free_multi_index(merge_operator + ".inputs") + ) + ) + + return proxy_path, string_replace_operator + def update(self, container, representation): # Update the standin standins = list() From cd1b02c59504dab76294663b326851cf70920d7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:14:53 +0100 Subject: [PATCH 237/356] move batch creator after simple creators --- .../hosts/traypublisher/plugins/create/create_movie_batch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py index 1dc4bad9b3..d077131e4c 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py +++ b/openpype/hosts/traypublisher/plugins/create/create_movie_batch.py @@ -33,6 +33,8 @@ class BatchMovieCreator(TrayPublishCreator): create_allow_context_change = False version_regex = re.compile(r"^(.+)_v([0-9]+)$") + # Position batch creator after simple creators + order = 110 def __init__(self, project_settings, *args, **kwargs): super(BatchMovieCreator, self).__init__(project_settings, From af752ae60663cd64f038ab154216a52e61d6dcb2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:23:04 +0000 Subject: [PATCH 238/356] Working proxy update --- .../maya/plugins/load/load_arnold_standin.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 57e1d8a6e0..a90aa02d4d 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -104,7 +104,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): return start_index start_index += 1 - def _setup_proxy(self, shape, path): + def _get_proxy_path(self, path): basename_split = os.path.basename(path).split(".") proxy_basename = ( basename_split[0] + "_proxy." + ".".join(basename_split[1:]) @@ -112,10 +112,14 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_path = "/".join( [os.path.dirname(path), "resources", proxy_basename] ) + return proxy_basename, proxy_path + + def _setup_proxy(self, shape, path): + proxy_basename, proxy_path = self._get_proxy_path(path) if not os.path.exists(proxy_path): self.log.error("Proxy files do not exist. Skipping proxy setup.") - return path + return os.path.basename(path), path options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") @@ -156,20 +160,33 @@ class ArnoldStandinLoader(load.LoaderPlugin): def update(self, container, representation): # Update the standin - standins = list() members = cmds.sets(container['objectName'], query=True) for member in members: + if cmds.nodeType(member) == "aiStringReplace": + string_replace_operator = member + shapes = cmds.listRelatives(member, shapes=True) if not shapes: continue if cmds.nodeType(shapes[0]) == "aiStandIn": - standins.append(shapes[0]) + standin = shapes[0] path = get_representation_path(representation) + proxy_basename, proxy_path = self._get_proxy_path(path) + cmds.setAttr( + string_replace_operator + ".match", + "resources/" + proxy_basename, + type="string" + ) + cmds.setAttr( + string_replace_operator + ".replace", + os.path.basename(path), + type="string" + ) + cmds.setAttr(standin + ".dso", proxy_path, type="string") + sequence = is_sequence(os.listdir(os.path.dirname(path))) - for standin in standins: - cmds.setAttr(standin + ".dso", path, type="string") - cmds.setAttr(standin + ".useFrameExtension", sequence) + cmds.setAttr(standin + ".useFrameExtension", sequence) cmds.setAttr( container["objectName"] + ".representation", From b51a2a72579966e721e268de68ad1d01e62c93a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Feb 2023 19:24:30 +0100 Subject: [PATCH 239/356] added more docstrings --- openpype/pipeline/create/context.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c978fcc2e1..0ee70f39cb 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1240,6 +1240,10 @@ class CreateContext: def get_sorted_creators(self, identifiers=None): """Sorted creators by 'order' attribute. + Args: + identifiers (Iterable[str]): Filter creators by identifiers. All + creators are returned if 'None' is passed. + Returns: List[BaseCreator]: Sorted creator plugins by 'order' value. """ @@ -1260,10 +1264,22 @@ class CreateContext: @property def sorted_creators(self): + """Sorted creators by 'order' attribute. + + Returns: + List[BaseCreator]: Sorted creator plugins by 'order' value. + """ + return self.get_sorted_creators() @property def sorted_autocreators(self): + """Sorted auto-creators by 'order' attribute. + + Returns: + List[AutoCreator]: Sorted plugins by 'order' value. + """ + return sorted( self.autocreators.values(), key=lambda creator: creator.order ) From bb4a44fe33d0a6e1518179f19efbf309969a3d28 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 7 Feb 2023 18:30:34 +0000 Subject: [PATCH 240/356] Clean up string replace operator --- .../hosts/maya/plugins/load/load_arnold_standin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index a90aa02d4d..635e86708b 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -75,7 +75,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.parent(standin, root) # Set the standin filepath - dso_path, operator = self._setup_proxy(standinShape, self.fname) + dso_path, operator = self._setup_proxy( + standinShape, self.fname, namespace + ) cmds.setAttr(standinShape + ".dso", dso_path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standinShape + ".useFrameExtension", sequence) @@ -114,7 +116,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) return proxy_basename, proxy_path - def _setup_proxy(self, shape, path): + def _setup_proxy(self, shape, path, namespace): proxy_basename, proxy_path = self._get_proxy_path(path) if not os.path.exists(proxy_path): @@ -131,7 +133,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): merge_operator = merge_operator.split(".")[0] - string_replace_operator = cmds.createNode("aiStringReplace") + string_replace_operator = cmds.createNode( + "aiStringReplace", name=namespace + ":string_replace_operator" + ) cmds.setAttr( string_replace_operator + ".selection", "*.(@node=='procedural')", @@ -198,7 +202,6 @@ class ArnoldStandinLoader(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) From d819a0c8c8882d325b708529205adf2315099625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Tue, 7 Feb 2023 19:37:11 +0100 Subject: [PATCH 241/356] :art: allow underscore in branch names allow underscore in branch names in regex for pre-commit hook - no-commit-branch --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 890df4613e..eec388924e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,4 +9,4 @@ repos: - id: check-yaml - id: check-added-large-files - id: no-commit-to-branch - args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-]+)$).*' ] + args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] From cba888e66e5e874cfa84add8f396f5a72d1b3e40 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Feb 2023 20:02:36 +0100 Subject: [PATCH 242/356] small docstring/comments enhancements --- openpype/pipeline/create/context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index f46b4eccdb..427cabce67 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -252,12 +252,13 @@ class TrackChangesItem(object): ``` Args: - old_value (Any): Previous value. + old_value (Any): Old value. new_value (Any): New value. """ def __init__(self, old_value, new_value): self._changed = old_value != new_value + # Resolve if value is '_EMPTY_VALUE' after comparison of the values if old_value is _EMPTY_VALUE: old_value = None if new_value is _EMPTY_VALUE: @@ -412,7 +413,7 @@ class TrackChangesItem(object): """All keys that are available in old and new value. Empty set is returned if both old and new value are not a dict. - Output it is Union of 'old_keys' and 'new_keys'. + Output is Union of 'old_keys' and 'new_keys'. Returns: Set[str]: All keys from old and new value. From e1befa7ae3a01be70577539b747445377b09f28f Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 8 Feb 2023 03:28:55 +0000 Subject: [PATCH 243/356] [Automated] Bump version --- openpype/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/version.py b/openpype/version.py index 3941912c6e..c65d1197c1 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.2" +__version__ = "3.15.1-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 2fc4f6fe39..6e88404700 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1" # OpenPype +version = "3.15.1-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 0921cef11ebdf3c4236c5b5b263e1352daf64721 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 03:29:43 +0000 Subject: [PATCH 244/356] Bump http-cache-semantics from 4.1.0 to 4.1.1 in /website Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] --- website/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/yarn.lock b/website/yarn.lock index 9af21c7500..0a56928cd9 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4273,9 +4273,9 @@ htmlparser2@^6.1.0: entities "^2.0.0" http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + 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-deceiver@^1.2.7: version "1.2.7" From 87712716d047716800bf27a934a9648b9024bed5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 07:48:45 +0000 Subject: [PATCH 245/356] Combine Alembic standin --- .../maya/plugins/load/load_abc_to_standin.py | 132 ------------------ .../maya/plugins/load/load_arnold_standin.py | 18 +-- 2 files changed, 10 insertions(+), 140 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/load/load_abc_to_standin.py diff --git a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py b/openpype/hosts/maya/plugins/load/load_abc_to_standin.py deleted file mode 100644 index 70866a3ba6..0000000000 --- a/openpype/hosts/maya/plugins/load/load_abc_to_standin.py +++ /dev/null @@ -1,132 +0,0 @@ -import os - -from openpype.pipeline import ( - legacy_io, - load, - get_representation_path -) -from openpype.settings import get_project_settings - - -class AlembicStandinLoader(load.LoaderPlugin): - """Load Alembic as Arnold Standin""" - - families = ["animation", "model", "proxyAbc", "pointcache"] - representations = ["abc"] - - label = "Import Alembic as Arnold Standin" - order = -5 - icon = "code-fork" - color = "orange" - - def load(self, context, name, namespace, options): - - import maya.cmds as cmds - import mtoa.ui.arnoldmenu - from openpype.hosts.maya.api.pipeline import containerise - from openpype.hosts.maya.api.lib import unique_namespace - - version = context["version"] - version_data = version.get("data", {}) - family = version["data"]["families"] - self.log.info("version_data: {}\n".format(version_data)) - self.log.info("family: {}\n".format(family)) - frameStart = version_data.get("frameStart", None) - - asset = context["asset"]["name"] - namespace = namespace or unique_namespace( - asset + "_", - prefix="_" if asset[0].isdigit() else "", - suffix="_", - ) - - # Root group - label = "{}:{}".format(namespace, name) - root = cmds.group(name=label, empty=True) - - settings = get_project_settings(os.environ['AVALON_PROJECT']) - colors = settings["maya"]["load"]["colors"] - fps = legacy_io.Session["AVALON_FPS"] - c = colors.get(family[0]) - if c is not None: - r = (float(c[0]) / 255) - g = (float(c[1]) / 255) - b = (float(c[2]) / 255) - cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - r, g, b) - - transform_name = label + "_ABC" - - standinShape = cmds.ls(mtoa.ui.arnoldmenu.createStandIn())[0] - standin = cmds.listRelatives(standinShape, parent=True, - typ="transform") - standin = cmds.rename(standin, transform_name) - standinShape = cmds.listRelatives(standin, children=True)[0] - - cmds.parent(standin, root) - - # Set the standin filepath - cmds.setAttr(standinShape + ".dso", self.fname, type="string") - cmds.setAttr(standinShape + ".abcFPS", float(fps)) - - if frameStart is None: - cmds.setAttr(standinShape + ".useFrameExtension", 0) - - elif "model" in family: - cmds.setAttr(standinShape + ".useFrameExtension", 0) - - else: - cmds.setAttr(standinShape + ".useFrameExtension", 1) - - nodes = [root, standin] - self[:] = nodes - - return containerise( - name=name, - namespace=namespace, - nodes=nodes, - context=context, - loader=self.__class__.__name__) - - def update(self, container, representation): - - import pymel.core as pm - - path = get_representation_path(representation) - fps = legacy_io.Session["AVALON_FPS"] - # Update the standin - standins = list() - members = pm.sets(container['objectName'], query=True) - self.log.info("container:{}".format(container)) - for member in members: - shape = member.getShape() - if (shape and shape.type() == "aiStandIn"): - standins.append(shape) - - for standin in standins: - standin.dso.set(path) - standin.abcFPS.set(float(fps)) - if "modelMain" in container['objectName']: - standin.useFrameExtension.set(0) - else: - standin.useFrameExtension.set(1) - - container = pm.PyNode(container["objectName"]) - container.representation.set(str(representation["_id"])) - - def switch(self, container, representation): - 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) - - # Clean up the namespace - try: - cmds.namespace(removeNamespace=container['namespace'], - deleteNamespaceContent=True) - except RuntimeError: - pass diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 635e86708b..3cfc5b71b3 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -25,12 +25,12 @@ def is_sequence(files): class ArnoldStandinLoader(load.LoaderPlugin): - """Load file as Arnold standin""" + """Load as Arnold standin""" - families = ["ass"] - representations = ["ass"] + families = ["ass", "animation", "model", "proxyAbc", "pointcache"] + representations = ["ass", "abc"] - label = "Load file as Arnold standin" + label = "Load as Arnold standin" order = -5 icon = "code-fork" color = "orange" @@ -75,14 +75,16 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.parent(standin, root) # Set the standin filepath - dso_path, operator = self._setup_proxy( + path, operator = self._setup_proxy( standinShape, self.fname, namespace ) - cmds.setAttr(standinShape + ".dso", dso_path, type="string") + cmds.setAttr(standinShape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standinShape + ".useFrameExtension", sequence) - nodes = [root, standin, operator] + nodes = [root, standin] + if operator is not None: + nodes.append(operator) self[:] = nodes return containerise( @@ -121,7 +123,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): if not os.path.exists(proxy_path): self.log.error("Proxy files do not exist. Skipping proxy setup.") - return os.path.basename(path), path + return path, None options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") From 067a6c7c93ba3b55debf0a6deae39218871bee76 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:19:11 +0000 Subject: [PATCH 246/356] Code cosmetics --- ...ect_ass.py => collect_arnold_scene_source.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename openpype/hosts/maya/plugins/publish/{collect_ass.py => collect_arnold_scene_source.py} (72%) diff --git a/openpype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py similarity index 72% rename from openpype/hosts/maya/plugins/publish/collect_ass.py rename to openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 7b5d1a00c7..06d0786665 100644 --- a/openpype/hosts/maya/plugins/publish/collect_ass.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -3,16 +3,16 @@ from maya import cmds import pyblish.api -class CollectAssData(pyblish.api.InstancePlugin): - """Collect Ass data.""" +class CollectArnoldSceneSource(pyblish.api.InstancePlugin): + """Collect Arnold Scene Source data.""" # Offset to be after renderable camera collection. order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Ass' + label = "Collect Arnold Scene Source" families = ["ass"] def process(self, instance): - objsets = instance.data['setMembers'] + objsets = instance.data["setMembers"] for objset in objsets: objset = str(objset) @@ -21,11 +21,11 @@ class CollectAssData(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if "content_SET" in objset: - instance.data['setMembers'] = members - self.log.debug('content members: {}'.format(members)) + instance.data["setMembers"] = members + self.log.debug("content members: {}".format(members)) elif objset.startswith("proxy_SET"): - instance.data['proxy'] = members - self.log.debug('proxy members: {}'.format(members)) + instance.data["proxy"] = members + self.log.debug("proxy members: {}".format(members)) # Use camera in object set if present else default to render globals # camera. From 7d5ede8ae51e5de7d22c8b6dfd3270638502dd98 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:27:10 +0000 Subject: [PATCH 247/356] Fix creating multiple ass instances --- .../{create_ass.py => create_arnold_scene_source.py} | 10 +++++----- .../plugins/publish/collect_arnold_scene_source.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename openpype/hosts/maya/plugins/create/{create_ass.py => create_arnold_scene_source.py} (84%) diff --git a/openpype/hosts/maya/plugins/create/create_ass.py b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py similarity index 84% rename from openpype/hosts/maya/plugins/create/create_ass.py rename to openpype/hosts/maya/plugins/create/create_arnold_scene_source.py index 935a068ca5..2afb897e94 100644 --- a/openpype/hosts/maya/plugins/create/create_ass.py +++ b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py @@ -6,7 +6,7 @@ from openpype.hosts.maya.api import ( from maya import cmds -class CreateAss(plugin.Creator): +class CreateArnoldSceneSource(plugin.Creator): """Arnold Scene Source""" name = "ass" @@ -29,7 +29,7 @@ class CreateAss(plugin.Creator): maskOperator = False def __init__(self, *args, **kwargs): - super(CreateAss, self).__init__(*args, **kwargs) + super(CreateArnoldSceneSource, self).__init__(*args, **kwargs) # Add animation data self.data.update(lib.collect_animation_data()) @@ -52,7 +52,7 @@ class CreateAss(plugin.Creator): self.data["maskOperator"] = self.maskOperator def process(self): - instance = super(CreateAss, self).process() + instance = super(CreateArnoldSceneSource, self).process() nodes = [] @@ -61,6 +61,6 @@ class CreateAss(plugin.Creator): cmds.sets(nodes, rm=instance) - assContent = cmds.sets(name="content_SET") - assProxy = cmds.sets(name="proxy_SET", empty=True) + assContent = cmds.sets(name=instance + "_content_SET") + assProxy = cmds.sets(name=instance + "_proxy_SET", empty=True) cmds.sets([assContent, assProxy], forceElement=instance) 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 06d0786665..c0275eef7b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -20,10 +20,10 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): if members is None: self.log.warning("Skipped empty instance: \"%s\" " % objset) continue - if "content_SET" in objset: + if objset.endswith("content_SET"): instance.data["setMembers"] = members self.log.debug("content members: {}".format(members)) - elif objset.startswith("proxy_SET"): + elif objset.endswith("proxy_SET"): instance.data["proxy"] = members self.log.debug("proxy members: {}".format(members)) From 33f2168e785206bd6eb3096fa52789f6cc7d737f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 08:27:17 +0000 Subject: [PATCH 248/356] Code cosmetics --- .../publish/{extract_ass.py => extract_arnold_scene_source.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/maya/plugins/publish/{extract_ass.py => extract_arnold_scene_source.py} (100%) diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py similarity index 100% rename from openpype/hosts/maya/plugins/publish/extract_ass.py rename to openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py From f03cb52538b048167ce5a6f994953094b58d88c5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 09:40:25 +0000 Subject: [PATCH 249/356] Proxy workflow for pointcache --- .../maya/plugins/create/create_pointcache.py | 8 +++ .../maya/plugins/load/load_arnold_standin.py | 3 +- .../plugins/publish/collect_pointcache.py | 30 ++++++++++ .../plugins/publish/extract_pointcache.py | 55 +++++++++++++++++-- 4 files changed, 89 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index cdec140ea8..63c0490dc7 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -1,3 +1,5 @@ +from maya import cmds + from openpype.hosts.maya.api import ( lib, plugin @@ -37,3 +39,9 @@ class CreatePointCache(plugin.Creator): # Default to not send to farm. self.data["farm"] = False self.data["priority"] = 50 + + def process(self): + instance = super(CreatePointCache, self).process() + + assProxy = cmds.sets(name=instance + "_proxy_SET", empty=True) + cmds.sets(assProxy, forceElement=instance) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 3cfc5b71b3..e2bb89ed77 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -138,9 +138,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): string_replace_operator = cmds.createNode( "aiStringReplace", name=namespace + ":string_replace_operator" ) + node_type = "alembic" if path.endswith(".abc") else "procedural" cmds.setAttr( string_replace_operator + ".selection", - "*.(@node=='procedural')", + "*.(@node=='{}')".format(node_type), type="string" ) cmds.setAttr( diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index a841341f72..332992ca92 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -1,3 +1,5 @@ +from maya import cmds + import pyblish.api @@ -12,3 +14,31 @@ class CollectPointcache(pyblish.api.InstancePlugin): def process(self, instance): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + + proxy_set = None + for node in instance.data["setMembers"]: + if cmds.nodeType(node) != "objectSet": + continue + members = cmds.sets(node, query=True) + if members is None: + self.log.warning("Skipped empty objectset: \"%s\" " % node) + continue + if node.endswith("proxy_SET"): + proxy_set = node + instance.data["proxy"] = [] + instance.data["proxyRoots"] = [] + for member in members: + instance.data["proxy"].extend(cmds.ls(member, long=True)) + instance.data["proxyRoots"].extend( + cmds.ls(member, long=True) + ) + instance.data["proxy"].extend( + cmds.listRelatives(member, shapes=True, fullPath=True) + ) + self.log.debug( + "proxy members: {}".format(instance.data["proxy"]) + ) + + if proxy_set: + instance.remove(proxy_set) + instance.data["setMembers"].remove(proxy_set) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7ed73fd5b0..0eb65e4226 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -1,4 +1,5 @@ import os +import copy from maya import cmds @@ -9,6 +10,7 @@ from openpype.hosts.maya.api.lib import ( maintained_selection, iter_visible_nodes_in_range ) +from openpype.lib import StringTemplate class ExtractAlembic(publish.Extractor): @@ -23,9 +25,7 @@ class ExtractAlembic(publish.Extractor): label = "Extract Pointcache (Alembic)" hosts = ["maya"] - families = ["pointcache", - "model", - "vrayproxy"] + families = ["pointcache", "model", "vrayproxy"] targets = ["local", "remote"] def process(self, instance): @@ -87,6 +87,7 @@ class ExtractAlembic(publish.Extractor): end=end)) suspend = not instance.data.get("refresh", False) + self.log.info(nodes) with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) @@ -101,9 +102,9 @@ class ExtractAlembic(publish.Extractor): instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": dirname } instance.data["representations"].append(representation) @@ -112,6 +113,48 @@ class ExtractAlembic(publish.Extractor): self.log.info("Extracted {} to {}".format(instance, dirname)) + # Extract proxy. + if not instance.data.get("proxy"): + return + + path = path.replace(".abc", "_proxy.abc") + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = instance.data["proxyRoots"] + + with suspended_refresh(suspend=suspend): + with maintained_selection(): + cmds.select(instance.data["proxy"]) + extract_alembic( + file=path, + startFrame=start, + endFrame=end, + **options + ) + + template_data = copy.deepcopy(instance.data["anatomyData"]) + template_data.update({"ext": "abc"}) + templates = instance.context.data["anatomy"].templates["publish"] + published_filename_without_extension = StringTemplate( + templates["file"] + ).format(template_data).replace(".abc", "_proxy") + transfers = [] + destination = os.path.join( + instance.data["resourcesDir"], + filename.replace( + filename.split(".")[0], + published_filename_without_extension + ) + ) + transfers.append((path, destination)) + + for source, destination in transfers: + self.log.debug("Transfer: {} > {}".format(source, destination)) + + instance.data["transfers"] = transfers + def get_members_and_roots(self, instance): return instance[:], instance.data.get("setMembers") From 713ede50049b3df473fb66d2ce8e556bce46df5c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 8 Feb 2023 10:11:10 +0000 Subject: [PATCH 250/356] Documentation --- website/docs/artist_hosts_maya.md | 3 +++ website/docs/artist_hosts_maya_arnold.md | 16 ++++++++++++++++ website/docs/assets/maya-pointcache_setup.png | Bin 49860 -> 88602 bytes website/sidebars.js | 1 + 4 files changed, 20 insertions(+) create mode 100644 website/docs/artist_hosts_maya_arnold.md diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 14619e52a1..9fab845e62 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -308,6 +308,8 @@ Select its root and Go **OpenPype → Create...** and select **Point Cache**. After that, publishing will create corresponding **abc** files. +When creating the instance, a objectset child `proxy` will be created. Meshes in the `proxy` objectset will be the viewport representation where loading supports proxies. Proxy representations are stored as `resources` of the subset. + Example setup: ![Maya - Point Cache Example](assets/maya-pointcache_setup.png) @@ -315,6 +317,7 @@ Example setup: :::note Publish on farm If your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle `Farm` property in created pointcache instance to True. +::: ### Loading Point Caches diff --git a/website/docs/artist_hosts_maya_arnold.md b/website/docs/artist_hosts_maya_arnold.md new file mode 100644 index 0000000000..b8b8da6d57 --- /dev/null +++ b/website/docs/artist_hosts_maya_arnold.md @@ -0,0 +1,16 @@ +--- +id: artist_hosts_maya_arnold +title: Arnold for Maya +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. + +## Standin +Arnold Scene Source `ass` and Alembic `abc` are supported to load as standins. + +If a subset has a proxy representation, this will be used as display in the viewport. At render time the standin path will be replaced using the recommended string replacement workflow; + +https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_maya_operators_am_Updating_procedural_file_paths_with_string_replace_html diff --git a/website/docs/assets/maya-pointcache_setup.png b/website/docs/assets/maya-pointcache_setup.png index 8904baa239f90f342f7252f5732ccf57dccf2ad7..b2dc12690199c264e6a8ceebbbff4308e08764be 100644 GIT binary patch literal 88602 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%0DJ*m$sy4TvzuBkclL5Ev|*snH)H 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 Zt#D4oNKPwk<*K!O4Q;!AW?@DqLa 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 Date: Wed, 8 Feb 2023 12:15:43 +0100 Subject: [PATCH 251/356] Fix - added missed scopes for Slack bot --- openpype/modules/slack/manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 7a65cc5915..233c39fbaf 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,6 +19,8 @@ oauth_config: - chat:write.public - files:write - channels:read + - users:read + - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 1a7f7d310ec02be47e6e95e5e8781d72ba181a33 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 12:21:55 +0100 Subject: [PATCH 252/356] OP-4653 - changed exception Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/photoshop/plugins/create/create_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index 8a103ea6c2..9bf47733f5 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -48,7 +48,7 @@ class ImageCreator(Creator): try: group = stub.group_selected_layers(subset_name_from_ui) except: - raise ValueError("Cannot group locked Background layer!") + raise CreatorError("Cannot group locked Background layer!") groups_to_create.append(group) # create empty group if nothing selected From 21d745cc3cc9ad288a1aa0d7eb6329a5aeea7f9e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 12:25:29 +0100 Subject: [PATCH 253/356] OP-4822 - remove unwanted variable Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../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 e7c1899513..ed37ff1897 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -105,7 +105,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): jobInfo = {} pluginInfo = {} group = "none" - disable_strict_check_profiles = [] def get_job_info(self): job_info = DeadlineJobInfo(Plugin="MayaBatch") From 3156cc542508949c7786809db94feb0de4c7da35 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 12:39:28 +0100 Subject: [PATCH 254/356] OP-4653 - added missed import for CreatorError --- openpype/hosts/photoshop/plugins/create/create_image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index 9bf47733f5..198c1586dc 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -5,7 +5,8 @@ from openpype.lib import BoolDef from openpype.pipeline import ( Creator, CreatedInstance, - legacy_io + legacy_io, + CreatorError ) from openpype.lib import prepare_template_data from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS From b28f91769367ad9b64eadb9d04f18bb2b4529c20 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 13:37:50 +0100 Subject: [PATCH 255/356] Fix - remove minor part in toml Causes issue in create_env and new Poetry --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e88404700..2fc4f6fe39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1-nightly.3" # OpenPype +version = "3.15.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 6e55202349b5ea4f62001a586e44e8306143fbee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Feb 2023 14:23:10 +0100 Subject: [PATCH 256/356] change used method names from 'isAlive' to 'is_alive' --- openpype/modules/ftrack/ftrack_server/event_server_cli.py | 6 +++--- openpype/modules/ftrack/tray/login_dialog.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index 25ebad6658..ad7ffd8e25 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -316,7 +316,7 @@ def main_loop(ftrack_url): statuser_failed_count = 0 # If thread failed test Ftrack and Mongo connection - elif not statuser_thread.isAlive(): + elif not statuser_thread.is_alive(): statuser_thread.join() statuser_thread = None ftrack_accessible = False @@ -359,7 +359,7 @@ def main_loop(ftrack_url): storer_failed_count = 0 # If thread failed test Ftrack and Mongo connection - elif not storer_thread.isAlive(): + elif not storer_thread.is_alive(): if storer_thread.mongo_error: raise MongoPermissionsError() storer_thread.join() @@ -396,7 +396,7 @@ def main_loop(ftrack_url): processor_failed_count = 0 # If thread failed test Ftrack and Mongo connection - elif not processor_thread.isAlive(): + elif not processor_thread.is_alive(): if processor_thread.mongo_error: raise Exception( "Exiting because have issue with acces to MongoDB" diff --git a/openpype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py index 0e676545f7..f374a71178 100644 --- a/openpype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -259,7 +259,7 @@ class CredentialsDialog(QtWidgets.QDialog): # If there is an existing server thread running we need to stop it. if self._login_server_thread: - if self._login_server_thread.isAlive(): + if self._login_server_thread.is_alive(): self._login_server_thread.stop() self._login_server_thread.join() self._login_server_thread = None From 2b78c9404c587e4a3f3f19402e30c4741a07b3db Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 15:01:02 +0100 Subject: [PATCH 257/356] OP-4822 - push property to render instance render instance is created from collected instance explicitly. --- openpype/hosts/maya/plugins/publish/collect_render.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index b1ad3ca58e..fc297ef612 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -318,7 +318,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "aovSeparator": layer_render_products.layer_data.aov_separator, # noqa: E501 "renderSetupIncludeLights": render_instance.data.get( "renderSetupIncludeLights" - ) + ), + "strict_error_checking": render_instance.data.get( + "strict_error_checking") } # Collect Deadline url if Deadline module is enabled From 91e1d28a9fdc15e7841cd55237f81607dfe0a7b8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 15:09:26 +0100 Subject: [PATCH 258/356] OP-4513 - platform specific logic Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 108c418e7b..b0560ce1e8 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -191,9 +191,8 @@ def get_openpype_executable(): "OpenPypeInstallationDirs", "") # clean '\ ' for MacOS pasting - if exe_list: + if platform.system().lower() == "darwin": exe_list = exe_list.replace("\\ ", " ") - if dir_list: dir_list = dir_list.replace("\\ ", " ") return exe_list, dir_list From 6894e17bbc9881b847a3a141574d336441b35f84 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 16:18:45 +0100 Subject: [PATCH 259/356] Refactor - use values from context if available Remove unnecessary keys from data --- openpype/plugins/load/add_site.py | 33 +++++++++++++++++++++++-------- openpype/tools/loader/widgets.py | 16 +++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 860e0ef15e..f0c9887b66 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -40,16 +40,29 @@ class AddSyncSite(load.LoaderPlugin): return self._sync_server def load(self, context, name=None, namespace=None, data=None): + """"Adds site skeleton information on representation_id + + Looks for loaded containers for workfile, adds them site skeleton too + (eg. they should be downloaded too). + Handles hero versions (for representation_id and referenced subsets) + Args: + context (dict): + name (str): + namespace (str): + data (dict): expects {"site_name": SITE_NAME_TO_ADD} + """ # self.log wont propagate print("Adding {} to representation: {}".format( data["site_name"], data["_id"])) - family = context["representation"]["context"]["family"] - project_name = data["project_name"] - repre_id = data["_id"] + project_name = context["project"]["name"] + repre_doc = context["representation"] + family = repre_doc["context"]["family"] + repre_id = [repre_doc["_id"]] site_name = data["site_name"] representation_ids = self._add_hero_representation_ids(project_name, - repre_id) + repre_id, + repre_doc) for repre_id in representation_ids: self.sync_server.add_site(project_name, repre_id, site_name, @@ -79,20 +92,24 @@ class AddSyncSite(load.LoaderPlugin): """No real file loading""" return "" - def _add_hero_representation_ids(self, project_name, repre_id): + def _add_hero_representation_ids(self, project_name, repre_id, + repre_doc=None): """Find hero version if exists for repre_id. Args: project_name (str) repre_id (ObjectId) + repre_doc (dict): repre document for 'repre_id', might be collected + previously Returns: (list): at least [repre_id] if no hero version found """ representation_ids = [repre_id] - repre_doc = get_representation_by_id( - project_name, repre_id, fields=["_id", "parent", "name"] - ) + if not repre_doc: + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["_id", "parent", "name"] + ) version_doc = get_version_by_id(project_name, repre_doc["parent"]) if version_doc["type"] != "hero_version": diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index faef6c8a26..dbf2feb624 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1468,23 +1468,21 @@ class RepresentationWidget(QtWidgets.QWidget): repre_ids = [] data_by_repre_id = {} selected_side = action_representation.get("selected_side") + site_name = "{}_site_name".format(selected_side) is_sync_loader = tools_lib.is_sync_loader(loader) for item in items: - item_id = item.get("_id") - repre_ids.append(item_id) + repre_id = item["_id"] + repre_ids.append(repre_id) if not is_sync_loader: continue - site_name = "{}_site_name".format(selected_side) data_site_name = item.get(site_name) if not data_site_name: continue - data_by_repre_id[item_id] = { - "_id": item_id, - "site_name": data_site_name, - "project_name": self.dbcon.active_project() + data_by_repre_id[repre_id] = { + "site_name": data_site_name } repre_contexts = get_repres_contexts(repre_ids, self.dbcon) @@ -1574,8 +1572,8 @@ def _load_representations_by_loader(loader, repre_contexts, version_name = version_doc.get("name") try: if data_by_repre_id: - _id = repre_context["representation"]["_id"] - data = data_by_repre_id.get(_id) + repre_id = repre_context["representation"]["_id"] + data = data_by_repre_id.get(repre_id) options.update(data) load_with_repre_context( loader, From d9877ae62323cea4099e9c01f2e47caffa4f5d4d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 16:54:20 +0100 Subject: [PATCH 260/356] Refactor - remove explict handling of hero version If representation_id should be added(downloaded) it shouldn't download hero version if downloaded repre is actually latest, eg hero version. They files are completely separate. Hero version should be downloaded explicitly in Loader. --- openpype/plugins/load/add_site.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index f0c9887b66..98a390fb30 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -44,7 +44,6 @@ class AddSyncSite(load.LoaderPlugin): Looks for loaded containers for workfile, adds them site skeleton too (eg. they should be downloaded too). - Handles hero versions (for representation_id and referenced subsets) Args: context (dict): name (str): @@ -60,13 +59,8 @@ class AddSyncSite(load.LoaderPlugin): repre_id = [repre_doc["_id"]] site_name = data["site_name"] - representation_ids = self._add_hero_representation_ids(project_name, - repre_id, - repre_doc) - - for repre_id in representation_ids: - self.sync_server.add_site(project_name, repre_id, site_name, - force=True) + self.sync_server.add_site(project_name, repre_id, site_name, + force=True) if family == "workfile": links = get_linked_representation_id( @@ -76,12 +70,9 @@ class AddSyncSite(load.LoaderPlugin): ) for link_repre_id in links: try: - representation_ids = self._add_hero_representation_ids( - project_name, link_repre_id) - for repre_id in representation_ids: - self.sync_server.add_site(project_name, repre_id, - site_name, - force=False) + self.sync_server.add_site(project_name, link_repre_id, + site_name, + force=False) except SiteAlreadyPresentError: # do not add/reset working site for references self.log.debug("Site present", exc_info=True) @@ -92,24 +83,20 @@ class AddSyncSite(load.LoaderPlugin): """No real file loading""" return "" - def _add_hero_representation_ids(self, project_name, repre_id, - repre_doc=None): + def _add_hero_representation_ids(self, project_name, repre_id): """Find hero version if exists for repre_id. Args: project_name (str) repre_id (ObjectId) - repre_doc (dict): repre document for 'repre_id', might be collected - previously Returns: (list): at least [repre_id] if no hero version found """ representation_ids = [repre_id] - if not repre_doc: - repre_doc = get_representation_by_id( - project_name, repre_id, fields=["_id", "parent", "name"] - ) + repre_doc = get_representation_by_id( + project_name, repre_id, fields=["_id", "parent", "name"] + ) version_doc = get_version_by_id(project_name, repre_doc["parent"]) if version_doc["type"] != "hero_version": From 4edf16cad6e9b3cab480c57068783af57d0ccd69 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 17:26:48 +0100 Subject: [PATCH 261/356] Fix - updated usage of correct variables --- openpype/plugins/load/add_site.py | 8 +++++--- openpype/plugins/load/remove_site.py | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 98a390fb30..38c27f9079 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -51,13 +51,13 @@ class AddSyncSite(load.LoaderPlugin): data (dict): expects {"site_name": SITE_NAME_TO_ADD} """ # self.log wont propagate - print("Adding {} to representation: {}".format( - data["site_name"], data["_id"])) project_name = context["project"]["name"] repre_doc = context["representation"] family = repre_doc["context"]["family"] - repre_id = [repre_doc["_id"]] + repre_id = repre_doc["_id"] site_name = data["site_name"] + print("Adding {} to representation: {}".format( + data["site_name"], repre_id)) self.sync_server.add_site(project_name, repre_id, site_name, force=True) @@ -70,6 +70,8 @@ class AddSyncSite(load.LoaderPlugin): ) for link_repre_id in links: try: + print("Adding {} to linked representation: {}".format( + data["site_name"], link_repre_id)) self.sync_server.add_site(project_name, link_repre_id, site_name, force=False) diff --git a/openpype/plugins/load/remove_site.py b/openpype/plugins/load/remove_site.py index c5f442b2f5..bea8b1b346 100644 --- a/openpype/plugins/load/remove_site.py +++ b/openpype/plugins/load/remove_site.py @@ -3,7 +3,10 @@ from openpype.pipeline import load class RemoveSyncSite(load.LoaderPlugin): - """Remove sync site and its files on representation""" + """Remove sync site and its files on representation. + + Removes files only on local site! + """ representations = ["*"] families = ["*"] @@ -24,13 +27,18 @@ class RemoveSyncSite(load.LoaderPlugin): return self._sync_server def load(self, context, name=None, namespace=None, data=None): - self.log.info("Removing {} on representation: {}".format( - data["site_name"], data["_id"])) - self.sync_server.remove_site(data["project_name"], - data["_id"], - data["site_name"], + project_name = context["project"]["name"] + repre_doc = context["representation"] + repre_id = repre_doc["_id"] + site_name = data["site_name"] + + print("Removing {} on representation: {}".format(site_name, repre_id)) + + self.sync_server.remove_site(project_name, + repre_id, + site_name, True) - self.log.debug("Site added.") + self.log.debug("Site removed.") def filepath_from_context(self, context): """No real file loading""" From f346ace6a12557879cb753c972cead6ea1f4e510 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 17:36:51 +0100 Subject: [PATCH 262/356] OP-4653 - removed obsolete import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/photoshop/plugins/create/create_image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index 198c1586dc..cdea82cb05 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -5,7 +5,6 @@ from openpype.lib import BoolDef from openpype.pipeline import ( Creator, CreatedInstance, - legacy_io, CreatorError ) from openpype.lib import prepare_template_data From b1ef1b751f5bdf3df1274b524ac55f34f4b1c51d Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 19:11:20 +0100 Subject: [PATCH 263/356] Abstract get_representation_path function and use it on shotgrid to fix remote errors with data instances not having 'published_path' --- .../publish/integrate_ftrack_instances.py | 52 ++----------------- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 48 ++++++++++++----- .../plugins/publish/integrate_slack_api.py | 11 ++-- openpype/plugins/publish/integrate.py | 46 ++++++++++++++++ 5 files changed, 92 insertions(+), 69 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 2d06e2ab02..c3baecec67 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -10,6 +10,7 @@ from openpype.lib.transcoding import ( ) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.transcoding import VIDEO_EXTENSIONS +from openpype.plugins.publish.integrate import get_representation_path class IntegrateFtrackInstance(pyblish.api.InstancePlugin): @@ -153,7 +154,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if not review_representations or has_movie_review: for repre in thumbnail_representations: - repre_path = self._get_repre_path(instance, repre, False) + repre_path = get_representation_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -210,7 +211,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "from {}".format(repre)) continue - repre_path = self._get_repre_path(instance, repre, False) + repre_path = get_representation_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -324,7 +325,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Add others representations as component for repre in other_representations: - published_path = self._get_repre_path(instance, repre, True) + published_path = get_representation_path(instance, repre, True) if not published_path: continue # Create copy of base comp item and append it @@ -364,51 +365,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): def _collect_additional_metadata(self, streams): pass - def _get_repre_path(self, instance, repre, only_published): - """Get representation path that can be used for integration. - - When 'only_published' is set to true the validation of path is not - relevant. In that case we just need what is set in 'published_path' - as "reference". The reference is not used to get or upload the file but - for reference where the file was published. - - Args: - instance (pyblish.Instance): Processed instance object. Used - for source of staging dir if representation does not have - filled it. - repre (dict): Representation on instance which could be and - could not be integrated with main integrator. - only_published (bool): Care only about published paths and - ignore if filepath is not existing anymore. - - Returns: - str: Path to representation file. - None: Path is not filled or does not exists. - """ - - published_path = repre.get("published_path") - if published_path: - published_path = os.path.normpath(published_path) - if os.path.exists(published_path): - return published_path - - if only_published: - return published_path - - comp_files = repre["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] - else: - filename = comp_files - - staging_dir = repre.get("stagingDir") - if not staging_dir: - staging_dir = instance.data["stagingDir"] - src_path = os.path.normpath(os.path.join(staging_dir, filename)) - if os.path.exists(src_path): - return src_path - return None - def _get_asset_version_status_name(self, instance): if not self.asset_versions_status_profiles: return None diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index cfd2d10fd9..ee6ece2e67 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -1,6 +1,8 @@ import os import pyblish.api +from openpype.plugins.publish.integrate import get_representation_path + class IntegrateShotgridPublish(pyblish.api.InstancePlugin): """ @@ -22,7 +24,7 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") + local_path = get_representation_path(instance, representation, False) code = os.path.basename(local_path) if representation.get("tags", []): diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index a1b7140e22..60ad1ff91d 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -1,6 +1,7 @@ -import os import pyblish.api +from openpype.plugins.publish.integrate import get_representation_path + class IntegrateShotgridVersion(pyblish.api.InstancePlugin): """Integrate Shotgrid Version""" @@ -17,15 +18,37 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): # TODO: Use path template solver to build version code from settings anatomy = instance.data.get("anatomyData", {}) - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) + ### Starts Alkemy-X Override ### + # code = "_".join( + # [ + # anatomy["project"]["code"], + # anatomy["parent"], + # anatomy["asset"], + # anatomy["task"]["name"], + # "v{:03}".format(int(anatomy["version"])), + # ] + # ) + # Initial editorial Shotgrid versions don't need task in name + if anatomy["app"] == "hiero": + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + else: + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) + ### Ends Alkemy-X Override ### version = self._find_existing_version(code, context) @@ -41,8 +64,9 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - local_path = representation.get("published_path") - code = os.path.basename(local_path) + # Get representation path from published_path or create it from stagingDir if not existent + local_path = get_representation_path(instance, representation, False) + self.log.info("Local path: %s", local_path) if "shotgridreview" in representation.get("tags", []): diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 612031efac..ac918381c0 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -9,6 +9,7 @@ import time from openpype.client import OpenPypeMongoConnection from openpype.lib.plugin_tools import prepare_template_data +from openpype.plugins.publish.integrate import get_representation_path class IntegrateSlackAPI(pyblish.api.InstancePlugin): @@ -167,10 +168,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = ( - repre.get("published_path") or - os.path.join(repre["stagingDir"], repre["files"]) - ) + repre_thumbnail_path = get_representation_path(instance, repre, False) if os.path.exists(repre_thumbnail_path): thumbnail_path = repre_thumbnail_path break @@ -184,10 +182,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = ( - repre.get("published_path") or - os.path.join(repre["stagingDir"], repre["files"]) - ) + repre_review_path = get_representation_path(instance, repre, False) if os.path.exists(repre_review_path): review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7b73943c37..854cf8b9ec 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -53,6 +53,52 @@ def get_frame_padded(frame, padding): return "{frame:0{padding}d}".format(padding=padding, frame=frame) +def get_representation_path(instance, repre, only_published): + """Get representation path that can be used for integration. + + When 'only_published' is set to true the validation of path is not + relevant. In that case we just need what is set in 'published_path' + as "reference". The reference is not used to get or upload the file but + for reference where the file was published. + + Args: + instance (pyblish.Instance): Processed instance object. Used + for source of staging dir if representation does not have + filled it. + repre (dict): Representation on instance which could be and + could not be integrated with main integrator. + only_published (bool): Care only about published paths and + ignore if filepath is not existing anymore. + + Returns: + str: Path to representation file. + None: Path is not filled or does not exists. + """ + + published_path = repre.get("published_path") + if published_path: + published_path = os.path.normpath(published_path) + if os.path.exists(published_path): + return published_path + + if only_published: + return published_path + + comp_files = repre["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + staging_dir = repre.get("stagingDir") + if not staging_dir: + staging_dir = instance.data["stagingDir"] + src_path = os.path.normpath(os.path.join(staging_dir, filename)) + if os.path.exists(src_path): + return src_path + return None + + class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. From c13416b68570365b9ce3b247979f79f721cc3031 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 19:39:47 +0100 Subject: [PATCH 264/356] Remove unintended code block and fix Hound lint warnings for <79 chars width --- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 46 +++++-------------- .../plugins/publish/integrate_slack_api.py | 8 +++- 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index ee6ece2e67..7789a47074 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -24,7 +24,9 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = get_representation_path(instance, representation, False) + local_path = get_representation_path( + instance, representation, False + ) code = os.path.basename(local_path) if representation.get("tags", []): diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index 60ad1ff91d..94fc4ae9e8 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -18,37 +18,15 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): # TODO: Use path template solver to build version code from settings anatomy = instance.data.get("anatomyData", {}) - ### Starts Alkemy-X Override ### - # code = "_".join( - # [ - # anatomy["project"]["code"], - # anatomy["parent"], - # anatomy["asset"], - # anatomy["task"]["name"], - # "v{:03}".format(int(anatomy["version"])), - # ] - # ) - # Initial editorial Shotgrid versions don't need task in name - if anatomy["app"] == "hiero": - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - else: - code = "_".join( - [ - anatomy["project"]["code"], - anatomy["parent"], - anatomy["asset"], - anatomy["task"]["name"], - "v{:03}".format(int(anatomy["version"])), - ] - ) - ### Ends Alkemy-X Override ### + code = "_".join( + [ + anatomy["project"]["code"], + anatomy["parent"], + anatomy["asset"], + anatomy["task"]["name"], + "v{:03}".format(int(anatomy["version"])), + ] + ) version = self._find_existing_version(code, context) @@ -64,9 +42,9 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - # Get representation path from published_path or create it from stagingDir if not existent - local_path = get_representation_path(instance, representation, False) - self.log.info("Local path: %s", local_path) + local_path = get_representation_path( + instance, representation, False + ) if "shotgridreview" in representation.get("tags", []): diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index ac918381c0..d486b2179a 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -168,7 +168,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = get_representation_path(instance, repre, False) + repre_thumbnail_path = get_representation_path( + instance, repre, False + ) if os.path.exists(repre_thumbnail_path): thumbnail_path = repre_thumbnail_path break @@ -182,7 +184,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = get_representation_path(instance, repre, False) + repre_review_path = get_representation_path( + instance, repre, False + ) if os.path.exists(repre_review_path): review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists From 27150e4abb148e7dfc4eb673ea4affa9c080f55f Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 8 Feb 2023 20:23:26 +0100 Subject: [PATCH 265/356] Address feedback on PR and move function to pipeline.publish.lib --- .../publish/integrate_ftrack_instances.py | 8 ++-- .../publish/integrate_shotgrid_publish.py | 4 +- .../publish/integrate_shotgrid_version.py | 4 +- .../plugins/publish/integrate_slack_api.py | 6 +-- openpype/pipeline/publish/__init__.py | 2 + openpype/pipeline/publish/lib.py | 46 +++++++++++++++++++ openpype/plugins/publish/integrate.py | 46 ------------------- 7 files changed, 59 insertions(+), 57 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index c3baecec67..d6cb3daf0d 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -3,6 +3,7 @@ import json import copy import pyblish.api +from openpype.pipeline.publish import get_publish_repre_path from openpype.lib.openpype_version import get_openpype_version from openpype.lib.transcoding import ( get_ffprobe_streams, @@ -10,7 +11,6 @@ from openpype.lib.transcoding import ( ) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.transcoding import VIDEO_EXTENSIONS -from openpype.plugins.publish.integrate import get_representation_path class IntegrateFtrackInstance(pyblish.api.InstancePlugin): @@ -154,7 +154,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if not review_representations or has_movie_review: for repre in thumbnail_representations: - repre_path = get_representation_path(instance, repre, False) + repre_path = get_publish_repre_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -211,7 +211,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "from {}".format(repre)) continue - repre_path = get_representation_path(instance, repre, False) + repre_path = get_publish_repre_path(instance, repre, False) if not repre_path: self.log.warning( "Published path is not set and source was removed." @@ -325,7 +325,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Add others representations as component for repre in other_representations: - published_path = get_representation_path(instance, repre, True) + published_path = get_publish_repre_path(instance, repre, True) if not published_path: continue # Create copy of base comp item and append it diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py index 7789a47074..fc15d5515f 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_publish.py @@ -1,7 +1,7 @@ import os import pyblish.api -from openpype.plugins.publish.integrate import get_representation_path +from openpype.pipeline.publish import get_publish_repre_path class IntegrateShotgridPublish(pyblish.api.InstancePlugin): @@ -24,7 +24,7 @@ class IntegrateShotgridPublish(pyblish.api.InstancePlugin): for representation in instance.data.get("representations", []): - local_path = get_representation_path( + local_path = get_publish_repre_path( instance, representation, False ) code = os.path.basename(local_path) diff --git a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py index 94fc4ae9e8..adfdca718c 100644 --- a/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py +++ b/openpype/modules/shotgrid/plugins/publish/integrate_shotgrid_version.py @@ -1,6 +1,6 @@ import pyblish.api -from openpype.plugins.publish.integrate import get_representation_path +from openpype.pipeline.publish import get_publish_repre_path class IntegrateShotgridVersion(pyblish.api.InstancePlugin): @@ -42,7 +42,7 @@ class IntegrateShotgridVersion(pyblish.api.InstancePlugin): data_to_update["sg_status_list"] = status for representation in instance.data.get("representations", []): - local_path = get_representation_path( + local_path = get_publish_repre_path( instance, representation, False ) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d486b2179a..4e2557ccc7 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -8,8 +8,8 @@ from abc import ABCMeta, abstractmethod import time from openpype.client import OpenPypeMongoConnection +from openpype.pipeline.publish import get_publish_repre_path from openpype.lib.plugin_tools import prepare_template_data -from openpype.plugins.publish.integrate import get_representation_path class IntegrateSlackAPI(pyblish.api.InstancePlugin): @@ -168,7 +168,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): thumbnail_path = None for repre in instance.data.get("representations", []): if repre.get('thumbnail') or "thumbnail" in repre.get('tags', []): - repre_thumbnail_path = get_representation_path( + repre_thumbnail_path = get_publish_repre_path( instance, repre, False ) if os.path.exists(repre_thumbnail_path): @@ -184,7 +184,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (repre.get("review") or "review" in tags or "burnin" in tags): - repre_review_path = get_representation_path( + repre_review_path = get_publish_repre_path( instance, repre, False ) if os.path.exists(repre_review_path): diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index dc6fc0f97a..5be973ad86 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -36,6 +36,7 @@ from .lib import ( filter_instances_for_context_plugin, context_plugin_should_run, get_instance_staging_dir, + get_publish_repre_path, ) from .abstract_expected_files import ExpectedFiles @@ -79,6 +80,7 @@ __all__ = ( "filter_instances_for_context_plugin", "context_plugin_should_run", "get_instance_staging_dir", + "get_publish_repre_path", "ExpectedFiles", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c76671fa39..e206c4552c 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -632,3 +632,49 @@ def get_instance_staging_dir(instance): instance.data["stagingDir"] = staging_dir return staging_dir + + +def get_publish_repre_path(instance, repre, only_published): + """Get representation path that can be used for integration. + + When 'only_published' is set to true the validation of path is not + relevant. In that case we just need what is set in 'published_path' + as "reference". The reference is not used to get or upload the file but + for reference where the file was published. + + Args: + instance (pyblish.Instance): Processed instance object. Used + for source of staging dir if representation does not have + filled it. + repre (dict): Representation on instance which could be and + could not be integrated with main integrator. + only_published (bool): Care only about published paths and + ignore if filepath is not existing anymore. + + Returns: + str: Path to representation file. + None: Path is not filled or does not exists. + """ + + published_path = repre.get("published_path") + if published_path: + published_path = os.path.normpath(published_path) + if os.path.exists(published_path): + return published_path + + if only_published: + return published_path + + comp_files = repre["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + staging_dir = repre.get("stagingDir") + if not staging_dir: + staging_dir = get_instance_staging_dir(instance) + src_path = os.path.normpath(os.path.join(staging_dir, filename)) + if os.path.exists(src_path): + return src_path + return None diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 854cf8b9ec..7b73943c37 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -53,52 +53,6 @@ def get_frame_padded(frame, padding): return "{frame:0{padding}d}".format(padding=padding, frame=frame) -def get_representation_path(instance, repre, only_published): - """Get representation path that can be used for integration. - - When 'only_published' is set to true the validation of path is not - relevant. In that case we just need what is set in 'published_path' - as "reference". The reference is not used to get or upload the file but - for reference where the file was published. - - Args: - instance (pyblish.Instance): Processed instance object. Used - for source of staging dir if representation does not have - filled it. - repre (dict): Representation on instance which could be and - could not be integrated with main integrator. - only_published (bool): Care only about published paths and - ignore if filepath is not existing anymore. - - Returns: - str: Path to representation file. - None: Path is not filled or does not exists. - """ - - published_path = repre.get("published_path") - if published_path: - published_path = os.path.normpath(published_path) - if os.path.exists(published_path): - return published_path - - if only_published: - return published_path - - comp_files = repre["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] - else: - filename = comp_files - - staging_dir = repre.get("stagingDir") - if not staging_dir: - staging_dir = instance.data["stagingDir"] - src_path = os.path.normpath(os.path.join(staging_dir, filename)) - if os.path.exists(src_path): - return src_path - return None - - class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. From 15d7a7589fa119010510232e3ae243f0e6834383 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:04:06 +0100 Subject: [PATCH 266/356] fix context collection from create context --- .../publish/collect_from_create_context.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index d3398c885e..5fcf8feb56 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -32,7 +32,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): thumbnail_paths_by_instance_id.get(None) ) - project_name = create_context.project_name + project_name = create_context.get_current_project_name() if project_name: context.data["projectName"] = project_name @@ -53,11 +53,15 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): context.data.update(create_context.context_data_to_store()) context.data["newPublishing"] = True # Update context data - for key in ("AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK"): - value = create_context.dbcon.Session.get(key) - if value is not None: - legacy_io.Session[key] = value - os.environ[key] = value + asset_name = create_context.get_current_asset_name() + task_name = create_context.get_current_task_name() + for key, value in ( + ("AVALON_PROJECT", project_name), + ("AVALON_ASSET", asset_name), + ("AVALON_TASK", task_name) + ): + legacy_io.Session[key] = value + os.environ[key] = value def create_instance( self, From b148dec04843137622960b50c484d94ad9b0e82b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:58:17 +0100 Subject: [PATCH 267/356] Added helper method to return unified information on create error --- openpype/pipeline/create/context.py | 70 +++++++++++++++++------------ 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 190d542724..dfe60d438b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1538,6 +1538,44 @@ class CreateContext: pre_create_data ) + def _create_with_unified_error( + self, identifier, creator, *args, **kwargs + ): + error_message = "Failed to run Creator with identifier \"{}\". {}" + + label = None + add_traceback = False + result = None + fail_info = None + success = False + + try: + # Try to get creator and his label + if creator is None: + creator = self._get_creator_in_create(identifier) + label = getattr(creator, "label", label) + + # Run create + result = creator.create(*args, **kwargs) + success = True + + except CreatorError: + exc_info = sys.exc_info() + self.log.warning(error_message.format(identifier, exc_info[1])) + + except: + add_traceback = True + exc_info = sys.exc_info() + self.log.warning( + error_message.format(identifier, ""), + exc_info=True + ) + + if not success: + fail_info = prepare_failed_creator_operation_info( + identifier, label, exc_info, add_traceback + ) + return result, fail_info def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. @@ -1663,37 +1701,11 @@ class CreateContext: Reset instances if any autocreator executed properly. """ - error_message = "Failed to run AutoCreator with identifier \"{}\". {}" failed_info = [] for identifier, creator in self.autocreators.items(): - label = creator.label - failed = False - add_traceback = False - try: - creator.create() - - except CreatorError: - failed = True - exc_info = sys.exc_info() - self.log.warning(error_message.format(identifier, exc_info[1])) - - # Use bare except because some hosts raise their exceptions that - # do not inherit from python's `BaseException` - except: - failed = True - add_traceback = True - exc_info = sys.exc_info() - self.log.warning( - error_message.format(identifier, ""), - exc_info=True - ) - - if failed: - failed_info.append( - prepare_failed_creator_operation_info( - identifier, label, exc_info, add_traceback - ) - ) + _, fail_info = self._create_with_unified_error(identifier, creator) + if fail_info is not None: + failed_info.append(fail_info) if failed_info: raise CreatorsCreateFailed(failed_info) From fac10d26337157bc253a1af90ae0a1040ff49533 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 12:58:51 +0100 Subject: [PATCH 268/356] added public method 'create_with_unified_error' used in publisher --- openpype/pipeline/create/context.py | 25 +++++++++++++++++++++++++ openpype/tools/publisher/control.py | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index dfe60d438b..2a92d21225 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1576,6 +1576,31 @@ class CreateContext: identifier, label, exc_info, add_traceback ) return result, fail_info + + def create_with_unified_error(self, identifier, *args, **kwargs): + """Trigger create but raise only one error if anything fails. + + Added to raise unified exception. Capture any possible issues and + reraise it with unified information. + + Args: + identifier (str): Identifier of creator. + *args (Tuple[Any]): Arguments for create method. + **kwargs (Dict[Any, Any]): Keyword argument for create method. + + Raises: + CreatorsCreateFailed: When creation fails due to any possible + reason. If anything goes wrong this is only possible exception + the method should raise. + """ + + result, fail_info = self._create_with_unified_error( + identifier, None, *args, **kwargs + ) + if fail_info is not None: + raise CreatorsCreateFailed([fail_info]) + return result + def creator_removed_instance(self, instance): """When creator removes instance context should be acknowledged. diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 670c22a43e..11215b5ff8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2017,9 +2017,10 @@ class PublisherController(BasePublisherController): success = True try: - self._create_context.raw_create( + self._create_context.create_with_unified_error( creator_identifier, subset_name, instance_data, options ) + except CreatorsOperationFailed as exc: success = False self._emit_event( From cb84cf769eea9d5a018ef67dd6e4cb0d6d7276b8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 13:00:28 +0100 Subject: [PATCH 269/356] 'create' method is not triggering 'raw_create' --- openpype/pipeline/create/context.py | 34 ++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2a92d21225..3287141970 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1415,6 +1415,30 @@ class CreateContext: with self.bulk_instances_collection(): self._bulk_instances_to_process.append(instance) + def _get_creator_in_create(self, identifier): + """Creator by identifier with unified error. + + Helper method to get creator by identifier with same error when creator + is not available. + + Args: + identifier (str): Identifier of creator plugin. + + Returns: + BaseCreator: Creator found by identifier. + + Raises: + CreatorError: When identifier is not known. + """ + + creator = self.creators.get(identifier) + # Fake CreatorError (Could be maybe specific exception?) + if creator is None: + raise CreatorError( + "Creator {} was not found".format(identifier) + ) + return creator + def raw_create(self, identifier, *args, **kwargs): """Wrapper for creators to trigger 'create' method. @@ -1497,14 +1521,9 @@ class CreateContext: Raises: CreatorError: If creator was not found or asset is empty. - CreatorsCreateFailed: When creation fails. """ - creator = self.creators.get(creator_identifier) - if creator is None: - raise CreatorError( - "Creator {} was not found".format(creator_identifier) - ) + creator = self._get_creator_in_create(creator_identifier) project_name = self.project_name if asset_doc is None: @@ -1531,8 +1550,7 @@ class CreateContext: "task": task_name, "variant": variant } - return self.raw_create( - creator_identifier, + return creator.create( subset_name, instance_data, pre_create_data From 0cb78a10e6cd7b0c7307c70be1827e2e8c1d1f2e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 13:00:49 +0100 Subject: [PATCH 270/356] removed unused 'raw_create' method --- openpype/pipeline/create/context.py | 53 ----------------------------- 1 file changed, 53 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 3287141970..078c50acc2 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1439,59 +1439,6 @@ class CreateContext: ) return creator - def raw_create(self, identifier, *args, **kwargs): - """Wrapper for creators to trigger 'create' method. - - Different types of creators may expect different arguments thus the - hints for args are blind. - - Args: - identifier (str): Creator's identifier. - *args (Tuple[Any]): Arguments for create method. - **kwargs (Dict[Any, Any]): Keyword argument for create method. - - Raises: - CreatorsCreateFailed: When creation fails. - """ - - error_message = "Failed to run Creator with identifier \"{}\". {}" - creator = self.creators.get(identifier) - label = getattr(creator, "label", None) - failed = False - add_traceback = False - exc_info = None - result = None - try: - # Fake CreatorError (Could be maybe specific exception?) - if creator is None: - raise CreatorError( - "Creator {} was not found".format(identifier) - ) - - result = creator.create(*args, **kwargs) - - except CreatorError: - failed = True - exc_info = sys.exc_info() - self.log.warning(error_message.format(identifier, exc_info[1])) - - except: - failed = True - add_traceback = True - exc_info = sys.exc_info() - self.log.warning( - error_message.format(identifier, ""), - exc_info=True - ) - - if failed: - raise CreatorsCreateFailed([ - prepare_failed_creator_operation_info( - identifier, label, exc_info, add_traceback - ) - ]) - return result - def create( self, creator_identifier, From a3c9f792c81ac6276594f73d6dc5152bce0b12cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 14:29:20 +0100 Subject: [PATCH 271/356] refactor tempdir creator function wip --- openpype/pipeline/publish/lib.py | 67 ++++++-------------------------- openpype/pipeline/tempdir.py | 62 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 openpype/pipeline/tempdir.py diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index b3d273781e..380f0df91a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -12,13 +12,15 @@ import pyblish.api from openpype.lib import ( Logger, - filter_profiles, - StringTemplate + filter_profiles ) from openpype.settings import ( get_project_settings, get_system_settings, ) +from openpype.pipeline import ( + tempdir +) from .contants import ( DEFAULT_PUBLISH_TEMPLATE, @@ -645,24 +647,12 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - openpype_temp_dir = os.getenv("OPENPYPE_TMPDIR") - custom_temp_dir = None - if openpype_temp_dir: - if "{" in openpype_temp_dir: - # path is anatomy template - custom_temp_dir = _format_staging_dir( - instance, openpype_temp_dir - ) - else: - # path is absolute - custom_temp_dir = openpype_temp_dir - - if not os.path.exists(custom_temp_dir): - try: - # create it if it doesnt exists - os.makedirs(custom_temp_dir) - except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) + anatomy_data = instance.data.get("anatomy_data") + project_name = + # get customized tempdir path from `OPENPYPE_TEMPDIR` env var + custom_temp_dir = tempdir.create_custom_tempdir( + instance.data["anatomy_data"]["project"]["name"] + ) if custom_temp_dir: staging_dir = os.path.normpath( @@ -677,39 +667,4 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - return staging_dir - - -def _format_staging_dir(instance, openpype_temp_dir): - """ Formating template - - Template path formatting is supporting: - - optional key formating - - available keys: - - root[work | ] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - - Args: - instance (pyblish.Instance): instance object - openpype_temp_dir (str): path string - - Returns: - str: formated path - """ - anatomy = instance.context.data["anatomy"] - # get anatomy formating data - # so template formating is supported - anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) - anatomy_data["root"] = anatomy.roots - - result = StringTemplate.format_template( - openpype_temp_dir, anatomy_data).normalized() - - # create the dir in case it doesnt exists - os.makedirs(os.path.dirname(result)) - return result + return staging_dir \ No newline at end of file diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py new file mode 100644 index 0000000000..c73fce2e9a --- /dev/null +++ b/openpype/pipeline/tempdir.py @@ -0,0 +1,62 @@ +import os + +from openpype.lib import ( + Anatomy, + StringTemplate +) + +def create_custom_tempdir(project_name, anatomy=None, formating_data=None): + """ Create custom tempdir + + Template path formatting is supporting: + - optional key formating + - available keys: + - root[work | ] + - project[name | code] + + Args: + instance (pyblish.Instance): instance object + openpype_temp_dir (str): path string + + Returns: + str: formated path + """ + openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") + if not openpype_tempdir: + return + + custom_tempdir = None + if "{" in openpype_tempdir: + if anatomy is None: + anatomy = Anatomy(project_name) + # create base formate data + data = { + "root": anatomy.roots + } + if formating_data is None: + # We still don't have `project_code` on Anatomy... + project_doc = anatomy.get_project_doc_from_cache(project_name) + data["project"] = { + "name": project_name, + "code": project_doc["data"]["code"], + } + else: + data["project"] = formating_data["project"] + + # path is anatomy template + custom_tempdir = StringTemplate.format_template( + openpype_tempdir, data).normalized() + + else: + # path is absolute + custom_tempdir = openpype_tempdir + + # create he dir path if it doesnt exists + if not os.path.exists(custom_tempdir): + try: + # create it if it doesnt exists + os.makedirs(custom_tempdir) + except IOError as error: + raise IOError("Path couldn't be created: {}".format(error)) + + return custom_tempdir From 304d7584042454193be5b1c85f0dc87e06c1d4c3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 14:40:40 +0100 Subject: [PATCH 272/356] renaming module for temporarydir, fixing docstring --- openpype/pipeline/publish/lib.py | 19 ++++++++++--------- .../pipeline/{tempdir.py => temporarydir.py} | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) rename openpype/pipeline/{tempdir.py => temporarydir.py} (82%) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 380f0df91a..c6c8b71b24 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -19,7 +19,7 @@ from openpype.settings import ( get_system_settings, ) from openpype.pipeline import ( - tempdir + temporarydir ) from .contants import ( @@ -626,11 +626,6 @@ def get_instance_staging_dir(instance): Available anatomy formatting keys: - root[work | ] - project[name | code] - - asset - - hierarchy - - task - - username - - app Note: Staging dir does not have to be necessarily in tempdir so be carefull @@ -648,10 +643,16 @@ def get_instance_staging_dir(instance): return staging_dir anatomy_data = instance.data.get("anatomy_data") - project_name = + anatomy = instance.data.get("anatomy") + + if anatomy_data: + project_name = anatomy_data["project"]["name"] + else: + project_name = os.getenv("AVALON_PROJECT") + # get customized tempdir path from `OPENPYPE_TEMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir( - instance.data["anatomy_data"]["project"]["name"] + custom_temp_dir = temporarydir.create_custom_tempdir( + project_name, anatomy=anatomy, formating_data=anatomy_data ) if custom_temp_dir: diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/temporarydir.py similarity index 82% rename from openpype/pipeline/tempdir.py rename to openpype/pipeline/temporarydir.py index c73fce2e9a..31586d82c8 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/temporarydir.py @@ -1,9 +1,11 @@ -import os +""" +Temporary folder operations +""" + +import os +from openpype.lib import StringTemplate +from openpype.pipeline import Anatomy -from openpype.lib import ( - Anatomy, - StringTemplate -) def create_custom_tempdir(project_name, anatomy=None, formating_data=None): """ Create custom tempdir @@ -15,11 +17,12 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): - project[name | code] Args: - instance (pyblish.Instance): instance object - openpype_temp_dir (str): path string + project_name (str): name of project + anatomy (openpype.pipeline.Anatomy): Anatomy object + formating_data (dict): formating data used for filling template. Returns: - str: formated path + bool | str: formated path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 171b9bc3dbc079d08248cc2cafa40342b9bcd762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Thu, 9 Feb 2023 05:49:02 -0800 Subject: [PATCH 273/356] Make only_published argument optional Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index e206c4552c..92c43c99e8 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -634,7 +634,7 @@ def get_instance_staging_dir(instance): return staging_dir -def get_publish_repre_path(instance, repre, only_published): +def get_publish_repre_path(instance, repre, only_published=False): """Get representation path that can be used for integration. When 'only_published' is set to true the validation of path is not From 5de967b6447fb2b7f7dc84ad84704857afa35af8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 15:07:40 +0100 Subject: [PATCH 274/356] updated documentation --- openpype/pipeline/publish/lib.py | 2 +- website/docs/admin_environment.md | 30 +++++++++++++++++++++++++++ website/docs/admin_settings_system.md | 29 ++++---------------------- website/sidebars.js | 1 + 4 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 website/docs/admin_environment.md diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c6c8b71b24..aaa2dd444a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -668,4 +668,4 @@ def get_instance_staging_dir(instance): ) instance.data['stagingDir'] = staging_dir - return staging_dir \ No newline at end of file + return staging_dir diff --git a/website/docs/admin_environment.md b/website/docs/admin_environment.md new file mode 100644 index 0000000000..2cc558b530 --- /dev/null +++ b/website/docs/admin_environment.md @@ -0,0 +1,30 @@ +--- +id: admin_environment +title: Environment +sidebar_label: Environment +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## OPENPYPE_TMPDIR: + - Custom staging dir directory + - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` + - supported formating keys: + - root[work] + - project[name | code] + +## OPENPYPE_DEBUG + - setting logger to debug mode + - example value: "1" (to activate) + +## OPENPYPE_LOG_LEVEL + - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) + - example value: "10" + +## OPENPYPE_MONGO +- If set it takes precedence over the one set in keyring +- 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 diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 39b58e6f81..6a17844755 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -14,39 +14,18 @@ Settings applicable to the full studio. ![general_settings](assets/settings/settings_system_general.png) ### Studio Name - - Full name of the studio (can be used as variable on some places) +Full name of the studio (can be used as variable on some places) ### Studio Code - - Studio acronym or a short code (can be used as variable on some places) +Studio acronym or a short code (can be used as variable on some places) ### Admin Password - - After setting admin password, normal user won't have access to OpenPype settings +After setting admin password, normal user won't have access to OpenPype settings and Project Manager GUI. Please keep in mind that this is a studio wide password and it is meant purely as a simple barrier to prevent artists from accidental setting changes. ### Environment - - Globally applied environment variables that will be appended to any OpenPype process in the studio. - - OpenPype is using some keys to configure some tools. Here are some: - -#### OPENPYPE_TMPDIR: - - Custom staging dir directory - - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` - - supported formating keys: - - root[work] - - project[name | code] - - asset - - hierarchy - - task - - username - - app - -#### OPENPYPE_DEBUG - - setting logger to debug mode - - example value: "1" (to activate) - -#### OPENPYPE_LOG_LEVEL - - stringified numeric value of log level. [Here for more info](https://docs.python.org/3/library/logging.html#logging-levels) - - example value: "10" +Globally applied environment variables that will be appended to any OpenPype process in the studio. ### Disk mapping - Platform dependent configuration for mapping of virtual disk(s) on an artist's OpenPype machines before OP starts up. diff --git a/website/sidebars.js b/website/sidebars.js index cc945a019e..ed4ff45db8 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -85,6 +85,7 @@ module.exports = { type: "category", label: "Configuration", items: [ + "admin_environment", "admin_settings", "admin_settings_system", "admin_settings_project_anatomy", From d774eab62603a8356ed55b6c74255332b6c675ee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 15:08:18 +0100 Subject: [PATCH 275/356] end line added --- website/docs/admin_settings_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 6a17844755..c39cac61f5 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -176,4 +176,4 @@ In the image before you can see that we set most of the environment variables in In this example MTOA will automatically will the `MAYA_VERSION`(which is set by Maya Application environment) and `MTOA_VERSION` into the `MTOA` variable. We then use the `MTOA` to set all the other variables needed for it to function within Maya. ![tools](assets/settings/tools_01.png) -All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. \ No newline at end of file +All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. From 3ac0a1cb6f8c96071f8f5e992bbb0bb868b84e3f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 9 Feb 2023 15:54:20 +0100 Subject: [PATCH 276/356] OP-4513 - fix Openpype plugin for MacOS --- .../repository/custom/plugins/OpenPype/OpenPype.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py b/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py index 6b0f69d98f..ab4a3d5e9b 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPype/OpenPype.py @@ -73,7 +73,7 @@ class OpenPypeDeadlinePlugin(DeadlinePlugin): """ # fix path for application bundle on macos if platform.system().lower() == "darwin": - path = os.path.join(path, "Contents", "MacOS", "lib", "Python") + path = os.path.join(path, "MacOS") version_file = os.path.join(path, "openpype", "version.py") if not os.path.isfile(version_file): @@ -107,8 +107,11 @@ class OpenPypeDeadlinePlugin(DeadlinePlugin): "Scanning for compatible requested " f"version {requested_version}")) dir_list = self.GetConfigEntry("OpenPypeInstallationDirs") + # clean '\ ' for MacOS pasting + if platform.system().lower() == "darwin": + dir_list = dir_list.replace("\\ ", " ") install_dir = DirectoryUtils.SearchDirectoryList(dir_list) - if dir: + if install_dir: sub_dirs = [ f.path for f in os.scandir(install_dir) if f.is_dir() @@ -120,6 +123,9 @@ class OpenPypeDeadlinePlugin(DeadlinePlugin): openpype_versions.append((version, subdir)) exe_list = self.GetConfigEntry("OpenPypeExecutable") + # clean '\ ' for MacOS pasting + if platform.system().lower() == "darwin": + exe_list = exe_list.replace("\\ ", " ") exe = FileUtils.SearchFileList(exe_list) if openpype_versions: # if looking for requested compatible version, @@ -161,7 +167,9 @@ class OpenPypeDeadlinePlugin(DeadlinePlugin): os.path.join( compatible_versions[-1][1], "openpype_console.exe"), os.path.join( - compatible_versions[-1][1], "openpype_console") + compatible_versions[-1][1], "openpype_console"), + os.path.join( + compatible_versions[-1][1], "MacOS", "openpype_console") ] exe = FileUtils.SearchFileList(";".join(exe_list)) From b16af10670181630de0fb1bc700877f46dbfa18d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 9 Feb 2023 16:27:34 +0100 Subject: [PATCH 277/356] Removed obsolete method and imports Fixed in get_linked_representation_id instead --- openpype/plugins/load/add_site.py | 38 ------------------------------- 1 file changed, 38 deletions(-) diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 38c27f9079..e31f746f51 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -2,12 +2,6 @@ from openpype.client import get_linked_representation_id from openpype.modules import ModulesManager from openpype.pipeline import load from openpype.modules.sync_server.utils import SiteAlreadyPresentError -from openpype.client.entities import ( - get_hero_version_by_subset_id, - get_representation_by_id, - get_version_by_id, - get_representation_by_name -) class AddSyncSite(load.LoaderPlugin): @@ -84,35 +78,3 @@ class AddSyncSite(load.LoaderPlugin): def filepath_from_context(self, context): """No real file loading""" return "" - - def _add_hero_representation_ids(self, project_name, repre_id): - """Find hero version if exists for repre_id. - - Args: - project_name (str) - repre_id (ObjectId) - Returns: - (list): at least [repre_id] if no hero version found - """ - representation_ids = [repre_id] - - repre_doc = get_representation_by_id( - project_name, repre_id, fields=["_id", "parent", "name"] - ) - - version_doc = get_version_by_id(project_name, repre_doc["parent"]) - if version_doc["type"] != "hero_version": - hero_version = get_hero_version_by_subset_id( - project_name, version_doc["parent"], - fields=["_id", "version_id"] - ) - if (hero_version and - hero_version["version_id"] == version_doc["_id"]): - hero_repre_doc = get_representation_by_name( - project_name, - repre_doc["name"], - hero_version["_id"] - ) - representation_ids.append(hero_repre_doc["_id"]) - - return representation_ids From af83e14dcea910b901969ed7590d6c72e749d2c8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 16:35:14 +0100 Subject: [PATCH 278/356] updating workflows to use YNPUT_BOT_TOKEN also some housecleaning was needed --- .github/workflows/automate-projects.yml | 19 -------- .github/workflows/milestone_assign.yml | 4 +- .github/workflows/milestone_create.yml | 8 ++-- .github/workflows/nightly_merge.yml | 4 +- .github/workflows/prerelease.yml | 40 +++------------- .github/workflows/release.yml | 64 ++++--------------------- .github/workflows/test_build.yml | 26 +--------- 7 files changed, 23 insertions(+), 142 deletions(-) delete mode 100644 .github/workflows/automate-projects.yml diff --git a/.github/workflows/automate-projects.yml b/.github/workflows/automate-projects.yml deleted file mode 100644 index b605071c2d..0000000000 --- a/.github/workflows/automate-projects.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Automate Projects - -on: - issues: - types: [opened, labeled] -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - assign_one_project: - runs-on: ubuntu-latest - name: Assign to One Project - steps: - - name: Assign NEW bugs to triage - uses: srggrs/assign-one-project-github-action@1.2.0 - if: contains(github.event.issue.labels.*.name, 'bug') - with: - project: 'https://github.com/pypeclub/pype/projects/2' - column_name: 'Needs triage' diff --git a/.github/workflows/milestone_assign.yml b/.github/workflows/milestone_assign.yml index 4b52dfc30d..d1490b64c0 100644 --- a/.github/workflows/milestone_assign.yml +++ b/.github/workflows/milestone_assign.yml @@ -13,7 +13,7 @@ jobs: if: github.event.pull_request.milestone == null uses: zoispag/action-assign-milestone@v1 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + repo-token: "${{ secrets.YNPUT_BOT_TOKEN }}" milestone: 'next-minor' run_if_develop: @@ -24,5 +24,5 @@ jobs: if: github.event.pull_request.milestone == null uses: zoispag/action-assign-milestone@v1 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + repo-token: "${{ secrets.YNPUT_BOT_TOKEN }}" milestone: 'next-patch' \ No newline at end of file diff --git a/.github/workflows/milestone_create.yml b/.github/workflows/milestone_create.yml index b56ca81dc1..cb459c7ae6 100644 --- a/.github/workflows/milestone_create.yml +++ b/.github/workflows/milestone_create.yml @@ -12,7 +12,7 @@ jobs: uses: "WyriHaximus/github-action-get-milestones@master" id: milestones env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" - run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number') id: querymilestone @@ -31,7 +31,7 @@ jobs: with: title: 'next-patch' env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" generate-next-minor: runs-on: ubuntu-latest @@ -40,7 +40,7 @@ jobs: uses: "WyriHaximus/github-action-get-milestones@master" id: milestones env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" - run: printf "name=number::%s" $(printenv MILESTONES | jq --arg MILESTONE $(printenv MILESTONE) '.[] | select(.title == $MILESTONE) | .number') id: querymilestone @@ -59,4 +59,4 @@ jobs: with: title: 'next-minor' env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index 1d36c89cc7..a50a5c2828 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -14,10 +14,10 @@ jobs: - name: 🚛 Checkout Code uses: actions/checkout@v2 - - name: 🔨 Merge develop to main + - name: 🔨 Merge develop to main uses: everlytic/branch-merge@1.1.0 with: - github_token: ${{ secrets.ADMIN_TOKEN }} + github_token: ${{ secrets.YNPUT_BOT_TOKEN }} source_ref: 'develop' target_branch: 'main' commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 94bbe48156..571b0339e1 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -25,43 +25,15 @@ jobs: - name: 🔎 Determine next version type id: version_type run: | - TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.GITHUB_TOKEN }}) - - echo ::set-output name=type::$TYPE + TYPE=$(python ./tools/ci_tools.py --bump --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) + echo "type=${TYPE}" >> $GITHUB_OUTPUT - name: 💉 Inject new version into files id: version if: steps.version_type.outputs.type != 'skip' run: | - RESULT=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.GITHUB_TOKEN }}) - - echo ::set-output name=next_tag::$RESULT - - # - name: "✏️ Generate full changelog" - # if: steps.version_type.outputs.type != 'skip' - # id: generate-full-changelog - # uses: heinrichreimer/github-changelog-generator-action@v2.3 - # with: - # token: ${{ secrets.ADMIN_TOKEN }} - # addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}' - # issues: false - # issuesWoLabels: false - # sinceTag: "3.12.0" - # maxIssues: 100 - # pullRequests: true - # prWoLabels: false - # author: false - # unreleased: true - # compareLink: true - # stripGeneratorNotice: true - # verbose: true - # unreleasedLabel: ${{ steps.version.outputs.next_tag }} - # excludeTagsRegex: "CI/.+" - # releaseBranch: "main" - - - name: "🖨️ Print changelog to console" - if: steps.version_type.outputs.type != 'skip' - run: cat CHANGELOG.md + NEW_VERSION_TAG=$(python ./tools/ci_tools.py --nightly --github_token ${{ secrets.YNPUT_BOT_TOKEN }}) + echo "next_tag=${NEW_VERSION_TAG}" >> $GITHUB_OUTPUT - name: 💾 Commit and Tag id: git_commit @@ -80,7 +52,7 @@ jobs: - name: Push to protected main branch uses: CasperWA/push-protected@v2.10.0 with: - token: ${{ secrets.ADMIN_TOKEN }} + token: ${{ secrets.YNPUT_BOT_TOKEN }} branch: main tags: true unprotect_reviews: true @@ -89,7 +61,7 @@ jobs: uses: everlytic/branch-merge@1.1.0 if: steps.version_type.outputs.type != 'skip' with: - github_token: ${{ secrets.ADMIN_TOKEN }} + github_token: ${{ secrets.YNPUT_BOT_TOKEN }} source_ref: 'main' target_branch: 'develop' commit_message_template: '[Automated] Merged {source_ref} into {target_branch}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e3b6eb05c..0b4c8af2c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,34 +26,12 @@ jobs: - name: 💉 Inject new version into files id: version run: | - echo ::set-output name=current_version::${GITHUB_REF#refs/*/} - RESULT=$(python ./tools/ci_tools.py --finalize ${GITHUB_REF#refs/*/}) - LASTRELEASE=$(python ./tools/ci_tools.py --lastversion release) + NEW_VERSION=$(python ./tools/ci_tools.py --finalize ${GITHUB_REF#refs/*/}) + LAST_VERSION=$(python ./tools/ci_tools.py --lastversion release) - echo ::set-output name=last_release::$LASTRELEASE - echo ::set-output name=release_tag::$RESULT - - # - name: "✏️ Generate full changelog" - # if: steps.version.outputs.release_tag != 'skip' - # id: generate-full-changelog - # uses: heinrichreimer/github-changelog-generator-action@v2.3 - # with: - # token: ${{ secrets.ADMIN_TOKEN }} - # addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}' - # issues: false - # issuesWoLabels: false - # sinceTag: "3.12.0" - # maxIssues: 100 - # pullRequests: true - # prWoLabels: false - # author: false - # unreleased: true - # compareLink: true - # stripGeneratorNotice: true - # verbose: true - # futureRelease: ${{ steps.version.outputs.release_tag }} - # excludeTagsRegex: "CI/.+" - # releaseBranch: "main" + echo "current_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + echo "last_release=${LAST_VERSION}" >> $GITHUB_OUTPUT + echo "release_tag=${NEW_VERSION}" >> $GITHUB_OUTPUT - name: 💾 Commit and Tag id: git_commit @@ -70,43 +48,17 @@ jobs: if: steps.version.outputs.release_tag != 'skip' uses: CasperWA/push-protected@v2.10.0 with: - token: ${{ secrets.ADMIN_TOKEN }} + token: ${{ secrets.YNPUT_BOT_TOKEN }} branch: main tags: true unprotect_reviews: true - - name: "✏️ Generate last changelog" - if: steps.version.outputs.release_tag != 'skip' - id: generate-last-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.2 - with: - token: ${{ secrets.ADMIN_TOKEN }} - addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}' - issues: false - issuesWoLabels: false - sinceTag: ${{ steps.version.outputs.last_release }} - maxIssues: 100 - pullRequests: true - prWoLabels: false - author: false - unreleased: true - compareLink: true - stripGeneratorNotice: true - verbose: true - futureRelease: ${{ steps.version.outputs.release_tag }} - excludeTagsRegex: "CI/.+" - releaseBranch: "main" - stripHeaders: true - base: 'none' - - - name: 🚀 Github Release if: steps.version.outputs.release_tag != 'skip' uses: ncipollo/release-action@v1 with: - body: ${{ steps.generate-last-changelog.outputs.changelog }} tag: ${{ steps.version.outputs.release_tag }} - token: ${{ secrets.ADMIN_TOKEN }} + token: ${{ secrets.YNPUT_BOT_TOKEN }} - name: ☠ Delete Pre-release if: steps.version.outputs.release_tag != 'skip' @@ -118,7 +70,7 @@ jobs: if: steps.version.outputs.release_tag != 'skip' uses: everlytic/branch-merge@1.1.0 with: - github_token: ${{ secrets.ADMIN_TOKEN }} + github_token: ${{ secrets.YNPUT_BOT_TOKEN }} source_ref: 'main' target_branch: 'develop' commit_message_template: '[Automated] Merged release {source_ref} into {target_branch}' diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index 0e6c242bd6..064a4d47e0 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -28,7 +28,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - + - name: 🧵 Install Requirements shell: pwsh run: | @@ -64,27 +64,3 @@ jobs: run: | export SKIP_THIRD_PARTY_VALIDATION="1" ./tools/build.sh - - # MacOS-latest: - - # runs-on: macos-latest - # strategy: - # matrix: - # python-version: [3.9] - - # steps: - # - name: 🚛 Checkout Code - # uses: actions/checkout@v2 - - # - name: Set up Python - # uses: actions/setup-python@v2 - # with: - # python-version: ${{ matrix.python-version }} - - # - name: 🧵 Install Requirements - # run: | - # ./tools/create_env.sh - - # - name: 🔨 Build - # run: | - # ./tools/build.sh From 240c77c9bbd49d5133c17717b6358769c2c63104 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 Feb 2023 16:43:18 +0100 Subject: [PATCH 279/356] fixing nightly versioning --- .github/workflows/milestone_assign.yml | 2 +- .github/workflows/milestone_create.yml | 2 +- .github/workflows/nightly_merge.yml | 2 +- pyproject.toml | 2 +- tools/ci_tools.py | 24 +++++++++++------------- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/.github/workflows/milestone_assign.yml b/.github/workflows/milestone_assign.yml index d1490b64c0..3cbee51472 100644 --- a/.github/workflows/milestone_assign.yml +++ b/.github/workflows/milestone_assign.yml @@ -25,4 +25,4 @@ jobs: uses: zoispag/action-assign-milestone@v1 with: repo-token: "${{ secrets.YNPUT_BOT_TOKEN }}" - milestone: 'next-patch' \ No newline at end of file + milestone: 'next-patch' diff --git a/.github/workflows/milestone_create.yml b/.github/workflows/milestone_create.yml index cb459c7ae6..632704e64a 100644 --- a/.github/workflows/milestone_create.yml +++ b/.github/workflows/milestone_create.yml @@ -59,4 +59,4 @@ jobs: with: title: 'next-minor' env: - GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" \ No newline at end of file + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index a50a5c2828..c5cd9f1466 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -26,4 +26,4 @@ jobs: uses: benc-uk/workflow-dispatch@v1 with: workflow: Nightly Prerelease - token: ${{ secrets.ADMIN_TOKEN }} \ No newline at end of file + token: ${{ secrets.ADMIN_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 6e88404700..a872ed3609 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1-nightly.3" # OpenPype +version = "3.15.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/tools/ci_tools.py b/tools/ci_tools.py index c8f0cd48b4..750bf8645d 100644 --- a/tools/ci_tools.py +++ b/tools/ci_tools.py @@ -7,16 +7,10 @@ from github import Github import os def get_release_type_github(Log, github_token): - # print(Log) minor_labels = ["Bump Minor"] - # patch_labels = [ - # "type: enhancement", - # "type: bug", - # "type: deprecated", - # "type: Feature"] g = Github(github_token) - repo = g.get_repo("pypeclub/OpenPype") + repo = g.get_repo("ynput/OpenPype") labels = set() for line in Log.splitlines(): @@ -35,12 +29,12 @@ def get_release_type_github(Log, github_token): else: return "patch" - # TODO: if all is working fine, this part can be cleaned up eventually + # TODO: if all is working fine, this part can be cleaned up eventually # if any(label in labels for label in patch_labels): # return "patch" - + return None - + def remove_prefix(text, prefix): return text[text.startswith(prefix) and len(prefix):] @@ -93,12 +87,16 @@ def file_regex_replace(filename, regex, version): f.truncate() -def bump_file_versions(version): +def bump_file_versions(version, nightly=False): filename = "./openpype/version.py" regex = "(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?" file_regex_replace(filename, regex, version) + if nightly: + # skip nightly reversion in pyproject.toml + return + # bump pyproject.toml filename = "pyproject.toml" regex = "version = \"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(\+((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?\" # OpenPype" @@ -196,7 +194,7 @@ def main(): if options.nightly: next_tag_v = calculate_next_nightly(github_token=options.github_token) print(next_tag_v) - bump_file_versions(next_tag_v) + bump_file_versions(next_tag_v, True) if options.finalize: new_release = finalize_prerelease(options.finalize) @@ -222,7 +220,7 @@ def main(): new_prerelease = current_prerelease.bump_prerelease().__str__() print(new_prerelease) bump_file_versions(new_prerelease) - + if options.version: bump_file_versions(options.version) print(f"Injected version {options.version} into the release") From f805e7501d488e82bffcf31aaaab2d63ae15d2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 9 Feb 2023 16:55:56 +0100 Subject: [PATCH 280/356] removing last reference of ADMIN_TOKEN --- .github/workflows/nightly_merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly_merge.yml b/.github/workflows/nightly_merge.yml index c5cd9f1466..1776d7a464 100644 --- a/.github/workflows/nightly_merge.yml +++ b/.github/workflows/nightly_merge.yml @@ -26,4 +26,4 @@ jobs: uses: benc-uk/workflow-dispatch@v1 with: workflow: Nightly Prerelease - token: ${{ secrets.ADMIN_TOKEN }} + token: ${{ secrets.YNPUT_BOT_TOKEN }} From 922fe5bdaf17d8e78b30b58b1ca41bbeb54f9307 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 9 Feb 2023 15:59:27 +0000 Subject: [PATCH 281/356] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c65d1197c1..61339fb3dd 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.3" +__version__ = "3.15.1-nightly.4" From d516de4ecdfcf17b2d8f91d91535d22ef7c6ad13 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Feb 2023 16:28:55 +0000 Subject: [PATCH 282/356] Ensure content and proxy hierarchy is the same. --- .../publish/collect_arnold_scene_source.py | 4 +- .../publish/extract_arnold_scene_source.py | 51 ++++++--- .../publish/validate_arnold_scene_source.py | 106 ++++++++++++++++++ 3 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py 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 c0275eef7b..0415808b7a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -21,10 +21,10 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if objset.endswith("content_SET"): - instance.data["setMembers"] = members + instance.data["setMembers"] = cmds.ls(members, long=True) self.log.debug("content members: {}".format(members)) elif objset.endswith("proxy_SET"): - instance.data["proxy"] = members + instance.data["proxy"] = cmds.ls(members, long=True) self.log.debug("proxy members: {}".format(members)) # Use camera in object set if present else default to render globals 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 4cff9d0183..10943dd810 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -5,14 +5,16 @@ from maya import cmds import arnold from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection, attribute_values +from openpype.hosts.maya.api.lib import ( + maintained_selection, attribute_values, delete_after +) from openpype.lib import StringTemplate class ExtractArnoldSceneSource(publish.Extractor): """Extract the content of the instance to an Arnold Scene Source file.""" - label = "Arnold Scene Source" + label = "Extract Arnold Scene Source" hosts = ["maya"] families = ["ass"] asciiAss = False @@ -124,22 +126,43 @@ class ExtractArnoldSceneSource(publish.Extractor): def _extract(self, nodes, attribute_data, kwargs): self.log.info("Writing: " + kwargs["filename"]) filenames = [] - with attribute_values(attribute_data): - with maintained_selection(): - self.log.info( - "Writing: {}".format(nodes) + # 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: + duplicate_nodes = [] + for node in nodes: + duplicate_transform = cmds.duplicate(node)[0] + delete_bin.append(duplicate_transform) + + # Discard the children. + shapes = cmds.listRelatives(duplicate_transform, shapes=True) + children = cmds.listRelatives( + duplicate_transform, children=True ) - cmds.select(nodes, noExpand=True) + cmds.delete(set(children) - set(shapes)) - self.log.info( - "Extracting ass sequence with: {}".format(kwargs) - ) + duplicate_transform = cmds.parent( + duplicate_transform, world=True + )[0] - exported_files = cmds.arnoldExportAss(**kwargs) + duplicate_nodes.append(duplicate_transform) - for file in exported_files: - filenames.append(os.path.split(file)[1]) + with attribute_values(attribute_data): + with maintained_selection(): + self.log.info( + "Writing: {}".format(duplicate_nodes) + ) + cmds.select(duplicate_nodes, noExpand=True) - self.log.info("Exported: {}".format(filenames)) + self.log.info( + "Extracting ass sequence with: {}".format(kwargs) + ) + + exported_files = cmds.arnoldExportAss(**kwargs) + + for file in exported_files: + filenames.append(os.path.split(file)[1]) + + self.log.info("Exported: {}".format(filenames)) return filenames diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py new file mode 100644 index 0000000000..ad00502d56 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -0,0 +1,106 @@ +import os +import types + +import maya.cmds as cmds +from mtoa.core import createOptions + +import pyblish.api +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishValidationError +) + + +class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): + """Validate Arnold Scene Source. + + If using proxies we need the nodes to share the same names and not be + parent to the world. This ends up needing at least two groups with content + nodes and proxy nodes in another. + """ + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["ass"] + label = "Validate Arnold Scene Source" + + def _get_nodes_data(self, nodes): + ungrouped_nodes = [] + nodes_by_name = {} + parents = [] + for node in nodes: + node_split = node.split("|") + if len(node_split) == 2: + ungrouped_nodes.append(node) + + parent = "|".join(node_split[:-1]) + 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 + + return ungrouped_nodes, nodes_by_name, parents + + def process(self, instance): + if not instance.data["proxy"]: + return + + ungrouped_nodes = [] + + nodes, content_nodes_by_name, content_parents = self._get_nodes_data( + instance.data["setMembers"] + ) + ungrouped_nodes.extend(nodes) + + nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_data( + instance.data["proxy"] + ) + ungrouped_nodes.extend(nodes) + + # Validate against nodes directly parented to world. + if ungrouped_nodes: + raise PublishValidationError( + "Found nodes parented to the world: {}\n" + "All nodes need to be grouped.".format(ungrouped_nodes) + ) + + # Validate for content and proxy nodes amount being the same. + if len(instance.data["setMembers"]) != 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["proxy"]) + ) + ) + + # Validate against content and proxy nodes sharing same parent. + if list(set(content_parents) & set(proxy_parents)): + raise PublishValidationError( + "Content and proxy nodes cannot share the same parent." + ) + + # Validate for content and proxy nodes sharing same names. + sorted_content_names = sorted(content_nodes_by_name.keys()) + sorted_proxy_names = sorted(proxy_nodes_by_name.keys()) + odd_content_names = list( + set(sorted_content_names) - set(sorted_proxy_names) + ) + odd_content_nodes = [ + content_nodes_by_name[x] for x in odd_content_names + ] + odd_proxy_names = list( + set(sorted_proxy_names) - set(sorted_content_names) + ) + odd_proxy_nodes = [ + proxy_nodes_by_name[x] for x in odd_proxy_names + ] + if not sorted_content_names == sorted_proxy_names: + raise PublishValidationError( + "Content and proxy nodes need to share the same names.\n" + "Content nodes not matching: {}\n" + "Proxy nodes not matching: {}".format( + odd_content_nodes, odd_proxy_nodes + ) + ) From 8ee1a3a2d2cb7b952f6aee1385b31abee5d9add7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 Feb 2023 17:11:31 +0000 Subject: [PATCH 283/356] Document proxy workflow --- website/docs/artist_hosts_maya_arnold.md | 14 ++++++++++++++ .../docs/assets/maya-arnold_scene_source.png | Bin 0 -> 15948 bytes website/docs/assets/maya-arnold_standin.png | Bin 0 -> 42985 bytes 3 files changed, 14 insertions(+) create mode 100644 website/docs/assets/maya-arnold_scene_source.png create mode 100644 website/docs/assets/maya-arnold_standin.png diff --git a/website/docs/artist_hosts_maya_arnold.md b/website/docs/artist_hosts_maya_arnold.md index b8b8da6d57..b3c02a0894 100644 --- a/website/docs/artist_hosts_maya_arnold.md +++ b/website/docs/artist_hosts_maya_arnold.md @@ -8,9 +8,23 @@ Arnold Scene Source can be published as a single file or a sequence of files, de 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. +### 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: + +![Arnold Scene Source](assets/maya-arnold_scene_source.png) + ## Standin Arnold Scene Source `ass` and Alembic `abc` are supported to load as standins. +### Standin Proxy Workflow If a subset has a proxy representation, this will be used as display in the viewport. At render time the standin path will be replaced using the recommended string replacement workflow; https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_maya_operators_am_Updating_procedural_file_paths_with_string_replace_html + +Since the content and proxy nodes share the same names and hierarchy, any manually shader assignments will be shared. + + +:::note for advanced users +You can stop the proxy swapping by disabling the string replacement operator found in the container. +![Arnold Standin](assets/maya-arnold_standin.png) +::: diff --git a/website/docs/assets/maya-arnold_scene_source.png b/website/docs/assets/maya-arnold_scene_source.png new file mode 100644 index 0000000000000000000000000000000000000000..4150b78aac7bfe776d9f4b4940a56d77176a2a57 GIT binary patch literal 15948 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

M-ZZ1puA}J8G$tO$CyZX_jwrh#*yh&^Hbc( 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

H1$S7zu1R^u(X=+a-N2qaE)ReLSRVk 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 Date: Thu, 9 Feb 2023 17:49:25 +0000 Subject: [PATCH 284/356] Hound --- .../maya/plugins/publish/validate_arnold_scene_source.py | 4 ---- 1 file changed, 4 deletions(-) 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 ad00502d56..2d6c6e8e14 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -1,8 +1,4 @@ -import os -import types - import maya.cmds as cmds -from mtoa.core import createOptions import pyblish.api from openpype.pipeline.publish import ( From 0fdb957a4e9b43b638ee1e36f30b54cb81be0280 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 19:13:39 +0100 Subject: [PATCH 285/356] have information about project code on anatomy --- openpype/pipeline/anatomy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index a18b46d9ac..49d86d69d6 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -60,6 +60,7 @@ class BaseAnatomy(object): def __init__(self, project_doc, local_settings, site_name): project_name = project_doc["name"] self.project_name = project_name + self.project_code = project_doc["data"]["code"] if (site_name and site_name not in ["studio", "local", get_local_site_id()]): From b5b155828aeb340621d6be12ed4716e259b190ac Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 20:10:41 +0100 Subject: [PATCH 286/356] use 'backslashreplace' on error of output decoding --- openpype/lib/execute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/execute.py b/openpype/lib/execute.py index f1f2a4fa0a..39532b7aa5 100644 --- a/openpype/lib/execute.py +++ b/openpype/lib/execute.py @@ -117,12 +117,12 @@ def run_subprocess(*args, **kwargs): full_output = "" _stdout, _stderr = proc.communicate() if _stdout: - _stdout = _stdout.decode("utf-8") + _stdout = _stdout.decode("utf-8", errors="backslashreplace") full_output += _stdout logger.debug(_stdout) if _stderr: - _stderr = _stderr.decode("utf-8") + _stderr = _stderr.decode("utf-8", errors="backslashreplace") # Add additional line break if output already contains stdout if full_output: full_output += "\n" From f6ee5db2278a4e7e3cbb71242aee418fafb72357 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 20:26:49 +0100 Subject: [PATCH 287/356] creasted spin boxes that allow mouse scroll changes only on active widgets --- openpype/tools/utils/__init__.py | 4 ++++ openpype/tools/utils/widgets.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index d51ebb5744..4292e2d726 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -1,4 +1,6 @@ from .widgets import ( + FocusSpinBox, + FocusDoubleSpinBox, CustomTextComboBox, PlaceholderLineEdit, BaseClickableFrame, @@ -34,6 +36,8 @@ from .overlay_messages import ( __all__ = ( + "FocusSpinBox", + "FocusDoubleSpinBox", "CustomTextComboBox", "PlaceholderLineEdit", "BaseClickableFrame", diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index 41573687e1..b416c56797 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -13,6 +13,34 @@ from openpype.lib.attribute_definitions import AbstractAttrDef log = logging.getLogger(__name__) +class FocusSpinBox(QtWidgets.QSpinBox): + """QSpinBox which allow scroll wheel changes only in active state.""" + + def __init__(self, *args, **kwargs): + super(FocusSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if not self.hasFocus(): + event.ignore() + else: + super(FocusSpinBox, self).wheelEvent(event) + + +class FocusDoubleSpinBox(QtWidgets.QDoubleSpinBox): + """QDoubleSpinBox which allow scroll wheel changes only in active state.""" + + def __init__(self, *args, **kwargs): + super(FocusDoubleSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if not self.hasFocus(): + event.ignore() + else: + super(FocusDoubleSpinBox, self).wheelEvent(event) + + class CustomTextComboBox(QtWidgets.QComboBox): """Combobox which can have different text showed.""" From 9cc8f1818c7b37d44c6c52b4c90cce3febe9a2ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 20:27:15 +0100 Subject: [PATCH 288/356] use new widgets in attribute definitions --- openpype/tools/attribute_defs/widgets.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 3cec1d2683..26aa794930 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -16,7 +16,11 @@ from openpype.lib.attribute_definitions import ( UISeparatorDef, UILabelDef ) -from openpype.tools.utils import CustomTextComboBox +from openpype.tools.utils import ( + CustomTextComboBox, + FocusSpinBox, + FocusDoubleSpinBox, +) from openpype.widgets.nice_checkbox import NiceCheckbox from .files_widget import FilesWidget @@ -243,10 +247,10 @@ class NumberAttrWidget(_BaseAttrDefWidget): def _ui_init(self): decimals = self.attr_def.decimals if decimals > 0: - input_widget = QtWidgets.QDoubleSpinBox(self) + input_widget = FocusDoubleSpinBox(self) input_widget.setDecimals(decimals) else: - input_widget = QtWidgets.QSpinBox(self) + input_widget = FocusSpinBox(self) if self.attr_def.tooltip: input_widget.setToolTip(self.attr_def.tooltip) From b7078d77e20bb2d6711f1fc842c99e915f3c9abd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Feb 2023 20:27:36 +0100 Subject: [PATCH 289/356] label of attribute definition also have tooltip --- openpype/tools/attribute_defs/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/attribute_defs/widgets.py b/openpype/tools/attribute_defs/widgets.py index 26aa794930..18e2e13d06 100644 --- a/openpype/tools/attribute_defs/widgets.py +++ b/openpype/tools/attribute_defs/widgets.py @@ -146,6 +146,9 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget): if attr_def.label: label_widget = QtWidgets.QLabel(attr_def.label, self) + tooltip = attr_def.tooltip + if tooltip: + label_widget.setToolTip(tooltip) layout.addWidget( label_widget, row, 0, 1, expand_cols ) From 514ba7e79b55e1aedcfe32c1fe4b949c32af0c13 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 07:25:38 +0000 Subject: [PATCH 290/356] Fix reset_frame_range --- openpype/hosts/maya/api/commands.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index 4a36406632..19ad18d824 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -4,6 +4,7 @@ from maya import cmds from openpype.client import get_asset_by_name, get_project from openpype.pipeline import legacy_io +from . import lib class ToolWindows: @@ -59,25 +60,11 @@ def edit_shader_definitions(): def reset_frame_range(): """Set frame range to current asset""" - # Set FPS first - fps = {15: 'game', - 24: 'film', - 25: 'pal', - 30: 'ntsc', - 48: 'show', - 50: 'palf', - 60: 'ntscf', - 23.98: '23.976fps', - 23.976: '23.976fps', - 29.97: '29.97fps', - 47.952: '47.952fps', - 47.95: '47.952fps', - 59.94: '59.94fps', - 44100: '44100fps', - 48000: '48000fps' - }.get(float(legacy_io.Session.get("AVALON_FPS", 25)), "pal") - cmds.currentUnit(time=fps) + fps = lib.convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + lib.set_scene_fps(fps) # Set frame start/end project_name = legacy_io.active_project() From 10c4305542b162d93907c3c1d48c6433cbde1742 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 08:06:47 +0000 Subject: [PATCH 291/356] Create Arnold options on repair. --- .../maya/plugins/publish/validate_ass_relative_paths.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index ac6ce4d22d..1c271a4a04 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -2,6 +2,7 @@ import os import types import maya.cmds as cmds +from mtoa.core import createOptions import pyblish.api from openpype.pipeline.publish import ( @@ -34,8 +35,7 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): "defaultArnoldRenderOptions.pspath" ) except ValueError: - assert False, ("Can not validate, render setting were not opened " - "yet so Arnold setting cannot be validate") + assert False, ("Default Arnold options has not been created yet.") scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) scene_name, _ = os.path.splitext(scene_basename) @@ -66,6 +66,8 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): + createOptions() + texture_path = cmds.getAttr("defaultArnoldRenderOptions.tspath") procedural_path = cmds.getAttr("defaultArnoldRenderOptions.pspath") From 561cebc51c61e02e01d2ae3145a688f2709beb85 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 08:14:30 +0000 Subject: [PATCH 292/356] Fix assertion --- .../maya/plugins/publish/validate_ass_relative_paths.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 1c271a4a04..6975d583bb 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -8,6 +8,7 @@ import pyblish.api from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -35,7 +36,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): "defaultArnoldRenderOptions.pspath" ) except ValueError: - assert False, ("Default Arnold options has not been created yet.") + raise PublishValidationError( + "Default Arnold options has not been created yet." + ) scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) scene_name, _ = os.path.splitext(scene_basename) From ff63a91864af8d9dbfdfe940863468a03c0233af Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 10:00:49 +0000 Subject: [PATCH 293/356] Support switching between proxy and non-proxy --- .../maya/plugins/load/load_arnold_standin.py | 25 ++++++++++++------- .../publish/extract_arnold_scene_source.py | 8 +++++- .../publish/validate_arnold_scene_source.py | 12 ++++++--- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index e2bb89ed77..bebe40f9a6 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -53,10 +53,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): root = cmds.group(name=label, empty=True) # Set color. - project_name = context["project"]["name"] - settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - color = colors.get('ass') + settings = get_project_settings(context["project"]["name"]) + color = settings['maya']['load']['colors'].get('ass') if color is not None: cmds.setAttr(root + ".useOutlinerColor", True) cmds.setAttr( @@ -121,10 +119,6 @@ class ArnoldStandinLoader(load.LoaderPlugin): def _setup_proxy(self, shape, path, namespace): proxy_basename, proxy_path = self._get_proxy_path(path) - if not os.path.exists(proxy_path): - self.log.error("Proxy files do not exist. Skipping proxy setup.") - return path, None - options_node = "defaultArnoldRenderOptions" merge_operator = get_attribute_input(options_node + ".operator") if merge_operator is None: @@ -163,6 +157,12 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) ) + # We setup the string operator no matter whether there is a proxy or + # not. This makes it easier to update since the string operator will + # always be created. Return original path to use for standin. + if not os.path.exists(proxy_path): + return path, string_replace_operator + return proxy_path, string_replace_operator def update(self, container, representation): @@ -180,6 +180,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): path = get_representation_path(representation) 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. cmds.setAttr( string_replace_operator + ".match", "resources/" + proxy_basename, @@ -190,7 +193,11 @@ class ArnoldStandinLoader(load.LoaderPlugin): os.path.basename(path), type="string" ) - cmds.setAttr(standin + ".dso", proxy_path, type="string") + + dso_path = path + if os.path.exists(proxy_path): + dso_path = proxy_path + cmds.setAttr(standin + ".dso", dso_path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(path))) cmds.setAttr(standin + ".useFrameExtension", sequence) 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 10943dd810..153a1a513e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -95,6 +95,9 @@ class ExtractArnoldSceneSource(publish.Extractor): ) # Extract proxy. + if not instance.data.get("proxy", []): + return + kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") filenames = self._extract( instance.data["proxy"], attribute_data, kwargs @@ -132,7 +135,6 @@ class ExtractArnoldSceneSource(publish.Extractor): duplicate_nodes = [] for node in nodes: duplicate_transform = cmds.duplicate(node)[0] - delete_bin.append(duplicate_transform) # Discard the children. shapes = cmds.listRelatives(duplicate_transform, shapes=True) @@ -145,7 +147,11 @@ class ExtractArnoldSceneSource(publish.Extractor): duplicate_transform, world=True )[0] + cmds.rename(duplicate_transform, node.split("|")[-1]) + duplicate_transform = "|" + node.split("|")[-1] + duplicate_nodes.append(duplicate_transform) + delete_bin.append(duplicate_transform) with attribute_values(attribute_data): with maintained_selection(): 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 2d6c6e8e14..3b0ffd52d7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -9,6 +9,9 @@ from openpype.pipeline.publish import ( class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): """Validate Arnold Scene Source. + We require at least 1 root node/parent for the meshes. This is to ensure we + can duplicate the nodes and preserve the names. + If using proxies we need the nodes to share the same names and not be parent to the world. This ends up needing at least two groups with content nodes and proxy nodes in another. @@ -39,9 +42,6 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): return ungrouped_nodes, nodes_by_name, parents def process(self, instance): - if not instance.data["proxy"]: - return - ungrouped_nodes = [] nodes, content_nodes_by_name, content_parents = self._get_nodes_data( @@ -50,7 +50,7 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): ungrouped_nodes.extend(nodes) nodes, proxy_nodes_by_name, proxy_parents = self._get_nodes_data( - instance.data["proxy"] + instance.data.get("proxy", []) ) ungrouped_nodes.extend(nodes) @@ -61,6 +61,10 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): "All nodes need to be grouped.".format(ungrouped_nodes) ) + # Proxy validation. + if not instance.data.get("proxy", []): + return + # Validate for content and proxy nodes amount being the same. if len(instance.data["setMembers"]) != len(instance.data["proxy"]): raise PublishValidationError( From 165689463dde7be46804a18166434a5ef8f6ee8b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:20:20 +0100 Subject: [PATCH 294/356] typo --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index aaa2dd444a..c15eadb22f 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -650,7 +650,7 @@ def get_instance_staging_dir(instance): else: project_name = os.getenv("AVALON_PROJECT") - # get customized tempdir path from `OPENPYPE_TEMPDIR` env var + # get customized tempdir path from `OPENPYPE_TMPDIR` env var custom_temp_dir = temporarydir.create_custom_tempdir( project_name, anatomy=anatomy, formating_data=anatomy_data ) From 87f9cf09d77cc8ccec04c2c8dd31905f425ba212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:23:53 +0100 Subject: [PATCH 295/356] Update openpype/pipeline/temporarydir.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/temporarydir.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/temporarydir.py b/openpype/pipeline/temporarydir.py index 31586d82c8..c5805b2dc1 100644 --- a/openpype/pipeline/temporarydir.py +++ b/openpype/pipeline/temporarydir.py @@ -38,10 +38,9 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): } if formating_data is None: # We still don't have `project_code` on Anatomy... - project_doc = anatomy.get_project_doc_from_cache(project_name) data["project"] = { "name": project_name, - "code": project_doc["data"]["code"], + "code": anatomy.project_code, } else: data["project"] = formating_data["project"] From bbd634bcd428b630324b7fbe57324c6eac8bf4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:24:06 +0100 Subject: [PATCH 296/356] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index c15eadb22f..423661880c 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -643,7 +643,7 @@ def get_instance_staging_dir(instance): return staging_dir anatomy_data = instance.data.get("anatomy_data") - anatomy = instance.data.get("anatomy") + anatomy = instance.context.data.get("anatomy") if anatomy_data: project_name = anatomy_data["project"]["name"] From af3c0cb951bcd4227ab07cfe174734cd43645b1d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:26:09 +0100 Subject: [PATCH 297/356] pr comments --- openpype/pipeline/publish/lib.py | 4 ++-- openpype/pipeline/{temporarydir.py => tempdir.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename openpype/pipeline/{temporarydir.py => tempdir.py} (100%) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 423661880c..d6e8097690 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -19,7 +19,7 @@ from openpype.settings import ( get_system_settings, ) from openpype.pipeline import ( - temporarydir + tempdir ) from .contants import ( @@ -651,7 +651,7 @@ def get_instance_staging_dir(instance): project_name = os.getenv("AVALON_PROJECT") # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = temporarydir.create_custom_tempdir( + custom_temp_dir = tempdir.create_custom_tempdir( project_name, anatomy=anatomy, formating_data=anatomy_data ) diff --git a/openpype/pipeline/temporarydir.py b/openpype/pipeline/tempdir.py similarity index 100% rename from openpype/pipeline/temporarydir.py rename to openpype/pipeline/tempdir.py From 69937c62858a69d9d42beaeeaa6d23e5073a9446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 11:27:30 +0100 Subject: [PATCH 298/356] Update openpype/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index d6e8097690..7d3c367c7a 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -648,7 +648,7 @@ def get_instance_staging_dir(instance): if anatomy_data: project_name = anatomy_data["project"]["name"] else: - project_name = os.getenv("AVALON_PROJECT") + project_name = instance.context.data["projectName"] # get customized tempdir path from `OPENPYPE_TMPDIR` env var custom_temp_dir = tempdir.create_custom_tempdir( From be0209e4135bea83ffbda230aa23f33651e9cbd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 11:34:05 +0100 Subject: [PATCH 299/356] refactor in favour of code changes from #4445 https://github.com/ynput/OpenPype/pull/4445 --- openpype/pipeline/publish/lib.py | 10 +--------- openpype/pipeline/tempdir.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 7d3c367c7a..2884dd495f 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -642,18 +642,10 @@ def get_instance_staging_dir(instance): if staging_dir: return staging_dir - anatomy_data = instance.data.get("anatomy_data") anatomy = instance.context.data.get("anatomy") - if anatomy_data: - project_name = anatomy_data["project"]["name"] - else: - project_name = instance.context.data["projectName"] - # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir( - project_name, anatomy=anatomy, formating_data=anatomy_data - ) + custom_temp_dir = tempdir.create_custom_tempdir(anatomy) if custom_temp_dir: staging_dir = os.path.normpath( diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index c5805b2dc1..ff5c58bbc5 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -7,7 +7,7 @@ from openpype.lib import StringTemplate from openpype.pipeline import Anatomy -def create_custom_tempdir(project_name, anatomy=None, formating_data=None): +def create_custom_tempdir(anatomy=None): """ Create custom tempdir Template path formatting is supporting: @@ -17,9 +17,7 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): - project[name | code] Args: - project_name (str): name of project anatomy (openpype.pipeline.Anatomy): Anatomy object - formating_data (dict): formating data used for filling template. Returns: bool | str: formated path or None @@ -31,20 +29,15 @@ def create_custom_tempdir(project_name, anatomy=None, formating_data=None): custom_tempdir = None if "{" in openpype_tempdir: if anatomy is None: - anatomy = Anatomy(project_name) + anatomy = Anatomy() # create base formate data data = { - "root": anatomy.roots - } - if formating_data is None: - # We still don't have `project_code` on Anatomy... - data["project"] = { - "name": project_name, + "root": anatomy.roots, + "project": { + "name": anatomy.project_name, "code": anatomy.project_code, } - else: - data["project"] = formating_data["project"] - + } # path is anatomy template custom_tempdir = StringTemplate.format_template( openpype_tempdir, data).normalized() From c5c91183c3930e8798a1d21340ab71b833342a05 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 11:35:01 +0100 Subject: [PATCH 300/356] fix other places where decoding of ffmpeg happens --- openpype/hosts/harmony/plugins/publish/extract_render.py | 4 ++-- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py index 2f8169248e..c29864bb28 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -108,9 +108,9 @@ class ExtractRender(pyblish.api.InstancePlugin): output = process.communicate()[0] if process.returncode != 0: - raise ValueError(output.decode("utf-8")) + raise ValueError(output.decode("utf-8", errors="backslashreplace")) - self.log.debug(output.decode("utf-8")) + self.log.debug(output.decode("utf-8", errors="backslashreplace")) # Generate representations. extension = collection.tail[1:] diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 625a3f1a28..861f16518c 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -204,10 +204,10 @@ def info_about_input(oiiotool_path, filepath): _stdout, _stderr = popen.communicate() output = "" if _stdout: - output += _stdout.decode("utf-8") + output += _stdout.decode("utf-8", errors="backslashreplace") if _stderr: - output += _stderr.decode("utf-8") + output += _stderr.decode("utf-8", errors="backslashreplace") output = output.replace("\r\n", "\n") xml_started = False From 01293aaa2db33747ee45a9194d65fd1555e2d61a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 12:00:30 +0100 Subject: [PATCH 301/356] fix burnins script again --- openpype/scripts/otio_burnin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 7223e8d4de..3e40bf0c8b 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -340,13 +340,11 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): _stdout, _stderr = proc.communicate() if _stdout: - for line in _stdout.split(b"\r\n"): - print(line.decode("utf-8")) + print(_stdout.decode("utf-8", errors="backslashreplace")) # This will probably never happen as ffmpeg use stdout if _stderr: - for line in _stderr.split(b"\r\n"): - print(line.decode("utf-8")) + print(_stderr.decode("utf-8", errors="backslashreplace")) if proc.returncode != 0: raise RuntimeError( From 9f4153fbe64ee0f5918354a2723358a760fb571d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 12:27:10 +0000 Subject: [PATCH 302/356] Code cosmetics --- .../hosts/maya/plugins/load/load_arnold_standin.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index bebe40f9a6..6e5fe16bcd 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -94,17 +94,13 @@ class ArnoldStandinLoader(load.LoaderPlugin): def get_next_free_multi_index(self, attr_name): """Find the next unconnected multi index at the input attribute.""" - - start_index = 0 - # Assume a max of 10 million connections - while start_index < 10000000: + for index in range(10000000): connection_info = cmds.connectionInfo( - "{}[{}]".format(attr_name, start_index), + "{}[{}]".format(attr_name, index), sourceFromDestination=True ) if len(connection_info or []) == 0: - return start_index - start_index += 1 + return index def _get_proxy_path(self, path): basename_split = os.path.basename(path).split(".") From 3927dc13af80888375dd1151ff9eef0929a71f9e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 13:42:46 +0100 Subject: [PATCH 303/356] adding back project name --- openpype/pipeline/publish/lib.py | 3 ++- openpype/pipeline/tempdir.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 2884dd495f..cc7f5678f5 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -645,7 +645,8 @@ def get_instance_staging_dir(instance): anatomy = instance.context.data.get("anatomy") # get customized tempdir path from `OPENPYPE_TMPDIR` env var - custom_temp_dir = tempdir.create_custom_tempdir(anatomy) + custom_temp_dir = tempdir.create_custom_tempdir( + anatomy.project_name, anatomy) if custom_temp_dir: staging_dir = os.path.normpath( diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index ff5c58bbc5..ab3cc216ef 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -7,7 +7,7 @@ from openpype.lib import StringTemplate from openpype.pipeline import Anatomy -def create_custom_tempdir(anatomy=None): +def create_custom_tempdir(project_name, anatomy=None): """ Create custom tempdir Template path formatting is supporting: @@ -17,7 +17,8 @@ def create_custom_tempdir(anatomy=None): - project[name | code] Args: - anatomy (openpype.pipeline.Anatomy): Anatomy object + project_name (str): project name + anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: bool | str: formated path or None @@ -29,7 +30,7 @@ def create_custom_tempdir(anatomy=None): custom_tempdir = None if "{" in openpype_tempdir: if anatomy is None: - anatomy = Anatomy() + anatomy = Anatomy(project_name) # create base formate data data = { "root": anatomy.roots, From f458fbc9258e97e0e9f340d3e9e59c3eb5b2b820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 10 Feb 2023 13:44:01 +0100 Subject: [PATCH 304/356] Update openpype/pipeline/tempdir.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index ab3cc216ef..6a346f3342 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -21,7 +21,7 @@ def create_custom_tempdir(project_name, anatomy=None): anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: - bool | str: formated path or None + str | None: formated path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 8494a45d6c6ac20f3a9fb287c3835378b2e0c4d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 16:11:10 +0100 Subject: [PATCH 305/356] validate representation files in separated method --- openpype/plugins/publish/integrate.py | 56 +++++++++++++++++++-------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7b73943c37..7074410a84 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -506,6 +506,43 @@ class IntegrateAsset(pyblish.api.InstancePlugin): return version_doc + def _validate_repre_files(self, files, is_sequence_representation): + """Validate representation files before transfer preparation. + + Check if files contain only filenames instead of full paths and check + if sequence don't contain more than one sequence or has remainders. + + Args: + files (Union[str, List[str]]): Files from representation. + is_sequence_representation (bool): Files are for sequence. + + Raises: + KnownPublishError: If validations don't pass. + """ + + if not files: + return + + if not is_sequence_representation: + files = [files] + + if any(os.path.isabs(fname) for fname in files): + raise KnownPublishError("Given file names contain full paths") + + if not is_sequence_representation: + return + + src_collections, remainders = clique.assemble(files) + if len(files) < 2 or len(src_collections) != 1 or remainders: + raise KnownPublishError(( + "Files of representation does not contain proper" + " sequence files.\nCollected collections: {}" + "\nCollected remainders: {}" + ).format( + ", ".join([str(col) for col in src_collections]), + ", ".join([str(rem) for rem in remainders]) + )) + def prepare_representation(self, repre, template_name, existing_repres_by_name, @@ -606,21 +643,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_data["originalDirname"] = without_root is_sequence_representation = isinstance(files, (list, tuple)) + self._validate_repre_files(files, is_sequence_representation) + if is_sequence_representation: # Collection of files (sequence) if any(os.path.isabs(fname) for fname in files): raise KnownPublishError("Given file names contain full paths") src_collections, remainders = clique.assemble(files) - if len(files) < 2 or len(src_collections) != 1 or remainders: - raise KnownPublishError(( - "Files of representation does not contain proper" - " sequence files.\nCollected collections: {}" - "\nCollected remainders: {}" - ).format( - ", ".join([str(col) for col in src_collections]), - ", ".join([str(rem) for rem in remainders]) - )) src_collection = src_collections[0] template_data["originalBasename"] = src_collection.head[:-1] @@ -705,14 +735,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): else: # Single file - fname = files - if os.path.isabs(fname): - self.log.error( - "Filename in representation is filepath {}".format(fname) - ) - raise KnownPublishError( - "This is a bug. Representation file name is full path" - ) template_data["originalBasename"], _ = os.path.splitext(fname) # Manage anatomy template data template_data.pop("frame", None) From c1499f9b2309b8604228762e56f1b655ae7b1980 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 16:12:08 +0100 Subject: [PATCH 306/356] Add special handling of transfers preparation if 'originalBasename' is in template --- openpype/plugins/publish/integrate.py | 56 +++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7074410a84..5c3766afad 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -645,11 +645,59 @@ class IntegrateAsset(pyblish.api.InstancePlugin): is_sequence_representation = isinstance(files, (list, tuple)) self._validate_repre_files(files, is_sequence_representation) - if is_sequence_representation: - # Collection of files (sequence) - if any(os.path.isabs(fname) for fname in files): - raise KnownPublishError("Given file names contain full paths") + # Output variables of conditions below: + # - transfers (List[Tuple[str, str]]): src -> dst filepaths to copy + # - repre_context (Dict[str, Any]): context data used to fill template + # - template_data (Dict[str, Any]): source data used to fill template + # - to add required data to 'repre_context' not used for + # formatting + # - anatomy_filled (Dict[str, Any]): filled anatomy of last file + # - to fill 'publishDir' on instance.data -> not ideal + # Treat template with 'orignalBasename' in special way + if "{originalBasename}" in template: + # Remove 'frame' from template data + template_data.pop("frame", None) + + # Find out first frame string value + first_index_padded = None + if not is_udim and is_sequence_representation: + col = clique.assemble(files)[0][0] + sorted_frames = tuple(sorted(col.indexes)) + # First frame used for end value + first_frame = sorted_frames[0] + # Get last frame for padding + last_frame = sorted_frames[-1] + # Use padding from collection of length of last frame as string + padding = max(col.padding, len(str(last_frame))) + first_index_padded = get_frame_padded( + frame=first_frame, + padding=padding + ) + + # Convert files to list for single file as remaining part is only + # transfers creation (iteration over files) + if not is_sequence_representation: + files = [files] + + repre_context = None + transfers = [] + for src_file_name in files: + template_data["originalBasename"], _ = os.path.splitext( + src_file_name) + + anatomy_filled = anatomy.format(template_data) + dst = anatomy_filled[template_name]["path"] + src = os.path.join(stagingdir, src_file_name) + transfers.append((src, dst)) + if repre_context is None: + repre_context = dst.used_values + + if not is_udim and first_index_padded is not None: + repre_context["frame"] = first_index_padded + + elif is_sequence_representation: + # Collection of files (sequence) src_collections, remainders = clique.assemble(files) src_collection = src_collections[0] From 9b2e97fb74649f4fbcb54424adf8490ac4e23fa5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 16:12:31 +0100 Subject: [PATCH 307/356] don't handle originalBasename in other conditions --- openpype/plugins/publish/integrate.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 5c3766afad..334262fb63 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -701,7 +701,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): src_collections, remainders = clique.assemble(files) src_collection = src_collections[0] - template_data["originalBasename"] = src_collection.head[:-1] destination_indexes = list(src_collection.indexes) # Use last frame for minimum padding # - that should cover both 'udim' and 'frame' minimum padding @@ -723,11 +722,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # In case source are published in place we need to # skip renumbering repre_frame_start = repre.get("frameStart") - if ( - "originalBasename" not in template - and repre_frame_start is not None - ): - index_frame_start = int(repre["frameStart"]) + if repre_frame_start is not None: + index_frame_start = int(repre_frame_start) # Shift destination sequence to the start frame destination_indexes = [ index_frame_start + idx @@ -783,7 +779,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): else: # Single file - template_data["originalBasename"], _ = os.path.splitext(fname) # Manage anatomy template data template_data.pop("frame", None) if is_udim: @@ -795,7 +790,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): dst = os.path.normpath(template_filled) # Single file transfer - src = os.path.join(stagingdir, fname) + src = os.path.join(stagingdir, files) transfers = [(src, dst)] # todo: Are we sure the assumption each representation From a09999174d7ae8daa98141efcd2d06e1fa527aeb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Feb 2023 16:12:47 +0100 Subject: [PATCH 308/356] more explicit check for originalDirname --- 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 334262fb63..b117006871 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -624,7 +624,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): is_udim = bool(repre.get("udim")) # handle publish in place - if "originalDirname" in template: + if "{originalDirname}" in template: # store as originalDirname only original value without project root # if instance collected originalDirname is present, it should be # used for all represe From 75dfd6c3f6b2e37cf9013a692df5385f9af7bddc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 10 Feb 2023 15:19:56 +0000 Subject: [PATCH 309/356] Publish proxy representation. --- .../maya/plugins/load/load_arnold_standin.py | 6 ++-- .../publish/extract_arnold_scene_source.py | 32 ++++++------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 6e5fe16bcd..66e8b69639 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -107,9 +107,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_basename = ( basename_split[0] + "_proxy." + ".".join(basename_split[1:]) ) - proxy_path = "/".join( - [os.path.dirname(path), "resources", proxy_basename] - ) + proxy_path = "/".join([os.path.dirname(path), proxy_basename]) return proxy_basename, proxy_path def _setup_proxy(self, shape, path, namespace): @@ -136,7 +134,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): ) cmds.setAttr( string_replace_operator + ".match", - "resources/" + proxy_basename, + proxy_basename, type="string" ) cmds.setAttr( 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 153a1a513e..924ac58c40 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -1,5 +1,4 @@ import os -import copy from maya import cmds import arnold @@ -8,7 +7,6 @@ from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( maintained_selection, attribute_values, delete_after ) -from openpype.lib import StringTemplate class ExtractArnoldSceneSource(publish.Extractor): @@ -103,28 +101,16 @@ class ExtractArnoldSceneSource(publish.Extractor): instance.data["proxy"], attribute_data, kwargs ) - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({"ext": "ass"}) - templates = instance.context.data["anatomy"].templates["publish"] - published_filename_without_extension = StringTemplate( - templates["file"] - ).format(template_data).replace(".ass", "_proxy") - transfers = [] - for filename in filenames: - source = os.path.join(staging_dir, filename) - destination = os.path.join( - instance.data["resourcesDir"], - filename.replace( - filename.split(".")[0], - published_filename_without_extension - ) - ) - transfers.append((source, destination)) + representation = { + "name": "proxy", + "ext": "ass", + "files": filenames if len(filenames) > 1 else filenames[0], + "stagingDir": staging_dir, + "frameStart": kwargs["startFrame"], + "outputName": "proxy" + } - for source, destination in transfers: - self.log.debug("Transfer: {} > {}".format(source, destination)) - - instance.data["transfers"] = transfers + instance.data["representations"].append(representation) def _extract(self, nodes, attribute_data, kwargs): self.log.info("Writing: " + kwargs["filename"]) From 4c98fe735f7151dbd5ba9d5c59879d0cd47f886b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 11 Feb 2023 03:27:14 +0000 Subject: [PATCH 310/356] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 61339fb3dd..8dfd638414 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.4" +__version__ = "3.15.1-nightly.5" From f56e7bcbf879072c88fcf74944d41cf4366c4e79 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Feb 2023 10:51:21 +0100 Subject: [PATCH 311/356] removed deprecated functions from openpype lib --- openpype/lib/__init__.py | 43 -- openpype/lib/anatomy.py | 38 -- openpype/lib/avalon_context.py | 431 +----------------- openpype/lib/plugin_tools.py | 119 ----- .../tests/test_lib_restructuralization.py | 6 - openpype/tests/test_pyblish_filter.py | 6 +- 6 files changed, 7 insertions(+), 636 deletions(-) delete mode 100644 openpype/lib/anatomy.py diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index b5fb955a84..9eb7724a60 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -82,9 +82,6 @@ from .mongo import ( validate_mongo_connection, OpenPypeMongoConnection ) -from .anatomy import ( - Anatomy -) from .dateutils import ( get_datetime_data, @@ -119,36 +116,19 @@ from .transcoding import ( ) from .avalon_context import ( CURRENT_DOC_SCHEMAS, - PROJECT_NAME_ALLOWED_SYMBOLS, - PROJECT_NAME_REGEX, create_project, - is_latest, - any_outdated, - get_asset, - get_linked_assets, - get_latest_version, - get_system_general_anatomy_data, get_workfile_template_key, get_workfile_template_key_from_context, - get_workdir_data, - get_workdir, - get_workdir_with_workdir_data, get_last_workfile_with_version, get_last_workfile, - create_workfile_doc, - save_workfile_data_to_doc, - get_workfile_doc, - BuildWorkfile, get_creator_by_name, get_custom_workfile_template, - change_timer_to_current_context, - get_custom_workfile_template_by_context, get_custom_workfile_template_by_string_context, get_custom_workfile_template @@ -186,8 +166,6 @@ from .plugin_tools import ( get_subset_name, get_subset_name_with_asset_doc, prepare_template_data, - filter_pyblish_plugins, - set_plugin_attributes_from_settings, source_hash, ) @@ -278,34 +256,17 @@ __all__ = [ "convert_ffprobe_fps_to_float", "CURRENT_DOC_SCHEMAS", - "PROJECT_NAME_ALLOWED_SYMBOLS", - "PROJECT_NAME_REGEX", "create_project", - "is_latest", - "any_outdated", - "get_asset", - "get_linked_assets", - "get_latest_version", - "get_system_general_anatomy_data", "get_workfile_template_key", "get_workfile_template_key_from_context", - "get_workdir_data", - "get_workdir", - "get_workdir_with_workdir_data", "get_last_workfile_with_version", "get_last_workfile", - "create_workfile_doc", - "save_workfile_data_to_doc", - "get_workfile_doc", - "BuildWorkfile", "get_creator_by_name", - "change_timer_to_current_context", - "get_custom_workfile_template_by_context", "get_custom_workfile_template_by_string_context", "get_custom_workfile_template", @@ -338,8 +299,6 @@ __all__ = [ "TaskNotSetError", "get_subset_name", "get_subset_name_with_asset_doc", - "filter_pyblish_plugins", - "set_plugin_attributes_from_settings", "source_hash", "format_file_size", @@ -358,8 +317,6 @@ __all__ = [ "terminal", - "Anatomy", - "get_datetime_data", "get_formatted_current_time", diff --git a/openpype/lib/anatomy.py b/openpype/lib/anatomy.py deleted file mode 100644 index 6d339f058f..0000000000 --- a/openpype/lib/anatomy.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Code related to project Anatomy was moved -to 'openpype.pipeline.anatomy' please change your imports as soon as -possible. File will be probably removed in OpenPype 3.14.* -""" - -import warnings -import functools - - -class AnatomyDeprecatedWarning(DeprecationWarning): - pass - - -def anatomy_deprecated(func): - """Mark functions as deprecated. - - It will result in a warning being emitted when the function is used. - """ - - @functools.wraps(func) - def new_func(*args, **kwargs): - warnings.simplefilter("always", AnatomyDeprecatedWarning) - warnings.warn( - ( - "Deprecated import of 'Anatomy'." - " Class was moved to 'openpype.pipeline.anatomy'." - " Please change your imports of Anatomy in codebase." - ), - category=AnatomyDeprecatedWarning - ) - return func(*args, **kwargs) - return new_func - - -@anatomy_deprecated -def Anatomy(*args, **kwargs): - from openpype.pipeline.anatomy import Anatomy - return Anatomy(*args, **kwargs) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 12f4a5198b..a9ae27cb79 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1,6 +1,5 @@ """Should be used only inside of hosts.""" -import os -import copy + import platform import logging import functools @@ -10,17 +9,12 @@ import six from openpype.client import ( get_project, - get_assets, get_asset_by_name, - get_last_version_by_subset_name, - get_workfile_info, ) from openpype.client.operations import ( CURRENT_ASSET_DOC_SCHEMA, CURRENT_PROJECT_SCHEMA, CURRENT_PROJECT_CONFIG_SCHEMA, - PROJECT_NAME_ALLOWED_SYMBOLS, - PROJECT_NAME_REGEX, ) from .profiles_filtering import filter_profiles from .path_templates import StringTemplate @@ -128,70 +122,6 @@ def with_pipeline_io(func): return wrapped -@deprecated("openpype.pipeline.context_tools.is_representation_from_latest") -def is_latest(representation): - """Return whether the representation is from latest version - - Args: - representation (dict): The representation document from the database. - - Returns: - bool: Whether the representation is of latest version. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import is_representation_from_latest - - return is_representation_from_latest(representation) - - -@deprecated("openpype.pipeline.load.any_outdated_containers") -def any_outdated(): - """Return whether the current scene has any outdated content. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.load import any_outdated_containers - - return any_outdated_containers() - - -@deprecated("openpype.pipeline.context_tools.get_current_project_asset") -def get_asset(asset_name=None): - """ Returning asset document from database by its name. - - Doesn't count with duplicities on asset names! - - Args: - asset_name (str) - - Returns: - (MongoDB document) - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import get_current_project_asset - - return get_current_project_asset(asset_name=asset_name) - - -@deprecated("openpype.pipeline.template_data.get_general_template_data") -def get_system_general_anatomy_data(system_settings=None): - """ - Deprecated: - Function will be removed after release version 3.15.* - """ - from openpype.pipeline.template_data import get_general_template_data - - return get_general_template_data(system_settings) - - @deprecated("openpype.client.get_linked_asset_ids") def get_linked_asset_ids(asset_doc): """Return linked asset ids for `asset_doc` from DB @@ -214,66 +144,6 @@ def get_linked_asset_ids(asset_doc): return get_linked_asset_ids(project_name, asset_doc=asset_doc) -@deprecated("openpype.client.get_linked_assets") -def get_linked_assets(asset_doc): - """Return linked assets for `asset_doc` from DB - - Args: - asset_doc (dict): Asset document from DB - - Returns: - (list) Asset documents of input links for passed asset doc. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline import legacy_io - from openpype.client import get_linked_assets - - project_name = legacy_io.active_project() - - return get_linked_assets(project_name, asset_doc=asset_doc) - - -@deprecated("openpype.client.get_last_version_by_subset_name") -def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): - """Retrieve latest version from `asset_name`, and `subset_name`. - - Do not use if you want to query more than 5 latest versions as this method - query 3 times to mongo for each call. For those cases is better to use - more efficient way, e.g. with help of aggregations. - - Args: - asset_name (str): Name of asset. - subset_name (str): Name of subset. - dbcon (AvalonMongoDB, optional): Avalon Mongo connection with Session. - project_name (str, optional): Find latest version in specific project. - - Returns: - None: If asset, subset or version were not found. - dict: Last version document for entered. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - if not project_name: - if not dbcon: - from openpype.pipeline import legacy_io - - log.debug("Using `legacy_io` for query.") - dbcon = legacy_io - # Make sure is installed - dbcon.install() - - project_name = dbcon.active_project() - - return get_last_version_by_subset_name( - project_name, subset_name, asset_name=asset_name - ) - - @deprecated( "openpype.pipeline.workfile.get_workfile_template_key_from_context") def get_workfile_template_key_from_context( @@ -361,142 +231,6 @@ def get_workfile_template_key( ) -@deprecated("openpype.pipeline.template_data.get_template_data") -def get_workdir_data(project_doc, asset_doc, task_name, host_name): - """Prepare data for workdir template filling from entered information. - - Args: - project_doc (dict): Mongo document of project from MongoDB. - asset_doc (dict): Mongo document of asset from MongoDB. - task_name (str): Task name for which are workdir data preapred. - host_name (str): Host which is used to workdir. This is required - because workdir template may contain `{app}` key. - - Returns: - dict: Data prepared for filling workdir template. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.template_data import get_template_data - - return get_template_data( - project_doc, asset_doc, task_name, host_name - ) - - -@deprecated("openpype.pipeline.workfile.get_workdir_with_workdir_data") -def get_workdir_with_workdir_data( - workdir_data, anatomy=None, project_name=None, template_key=None -): - """Fill workdir path from entered data and project's anatomy. - - It is possible to pass only project's name instead of project's anatomy but - one of them **must** be entered. It is preferred to enter anatomy if is - available as initialization of a new Anatomy object may be time consuming. - - Args: - workdir_data (dict): Data to fill workdir template. - anatomy (Anatomy): Anatomy object for specific project. Optional if - `project_name` is entered. - project_name (str): Project's name. Optional if `anatomy` is entered - otherwise Anatomy object is created with using the project name. - template_key (str): Key of work templates in anatomy templates. If not - passed `get_workfile_template_key_from_context` is used to get it. - dbcon(AvalonMongoDB): Mongo connection. Required only if 'template_key' - and 'project_name' are not passed. - - Returns: - TemplateResult: Workdir path. - - Raises: - ValueError: When both `anatomy` and `project_name` are set to None. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - if not anatomy and not project_name: - raise ValueError(( - "Missing required arguments one of `project_name` or `anatomy`" - " must be entered." - )) - - if not project_name: - project_name = anatomy.project_name - - from openpype.pipeline.workfile import get_workdir_with_workdir_data - - return get_workdir_with_workdir_data( - workdir_data, project_name, anatomy, template_key - ) - - -@deprecated("openpype.pipeline.workfile.get_workdir_with_workdir_data") -def get_workdir( - project_doc, - asset_doc, - task_name, - host_name, - anatomy=None, - template_key=None -): - """Fill workdir path from entered data and project's anatomy. - - Args: - project_doc (dict): Mongo document of project from MongoDB. - asset_doc (dict): Mongo document of asset from MongoDB. - task_name (str): Task name for which are workdir data preapred. - host_name (str): Host which is used to workdir. This is required - because workdir template may contain `{app}` key. In `Session` - is stored under `AVALON_APP` key. - anatomy (Anatomy): Optional argument. Anatomy object is created using - project name from `project_doc`. It is preferred to pass this - argument as initialization of a new Anatomy object may be time - consuming. - template_key (str): Key of work templates in anatomy templates. Default - value is defined in `get_workdir_with_workdir_data`. - - Returns: - TemplateResult: Workdir path. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.workfile import get_workdir - # Output is TemplateResult object which contain useful data - return get_workdir( - project_doc, - asset_doc, - task_name, - host_name, - anatomy, - template_key - ) - - -@deprecated("openpype.pipeline.context_tools.get_template_data_from_session") -def template_data_from_session(session=None): - """ Return dictionary with template from session keys. - - Args: - session (dict, Optional): The Session to use. If not provided use the - currently active global Session. - - Returns: - dict: All available data from session. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.context_tools import get_template_data_from_session - - return get_template_data_from_session(session) - - @deprecated("openpype.pipeline.context_tools.compute_session_changes") def compute_session_changes( session, task=None, asset=None, app=None, template_key=None @@ -588,133 +322,6 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): return change_current_context(asset, task, template_key) -@deprecated("openpype.client.get_workfile_info") -def get_workfile_doc(asset_id, task_name, filename, dbcon=None): - """Return workfile document for entered context. - - Do not use this method to get more than one document. In that cases use - custom query as this will return documents from database one by one. - - Args: - asset_id (ObjectId): Mongo ID of an asset under which workfile belongs. - task_name (str): Name of task under which the workfile belongs. - filename (str): Name of a workfile. - dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and - `legacy_io` is used if not entered. - - Returns: - dict: Workfile document or None. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - project_name = dbcon.active_project() - return get_workfile_info(project_name, asset_id, task_name, filename) - - -@deprecated -def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): - """Creates or replace workfile document in mongo. - - Do not use this method to update data. This method will remove all - additional data from existing document. - - Args: - asset_doc (dict): Document of asset under which workfile belongs. - task_name (str): Name of task for which is workfile related to. - filename (str): Filename of workfile. - workdir (str): Path to directory where `filename` is located. - dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and - `legacy_io` is used if not entered. - """ - - from openpype.pipeline import Anatomy - from openpype.pipeline.template_data import get_template_data - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - # Filter of workfile document - doc_filter = { - "type": "workfile", - "parent": asset_doc["_id"], - "task_name": task_name, - "filename": filename - } - # Document data are copy of filter - doc_data = copy.deepcopy(doc_filter) - - # Prepare project for workdir data - project_name = dbcon.active_project() - project_doc = get_project(project_name) - workdir_data = get_template_data( - project_doc, asset_doc, task_name, dbcon.Session["AVALON_APP"] - ) - # Prepare anatomy - anatomy = Anatomy(project_name) - # Get workdir path (result is anatomy.TemplateResult) - template_workdir = get_workdir_with_workdir_data( - workdir_data, anatomy - ) - template_workdir_path = str(template_workdir).replace("\\", "/") - - # Replace slashses in workdir path where workfile is located - mod_workdir = workdir.replace("\\", "/") - - # Replace workdir from templates with rootless workdir - rootles_workdir = mod_workdir.replace( - template_workdir_path, - template_workdir.rootless.replace("\\", "/") - ) - - doc_data["schema"] = "pype:workfile-1.0" - doc_data["files"] = ["/".join([rootles_workdir, filename])] - doc_data["data"] = {} - - dbcon.replace_one( - doc_filter, - doc_data, - upsert=True - ) - - -@deprecated -def save_workfile_data_to_doc(workfile_doc, data, dbcon=None): - if not workfile_doc: - # TODO add log message - return - - if not data: - return - - # Use legacy_io if dbcon is not entered - if not dbcon: - from openpype.pipeline import legacy_io - dbcon = legacy_io - - # Convert data to mongo modification keys/values - # - this is naive implementation which does not expect nested - # dictionaries - set_data = {} - for key, value in data.items(): - new_key = "data.{}".format(key) - set_data[new_key] = value - - # Update workfile document with data - dbcon.update_one( - {"_id": workfile_doc["_id"]}, - {"$set": set_data} - ) - - @deprecated("openpype.pipeline.workfile.BuildWorkfile") def BuildWorkfile(): """Build workfile class was moved to workfile pipeline. @@ -747,38 +354,6 @@ def get_creator_by_name(creator_name, case_sensitive=False): return get_legacy_creator_by_name(creator_name, case_sensitive) -@deprecated -def change_timer_to_current_context(): - """Called after context change to change timers. - - Deprecated: - This method is specific for TimersManager module so please use the - functionality from there. Function will be removed after release - version 3.15.* - """ - - from openpype.pipeline import legacy_io - - webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") - if not webserver_url: - log.warning("Couldn't find webserver url") - return - - rest_api_url = "{}/timers_manager/start_timer".format(webserver_url) - try: - import requests - except Exception: - log.warning("Couldn't start timer") - return - data = { - "project_name": legacy_io.Session["AVALON_PROJECT"], - "asset_name": legacy_io.Session["AVALON_ASSET"], - "task_name": legacy_io.Session["AVALON_TASK"] - } - - requests.post(rest_api_url, json=data) - - def _get_task_context_data_for_anatomy( project_doc, asset_doc, task_name, anatomy=None ): @@ -800,6 +375,8 @@ def _get_task_context_data_for_anatomy( dict: With Anatomy context data. """ + from openpype.pipeline.template_data import get_general_template_data + if anatomy is None: from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) @@ -840,7 +417,7 @@ def _get_task_context_data_for_anatomy( } } - system_general_data = get_system_general_anatomy_data() + system_general_data = get_general_template_data() data.update(system_general_data) return data diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 1e157dfbfd..10fd3940b8 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -8,7 +8,6 @@ import warnings import functools from openpype.client import get_asset_by_id -from openpype.settings import get_project_settings log = logging.getLogger(__name__) @@ -101,8 +100,6 @@ def get_subset_name_with_asset_doc( is not passed. dynamic_data (dict): Dynamic data specific for a creator which creates instance. - dbcon (AvalonMongoDB): Mongo connection to be able query asset document - if 'asset_doc' is not passed. """ from openpype.pipeline.create import get_subset_name @@ -202,122 +199,6 @@ def prepare_template_data(fill_pairs): return fill_data -@deprecated("openpype.pipeline.publish.lib.filter_pyblish_plugins") -def filter_pyblish_plugins(plugins): - """Filter pyblish plugins by presets. - - This servers as plugin filter / modifier for pyblish. It will load plugin - definitions from presets and filter those needed to be excluded. - - Args: - plugins (dict): Dictionary of plugins produced by :mod:`pyblish-base` - `discover()` method. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - from openpype.pipeline.publish.lib import filter_pyblish_plugins - - filter_pyblish_plugins(plugins) - - -@deprecated -def set_plugin_attributes_from_settings( - plugins, superclass, host_name=None, project_name=None -): - """Change attribute values on Avalon plugins by project settings. - - This function should be used only in host context. Modify - behavior of plugins. - - Args: - plugins (list): Plugins discovered by origin avalon discover method. - superclass (object): Superclass of plugin type (e.g. Cretor, Loader). - host_name (str): Name of host for which plugins are loaded and from. - Value from environment `AVALON_APP` is used if not entered. - project_name (str): Name of project for which settings will be loaded. - Value from environment `AVALON_PROJECT` is used if not entered. - - Deprecated: - Function will be removed after release version 3.15.* - """ - - # Function is not used anymore - from openpype.pipeline import LegacyCreator, LoaderPlugin - - # determine host application to use for finding presets - if host_name is None: - host_name = os.environ.get("AVALON_APP") - - if project_name is None: - project_name = os.environ.get("AVALON_PROJECT") - - # map plugin superclass to preset json. Currently supported is load and - # create (LoaderPlugin and LegacyCreator) - plugin_type = None - if superclass is LoaderPlugin or issubclass(superclass, LoaderPlugin): - plugin_type = "load" - elif superclass is LegacyCreator or issubclass(superclass, LegacyCreator): - plugin_type = "create" - - if not host_name or not project_name or plugin_type is None: - msg = "Skipped attributes override from settings." - if not host_name: - msg += " Host name is not defined." - - if not project_name: - msg += " Project name is not defined." - - if plugin_type is None: - msg += " Plugin type is unsupported for class {}.".format( - superclass.__name__ - ) - - print(msg) - return - - print(">>> Finding presets for {}:{} ...".format(host_name, plugin_type)) - - project_settings = get_project_settings(project_name) - plugin_type_settings = ( - project_settings - .get(host_name, {}) - .get(plugin_type, {}) - ) - global_type_settings = ( - project_settings - .get("global", {}) - .get(plugin_type, {}) - ) - if not global_type_settings and not plugin_type_settings: - return - - for plugin in plugins: - plugin_name = plugin.__name__ - - plugin_settings = None - # Look for plugin settings in host specific settings - if plugin_name in plugin_type_settings: - plugin_settings = plugin_type_settings[plugin_name] - - # Look for plugin settings in global settings - elif plugin_name in global_type_settings: - plugin_settings = global_type_settings[plugin_name] - - if not plugin_settings: - continue - - print(">>> We have preset for {}".format(plugin_name)) - for option, value in plugin_settings.items(): - if option == "enabled" and value is False: - setattr(plugin, "active", False) - print(" - is disabled by preset") - else: - setattr(plugin, option, value) - print(" - setting `{}`: `{}`".format(option, value)) - - def source_hash(filepath, *args): """Generate simple identifier for a source file. This is used to identify whether a source file has previously been diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py index c8952e5a1c..669706d470 100644 --- a/openpype/tests/test_lib_restructuralization.py +++ b/openpype/tests/test_lib_restructuralization.py @@ -5,11 +5,9 @@ def test_backward_compatibility(printer): printer("Test if imports still work") try: - from openpype.lib import filter_pyblish_plugins from openpype.lib import execute_hook from openpype.lib import PypeHook - from openpype.lib import get_latest_version from openpype.lib import ApplicationLaunchFailed from openpype.lib import get_ffmpeg_tool_path @@ -18,10 +16,6 @@ def test_backward_compatibility(printer): from openpype.lib import get_version_from_path from openpype.lib import version_up - from openpype.lib import is_latest - from openpype.lib import any_outdated - from openpype.lib import get_asset - from openpype.lib import get_linked_assets from openpype.lib import get_ffprobe_streams from openpype.hosts.fusion.lib import switch_item diff --git a/openpype/tests/test_pyblish_filter.py b/openpype/tests/test_pyblish_filter.py index ea23da26e4..b74784145f 100644 --- a/openpype/tests/test_pyblish_filter.py +++ b/openpype/tests/test_pyblish_filter.py @@ -1,9 +1,9 @@ -from . import lib +import os import pyblish.api import pyblish.util import pyblish.plugin -from openpype.lib import filter_pyblish_plugins -import os +from openpype.pipeline.publish.lib import filter_pyblish_plugins +from . import lib def test_pyblish_plugin_filter_modifier(printer, monkeypatch): From 09dff1629d734e4172cebc4ec9d1134ff36d0f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:36 +0100 Subject: [PATCH 312/356] Update openpype/pipeline/tempdir.py 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> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 6a346f3342..7e1778539c 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -11,7 +11,7 @@ def create_custom_tempdir(project_name, anatomy=None): """ Create custom tempdir Template path formatting is supporting: - - optional key formating + - optional key formatting - available keys: - root[work | ] - project[name | code] From 8daa8059ccede7693a01a869810acfe0c0fd0cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:45 +0100 Subject: [PATCH 313/356] Update openpype/pipeline/tempdir.py 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> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 7e1778539c..f26f988557 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -21,7 +21,7 @@ def create_custom_tempdir(project_name, anatomy=None): anatomy (openpype.pipeline.Anatomy)[optional]: Anatomy object Returns: - str | None: formated path or None + str | None: formatted path or None """ openpype_tempdir = os.getenv("OPENPYPE_TMPDIR") if not openpype_tempdir: From 198050959a4835b436d3fc7e7529f341ed870560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:42:54 +0100 Subject: [PATCH 314/356] Update openpype/pipeline/tempdir.py 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> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index f26f988557..4bb62f0afa 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -47,7 +47,7 @@ def create_custom_tempdir(project_name, anatomy=None): # path is absolute custom_tempdir = openpype_tempdir - # create he dir path if it doesnt exists + # create the dir path if it doesn't exists if not os.path.exists(custom_tempdir): try: # create it if it doesnt exists From 053a903662b026837f43308e26df18d049589df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:21 +0100 Subject: [PATCH 315/356] Update openpype/pipeline/tempdir.py 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> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 4bb62f0afa..88f8296dcf 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -53,6 +53,6 @@ def create_custom_tempdir(project_name, anatomy=None): # create it if it doesnt exists os.makedirs(custom_tempdir) except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) + raise IOError("Path couldn't be created: {}".format(error)) from error return custom_tempdir From 7ea78fee7b0a7b59fdfbe830ea83d66f399350b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:30 +0100 Subject: [PATCH 316/356] Update openpype/pipeline/tempdir.py 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> --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 88f8296dcf..3f9384a7fd 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -50,7 +50,7 @@ def create_custom_tempdir(project_name, anatomy=None): # create the dir path if it doesn't exists if not os.path.exists(custom_tempdir): try: - # create it if it doesnt exists + # create it if it doesn't exists os.makedirs(custom_tempdir) except IOError as error: raise IOError("Path couldn't be created: {}".format(error)) from error From 859863129a033bcde335f4b3af441aecd991716b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:43:39 +0100 Subject: [PATCH 317/356] Update openpype/pipeline/publish/lib.py 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> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index cc7f5678f5..2b0d111412 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -620,7 +620,7 @@ def get_instance_staging_dir(instance): It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. - Template formating is supported also with optional keys. Folder is + Template formatting is supported also with optional keys. Folder is created in case it doesnt exists. Available anatomy formatting keys: From 1735d6cc74d75ae732fcd1b0d7832ccd8d89fb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:00 +0100 Subject: [PATCH 318/356] Update openpype/pipeline/publish/lib.py 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> --- openpype/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 2b0d111412..27ab523352 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -628,7 +628,7 @@ def get_instance_staging_dir(instance): - project[name | code] Note: - Staging dir does not have to be necessarily in tempdir so be carefull + Staging dir does not have to be necessarily in tempdir so be careful about it's usage. Args: From abe803234ea73ebad6fa12b22932689daa527a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:12 +0100 Subject: [PATCH 319/356] Update website/docs/admin_environment.md 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> --- website/docs/admin_environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/admin_environment.md b/website/docs/admin_environment.md index 2cc558b530..1eb755b90b 100644 --- a/website/docs/admin_environment.md +++ b/website/docs/admin_environment.md @@ -9,8 +9,8 @@ import TabItem from '@theme/TabItem'; ## OPENPYPE_TMPDIR: - Custom staging dir directory - - Supports anatomy keys formating. ex `{root[work]}/{project[name]}/temp` - - supported formating keys: + - Supports anatomy keys formatting. ex `{root[work]}/{project[name]}/temp` + - supported formatting keys: - root[work] - project[name | code] From 3885f3cd7c502d04d0ec801cf62e4c047e2a2d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 13 Feb 2023 12:45:29 +0100 Subject: [PATCH 320/356] Update website/docs/admin_settings_system.md 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> --- website/docs/admin_settings_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index c39cac61f5..d61713ccd5 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -176,4 +176,4 @@ In the image before you can see that we set most of the environment variables in In this example MTOA will automatically will the `MAYA_VERSION`(which is set by Maya Application environment) and `MTOA_VERSION` into the `MTOA` variable. We then use the `MTOA` to set all the other variables needed for it to function within Maya. ![tools](assets/settings/tools_01.png) -All of the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. +All the tools defined in here can then be assigned to projects. You can also change the tools versions on any project level all the way down to individual asset or shot overrides. So if you just need to upgrade you render plugin for a single shot, while not risking the incompatibilities on the rest of the project, it is possible. From 9591d42b84aed7c3321cff5b01b718053593f58d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Feb 2023 12:51:51 +0100 Subject: [PATCH 321/356] spell errors --- openpype/pipeline/publish/lib.py | 6 +++--- openpype/pipeline/tempdir.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 27ab523352..d0a9396a42 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -601,7 +601,7 @@ def context_plugin_should_run(plugin, context): Args: plugin (pyblish.api.Plugin): Plugin with filters. - context (pyblish.api.Context): Pyblish context with insances. + context (pyblish.api.Context): Pyblish context with instances. Returns: bool: Context plugin should run based on valid instances. @@ -621,7 +621,7 @@ def get_instance_staging_dir(instance): It also supports `OPENPYPE_TMPDIR`, so studio can define own temp shared repository per project or even per more granular context. Template formatting is supported also with optional keys. Folder is - created in case it doesnt exists. + created in case it doesn't exists. Available anatomy formatting keys: - root[work | ] @@ -629,7 +629,7 @@ def get_instance_staging_dir(instance): Note: Staging dir does not have to be necessarily in tempdir so be careful - about it's usage. + about its usage. Args: instance (pyblish.lib.Instance): Instance for which we want to get diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 3f9384a7fd..3216c596da 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -53,6 +53,7 @@ def create_custom_tempdir(project_name, anatomy=None): # create it if it doesn't exists os.makedirs(custom_tempdir) except IOError as error: - raise IOError("Path couldn't be created: {}".format(error)) from error + raise IOError( + "Path couldn't be created: {}".format(error)) from error return custom_tempdir From 75637cc1a46eca825fee99346424828fe41a83fc Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 07:00:21 +0000 Subject: [PATCH 322/356] Strict Error Checking Default Provide default of strict error checking for instances created prior to PR. --- openpype/hosts/maya/plugins/publish/collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index fc297ef612..5bc295a56f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -320,7 +320,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ), "strict_error_checking": render_instance.data.get( - "strict_error_checking") + "strict_error_checking", False + ) } # Collect Deadline url if Deadline module is enabled From d3cc8b59c5add4269908947f5fdad108ec2ade30 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Feb 2023 10:42:54 +0100 Subject: [PATCH 323/356] replaced call to mongo 'dbcon.parenthood' with 'get_representation_parents' function --- openpype/pipeline/load/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index e30923f922..fefdb8537b 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -28,7 +28,6 @@ from openpype.lib import ( TemplateUnsolved, ) from openpype.pipeline import ( - schema, legacy_io, Anatomy, ) @@ -643,7 +642,10 @@ def get_representation_path(representation, root=None, dbcon=None): def path_from_config(): try: - version_, subset, asset, project = dbcon.parenthood(representation) + project_name = dbcon.active_project() + version_, subset, asset, project = get_representation_parents( + project_name, representation + ) except ValueError: log.debug( "Representation %s wasn't found in database, " From 33a7ecd19eacfe237cc7ea68513ff17d692e6a77 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 10:03:01 +0000 Subject: [PATCH 324/356] Code cosmetics --- openpype/hosts/maya/plugins/publish/collect_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 5bc295a56f..aa35f687ca 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,7 +42,6 @@ Provides: import re import os import platform -import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup From abe9a2b8951414327c2df4718085dec5ccd20485 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 10:03:12 +0000 Subject: [PATCH 325/356] Default should be True --- openpype/hosts/maya/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index aa35f687ca..f2b5262187 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -319,7 +319,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights" ), "strict_error_checking": render_instance.data.get( - "strict_error_checking", False + "strict_error_checking", True ) } From 6bdbdd4337d7d268667cf08f3ef784a8e306184f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 14 Feb 2023 15:32:54 +0000 Subject: [PATCH 326/356] Update openpype/hosts/maya/plugins/load/load_arnold_standin.py 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/maya/plugins/load/load_arnold_standin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 66e8b69639..ab69d62ef5 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -65,20 +65,20 @@ class ArnoldStandinLoader(load.LoaderPlugin): # Create transform with shape transform_name = label + "_standin" - standinShape = mtoa.ui.arnoldmenu.createStandIn() - standin = cmds.listRelatives(standinShape, parent=True)[0] + standin_shape = mtoa.ui.arnoldmenu.createStandIn() + standin = cmds.listRelatives(standin_shape, parent=True)[0] standin = cmds.rename(standin, transform_name) - standinShape = cmds.listRelatives(standin, shapes=True)[0] + standin_shape = cmds.listRelatives(standin, shapes=True)[0] cmds.parent(standin, root) # Set the standin filepath path, operator = self._setup_proxy( - standinShape, self.fname, namespace + standin_shape, self.fname, namespace ) - cmds.setAttr(standinShape + ".dso", path, type="string") + cmds.setAttr(standin_shape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) - cmds.setAttr(standinShape + ".useFrameExtension", sequence) + cmds.setAttr(standin_shape + ".useFrameExtension", sequence) nodes = [root, standin] if operator is not None: From 5903dbce9e176d26d45681a86efe92f607a512e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Feb 2023 17:03:05 +0100 Subject: [PATCH 327/356] autofill precreate attributes if are not passed --- openpype/pipeline/create/context.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index ba566f93d4..1567acdb79 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -17,6 +17,7 @@ from openpype.lib.attribute_definitions import ( UnknownDef, serialize_attr_defs, deserialize_attr_defs, + get_default_values, ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io @@ -1866,6 +1867,13 @@ class CreateContext: if pre_create_data is None: pre_create_data = {} + precreate_attr_defs = creator.get_pre_create_attr_defs() or [] + # Create default values of precreate data + _pre_create_data = get_default_values(precreate_attr_defs) + # Update passed precreate data to default values + # TODO validate types + _pre_create_data.update(pre_create_data) + subset_name = creator.get_subset_name( variant, task_name, @@ -1881,7 +1889,7 @@ class CreateContext: return creator.create( subset_name, instance_data, - pre_create_data + _pre_create_data ) def _create_with_unified_error( From 8ddcc9c151aea67ae893ee1518b70731fa776deb Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 15 Feb 2023 03:29:31 +0000 Subject: [PATCH 328/356] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 8dfd638414..6d060656cb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.1-nightly.5" +__version__ = "3.15.1-nightly.6" From ac4078259200edbdf88d58954b1e96e09468e5b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 10:14:19 +0100 Subject: [PATCH 329/356] fix used constant 'ActiveWindow' -> 'WindowActive' --- openpype/tools/publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 097e289f32..a82f60d5a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -366,7 +366,7 @@ class PublisherWindow(QtWidgets.QDialog): def make_sure_is_visible(self): if self._window_is_visible: - self.setWindowState(QtCore.Qt.ActiveWindow) + self.setWindowState(QtCore.Qt.WindowActive) else: self.show() From 3e6a120eaa808bf69e0bdbe893b0dd8c21c6939a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 11:46:20 +0100 Subject: [PATCH 330/356] fix default settings of nuke --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index cd8ea02272..2ec2028219 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -1,7 +1,6 @@ { "general": { "menu": { - "create": "ctrl+alt+c", "publish": "ctrl+alt+p", "load": "ctrl+alt+l", "manage": "ctrl+alt+m", @@ -246,6 +245,7 @@ "sourcetype": "python", "title": "Gizmo Note", "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", + "icon": "", "shortcut": "" } ] From df532268a2e05b6b48074336453ab3e18b86e08f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:00:15 +0100 Subject: [PATCH 331/356] add family to instance data --- openpype/pipeline/create/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 1567acdb79..79c9805604 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1884,6 +1884,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, + "family": self.family, "variant": variant } return creator.create( From fb93780640ed5de588cb11499ccd68d1f6a91d75 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:08:45 +0100 Subject: [PATCH 332/356] use family form creator --- openpype/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 79c9805604..89eec52676 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1884,7 +1884,7 @@ class CreateContext: instance_data = { "asset": asset_doc["name"], "task": task_name, - "family": self.family, + "family": creator.family, "variant": variant } return creator.create( From 222b39f024e5fcf8890145f08eba8298282ddb39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Feb 2023 12:22:13 +0100 Subject: [PATCH 333/356] nuke: adding back Create shortcut it was removed accidentally --- openpype/settings/defaults/project_settings/nuke.json | 3 ++- .../schemas/projects_schema/schema_project_nuke.json | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 2ec2028219..d475c337d9 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -1,6 +1,7 @@ { "general": { "menu": { + "create": "ctrl+alt+c", "publish": "ctrl+alt+p", "load": "ctrl+alt+l", "manage": "ctrl+alt+m", @@ -532,4 +533,4 @@ "profiles": [] }, "filters": {} -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index b1a8cc1812..26c64e6219 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -17,6 +17,11 @@ "key": "menu", "label": "OpenPype Menu shortcuts", "children": [ + { + "type": "text", + "key": "create", + "label": "Create..." + }, { "type": "text", "key": "publish", @@ -288,4 +293,4 @@ "name": "schema_publish_gui_filter" } ] -} \ No newline at end of file +} From e93c5d0d4055db7e2ff8cd7067fda24ccc243250 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:26:27 +0100 Subject: [PATCH 334/356] OP-4928 - fix wrong usage of legacy_io Import was removed, but usage stayed. Now it should be replaced from context --- openpype/hosts/photoshop/plugins/create/create_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py index cdea82cb05..3d82d6b6f0 100644 --- a/openpype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -193,7 +193,7 @@ class ImageCreator(Creator): instance_data.pop("uuid") if not instance_data.get("task"): - instance_data["task"] = legacy_io.Session.get("AVALON_TASK") + instance_data["task"] = self.create_context.get_current_task_name() if not instance_data.get("variant"): instance_data["variant"] = '' From 423f2bcbdadc723d6499f8f92a9ed391533a75e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Feb 2023 12:26:50 +0100 Subject: [PATCH 335/356] removing python3 only code --- openpype/pipeline/tempdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/tempdir.py b/openpype/pipeline/tempdir.py index 3216c596da..55a1346b08 100644 --- a/openpype/pipeline/tempdir.py +++ b/openpype/pipeline/tempdir.py @@ -54,6 +54,6 @@ def create_custom_tempdir(project_name, anatomy=None): os.makedirs(custom_tempdir) except IOError as error: raise IOError( - "Path couldn't be created: {}".format(error)) from error + "Path couldn't be created: {}".format(error)) return custom_tempdir From 410ea87e18a582628fbd456549207e2dac2ef164 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:27:44 +0100 Subject: [PATCH 336/356] OP-4928 - fix wrong usage of legacy_io Import should be removed. Now it should be replaced from context. --- openpype/hosts/aftereffects/plugins/create/create_render.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 10ded8b912..02f045b0ec 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -6,8 +6,7 @@ from openpype.hosts.aftereffects import api from openpype.pipeline import ( Creator, CreatedInstance, - CreatorError, - legacy_io, + CreatorError ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances from openpype.lib import prepare_template_data @@ -195,7 +194,7 @@ class RenderCreator(Creator): instance_data.pop("uuid") if not instance_data.get("task"): - instance_data["task"] = legacy_io.Session.get("AVALON_TASK") + instance_data["task"] = self.create_context.get_current_task_name() if not instance_data.get("creator_attributes"): is_old_farm = instance_data["family"] != "renderLocal" From eb5d1e3816b07760c6ffdc8c71999fe1167dfdf9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 12:29:52 +0100 Subject: [PATCH 337/356] resave to remove empty line --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index d475c337d9..2999d1427d 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -533,4 +533,4 @@ "profiles": [] }, "filters": {} -} +} \ No newline at end of file From 66c42dde73174c8a3b288419a616f8c23b98064a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:32:41 +0100 Subject: [PATCH 338/356] OP-4928 - removed legacy_io in workfile creator in PS Legacy_io should be eradicated, replaced by abstracted methods --- .../photoshop/plugins/create/workfile_creator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index 8ee9a0d832..f5d56adcbc 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -2,8 +2,7 @@ import openpype.hosts.photoshop.api as api from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, - CreatedInstance, - legacy_io + CreatedInstance ) from openpype.hosts.photoshop.api.pipeline import cache_and_get_instances @@ -38,10 +37,11 @@ class PSWorkfileCreator(AutoCreator): existing_instance = instance break - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] + context = self.create_context + project_name = context.get_current_project_name() + asset_name = context.get_current_asset_name() + task_name = context.get_current_task_name() + host_name = context.host_name if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( From 03013095023cdce494142740a70efdbce60cb03c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 12:33:35 +0100 Subject: [PATCH 339/356] OP-4928 - removed legacy_io in workfile creator in AE Legacy_io should be eradicated, replaced by abstracted methods --- .../aftereffects/plugins/create/workfile_creator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index c698af896b..2e7b9d4a7e 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -2,8 +2,7 @@ import openpype.hosts.aftereffects.api as api from openpype.client import get_asset_by_name from openpype.pipeline import ( AutoCreator, - CreatedInstance, - legacy_io, + CreatedInstance ) from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances @@ -38,10 +37,11 @@ class AEWorkfileCreator(AutoCreator): existing_instance = instance break - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - host_name = legacy_io.Session["AVALON_APP"] + context = self.create_context + project_name = context.get_current_project_name() + asset_name = context.get_current_asset_name() + task_name = context.get_current_task_name() + host_name = context.host_name if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) From 6ab581df7da24302158d32c9a68a9baca33b1cb3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:45:22 +0100 Subject: [PATCH 340/356] on first reset always go to create tab --- openpype/tools/publisher/window.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 097e289f32..5ef25c9f8c 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -647,10 +647,7 @@ class PublisherWindow(QtWidgets.QDialog): # otherwise 'create' is used # - this happens only on first show if first_reset: - if self._overview_widget.has_items(): - self._go_to_publish_tab() - else: - self._go_to_create_tab() + self._go_to_create_tab() elif ( not self._is_on_create_tab() From 1cc9a7a90fd6deef343b45f0944bcefed6497521 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:45:45 +0100 Subject: [PATCH 341/356] change tab on reset only if is on report tab (Details for user) --- openpype/tools/publisher/window.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5ef25c9f8c..ef9c99d998 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -649,11 +649,8 @@ class PublisherWindow(QtWidgets.QDialog): if first_reset: self._go_to_create_tab() - elif ( - not self._is_on_create_tab() - and not self._is_on_publish_tab() - ): - # If current tab is not 'Create' or 'Publish' go to 'Publish' + elif self._is_on_report_tab(): + # Go to 'Publish' tab if is on 'Details' tab # - this can happen when publishing started and was reset # at that moment it doesn't make sense to stay at publish # specific tabs. From 37a7841db8024341cbc4fa0c7881c6925ab7a188 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Feb 2023 15:46:02 +0100 Subject: [PATCH 342/356] reordered methods to match order of tabs in UI --- openpype/tools/publisher/window.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index ef9c99d998..86eed31afd 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -566,24 +566,24 @@ class PublisherWindow(QtWidgets.QDialog): def _go_to_publish_tab(self): self._set_current_tab("publish") - def _go_to_details_tab(self): - self._set_current_tab("details") - def _go_to_report_tab(self): self._set_current_tab("report") + def _go_to_details_tab(self): + self._set_current_tab("details") + def _is_on_create_tab(self): return self._is_current_tab("create") def _is_on_publish_tab(self): return self._is_current_tab("publish") - def _is_on_details_tab(self): - return self._is_current_tab("details") - def _is_on_report_tab(self): return self._is_current_tab("report") + def _is_on_details_tab(self): + return self._is_current_tab("details") + def _set_publish_overlay_visibility(self, visible): if visible: widget = self._publish_overlay From b70c6e4bfd433c1470efa1fde319834ec7068264 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 18:32:56 +0100 Subject: [PATCH 343/356] OP-4938 - fix obsolete access to instance change --- openpype/hosts/aftereffects/plugins/create/create_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 02f045b0ec..c20b0ec51b 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -126,7 +126,7 @@ class RenderCreator(Creator): subset_change = _changes.get("subset") if subset_change: api.get_stub().rename_item(created_inst.data["members"][0], - subset_change[1]) + subset_change.new_value) def remove_instances(self, instances): for instance in instances: From eef8990101eef7e79ad34b7b8429c164b93e14c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:52:39 +0100 Subject: [PATCH 344/356] public 'discover' function can expect all possible arguments --- openpype/pipeline/plugin_discover.py | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/plugin_discover.py b/openpype/pipeline/plugin_discover.py index 7edd9ac290..e5257b801a 100644 --- a/openpype/pipeline/plugin_discover.py +++ b/openpype/pipeline/plugin_discover.py @@ -135,11 +135,12 @@ class PluginDiscoverContext(object): allow_duplicates (bool): Validate class name duplications. ignore_classes (list): List of classes that will be ignored and not added to result. + return_report (bool): Output will be full report if set to 'True'. Returns: - DiscoverResult: Object holding succesfully discovered plugins, - ignored plugins, plugins with missing abstract implementation - and duplicated plugin. + Union[DiscoverResult, list[Any]]: Object holding successfully + discovered plugins, ignored plugins, plugins with missing + abstract implementation and duplicated plugin. """ if not ignore_classes: @@ -268,9 +269,34 @@ class _GlobalDiscover: return cls._context -def discover(superclass, allow_duplicates=True): +def discover( + superclass, + allow_duplicates=True, + ignore_classes=None, + return_report=False +): + """Find and return subclasses of `superclass` + + Args: + superclass (type): Class which determines discovered subclasses. + allow_duplicates (bool): Validate class name duplications. + ignore_classes (list): List of classes that will be ignored + and not added to result. + return_report (bool): Output will be full report if set to 'True'. + + Returns: + Union[DiscoverResult, list[Any]]: Object holding successfully + discovered plugins, ignored plugins, plugins with missing + abstract implementation and duplicated plugin. + """ + context = _GlobalDiscover.get_context() - return context.discover(superclass, allow_duplicates) + return context.discover( + superclass, + allow_duplicates, + ignore_classes, + return_report + ) def get_last_discovered_plugins(superclass): From 542405775a36c08075dc118dc0801be312d0e5e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:53:15 +0100 Subject: [PATCH 345/356] discover creators and convertors can pass other arguments to 'discover' function --- openpype/pipeline/create/creator_plugins.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 53acb618ed..74e6cb289a 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -605,12 +605,12 @@ class AutoCreator(BaseCreator): pass -def discover_creator_plugins(): - return discover(BaseCreator) +def discover_creator_plugins(*args, **kwargs): + return discover(BaseCreator, *args, **kwargs) -def discover_convertor_plugins(): - return discover(SubsetConvertorPlugin) +def discover_convertor_plugins(*args, **kwargs): + return discover(SubsetConvertorPlugin, *args, **kwargs) def discover_legacy_creator_plugins(): From 86a9c77c1e970a78d08888af5326c19c0fd51aa3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:53:47 +0100 Subject: [PATCH 346/356] reuse 'DiscoverResult' from plugin discover --- openpype/pipeline/create/context.py | 4 ++-- openpype/pipeline/publish/__init__.py | 2 -- openpype/pipeline/publish/lib.py | 23 +---------------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 89eec52676..8b5da74bc7 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -21,6 +21,7 @@ from openpype.lib.attribute_definitions import ( ) from openpype.host import IPublishHost from openpype.pipeline import legacy_io +from openpype.pipeline.plugin_discover import DiscoverResult from .creator_plugins import ( Creator, @@ -1620,8 +1621,7 @@ class CreateContext: from openpype.pipeline import OpenPypePyblishPluginMixin from openpype.pipeline.publish import ( - publish_plugins_discover, - DiscoverResult + publish_plugins_discover ) # Reset publish plugins diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index dc6fc0f97a..86f3bde0dc 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -25,7 +25,6 @@ from .publish_plugins import ( from .lib import ( get_publish_template_name, - DiscoverResult, publish_plugins_discover, load_help_content_from_plugin, load_help_content_from_filepath, @@ -68,7 +67,6 @@ __all__ = ( "get_publish_template_name", - "DiscoverResult", "publish_plugins_discover", "load_help_content_from_plugin", "load_help_content_from_filepath", diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index d0a9396a42..50623e5110 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -21,6 +21,7 @@ from openpype.settings import ( from openpype.pipeline import ( tempdir ) +from openpype.pipeline.plugin_discover import DiscoverResult from .contants import ( DEFAULT_PUBLISH_TEMPLATE, @@ -202,28 +203,6 @@ def get_publish_template_name( return template or default_template -class DiscoverResult: - """Hold result of publish plugins discovery. - - Stores discovered plugins duplicated plugins and file paths which - crashed on execution of file. - """ - def __init__(self): - self.plugins = [] - self.crashed_file_paths = {} - self.duplicated_plugins = [] - - def __iter__(self): - for plugin in self.plugins: - yield plugin - - def __getitem__(self, item): - return self.plugins[item] - - def __setitem__(self, item, value): - self.plugins[item] = value - - class HelpContent: def __init__(self, title, description, detail=None): self.title = title From b3a86bdbf540c8e8f0df6c52cb0873f2b84b513b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:54:18 +0100 Subject: [PATCH 347/356] store reports of discovered plugins --- openpype/pipeline/create/context.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 8b5da74bc7..5f1371befa 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1379,6 +1379,8 @@ class CreateContext: # Instances by their ID self._instances_by_id = {} + self.creator_discover_result = None + self.convertor_discover_result = None # Discovered creators self.creators = {} # Prepare categories of creators @@ -1666,7 +1668,9 @@ class CreateContext: creators = {} autocreators = {} manual_creators = {} - for creator_class in discover_creator_plugins(): + report = discover_creator_plugins(return_report=True) + self.creator_discover_result = report + for creator_class in report.plugins: if inspect.isabstract(creator_class): self.log.info( "Skipping abstract Creator {}".format(str(creator_class)) @@ -1711,7 +1715,9 @@ class CreateContext: def _reset_convertor_plugins(self): convertors_plugins = {} - for convertor_class in discover_convertor_plugins(): + report = discover_convertor_plugins(return_report=True) + self.convertor_discover_result = report + for convertor_class in report.plugins: if inspect.isabstract(convertor_class): self.log.info( "Skipping abstract Creator {}".format(str(convertor_class)) From 0dc73617a65dfc947c3c2715a6948928403c9f54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:54:44 +0100 Subject: [PATCH 348/356] use reports to store crashed files to publish report --- openpype/tools/publisher/control.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9ab37f2a3e..023a20ca5e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -169,6 +169,8 @@ class PublishReport: def __init__(self, controller): self.controller = controller + self._create_discover_result = None + self._convert_discover_result = None self._publish_discover_result = None self._plugin_data = [] self._plugin_data_with_plugin = [] @@ -181,6 +183,10 @@ class PublishReport: def reset(self, context, create_context): """Reset report and clear all data.""" + self._create_discover_result = create_context.creator_discover_result + self._convert_discover_result = ( + create_context.convertor_discover_result + ) self._publish_discover_result = create_context.publish_discover_result self._plugin_data = [] self._plugin_data_with_plugin = [] @@ -293,9 +299,19 @@ class PublishReport: if plugin not in self._stored_plugins: plugins_data.append(self._create_plugin_data_item(plugin)) - crashed_file_paths = {} + reports = [] + if self._create_discover_result is not None: + reports.append(self._create_discover_result) + + if self._convert_discover_result is not None: + reports.append(self._convert_discover_result) + if self._publish_discover_result is not None: - items = self._publish_discover_result.crashed_file_paths.items() + reports.append(self._publish_discover_result) + + crashed_file_paths = {} + for report in reports: + items = report.crashed_file_paths.items() for filepath, exc_info in items: crashed_file_paths[filepath] = "".join( traceback.format_exception(*exc_info) From 56470c47e23bcb337b48731252439c22e0300130 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 09:55:32 +0100 Subject: [PATCH 349/356] added Args to documentation --- openpype/pipeline/create/creator_plugins.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 74e6cb289a..628245faf2 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -153,6 +153,12 @@ class BaseCreator: Single object should be used for multiple instances instead of single instance per one creator object. Do not store temp data or mid-process data to `self` if it's not Plugin specific. + + Args: + project_settings (Dict[str, Any]): Project settings. + system_settings (Dict[str, Any]): System settings. + create_context (CreateContext): Context which initialized creator. + headless (bool): Running in headless mode. """ # Label shown in UI From 7739c3a54f0b12e106d05f8742dfa63266bbde7f Mon Sep 17 00:00:00 2001 From: mre7a <68907585+mre7a@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:43:13 +0100 Subject: [PATCH 350/356] Update openpype/hosts/maya/api/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 56a53c070c..1e6094e996 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -2,7 +2,7 @@ import json from maya import cmds -from openpype.pipeline import registered_host, legacy_io +from openpype.pipeline import registered_host, get_current_asset_name from openpype.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, AbstractTemplateBuilder, From 035c888d9be86d546c49569b370dbe1264844fa7 Mon Sep 17 00:00:00 2001 From: mre7a <68907585+mre7a@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:43:55 +0100 Subject: [PATCH 351/356] Update openpype/hosts/maya/api/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/maya/api/workfile_template_builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 1e6094e996..094f45221c 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -50,6 +50,7 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): return True # update imported sets information + asset_name = get_current_asset_name() for node in imported_sets: if not cmds.attributeQuery("id", node=node, exists=True): continue @@ -57,9 +58,9 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): continue if not cmds.attributeQuery("asset", node=node, exists=True): continue - asset = legacy_io.Session["AVALON_ASSET"] - cmds.setAttr("{}.asset".format(node), asset, type="string") + cmds.setAttr( + "{}.asset".format(node), asset_name, type="string") return True From c2b2dbb3f32aec8041b6cf6b1da08b3968d330ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 10:47:24 +0100 Subject: [PATCH 352/356] fix compatibility of QAction in Publisher --- openpype/tools/publisher/widgets/widgets.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 587bcb059d..8da3886419 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -250,21 +250,25 @@ class PublishReportBtn(PublishIconBtn): self._actions = [] def add_action(self, label, identifier): - action = QtWidgets.QAction(label) - action.setData(identifier) - action.triggered.connect( - functools.partial(self._on_action_trigger, action) + self._actions.append( + (label, identifier) ) - self._actions.append(action) - def _on_action_trigger(self, action): - identifier = action.data() + def _on_action_trigger(self, identifier): self.triggered.emit(identifier) def mouseReleaseEvent(self, event): super(PublishReportBtn, self).mouseReleaseEvent(event) menu = QtWidgets.QMenu(self) - menu.addActions(self._actions) + actions = [] + for item in self._actions: + label, identifier = item + action = QtWidgets.QAction(label, menu) + action.triggered.connect( + functools.partial(self._on_action_trigger, identifier) + ) + actions.append(action) + menu.addActions(actions) menu.exec_(event.globalPos()) From 8eef66c3df8583a8503caefd69e6bd7255fd1498 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 12:29:43 +0100 Subject: [PATCH 353/356] Fix creation of DiscoverResult for pyblish plugins --- openpype/pipeline/create/context.py | 7 ++++--- openpype/pipeline/publish/lib.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 5f1371befa..7672c49eb3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -8,6 +8,9 @@ import inspect from uuid import uuid4 from contextlib import contextmanager +import pyblish.logic +import pyblish.api + from openpype.client import get_assets, get_asset_by_name from openpype.settings import ( get_system_settings, @@ -1619,8 +1622,6 @@ class CreateContext: self._reset_convertor_plugins() def _reset_publish_plugins(self, discover_publish_plugins): - import pyblish.logic - from openpype.pipeline import OpenPypePyblishPluginMixin from openpype.pipeline.publish import ( publish_plugins_discover @@ -1629,7 +1630,7 @@ class CreateContext: # Reset publish plugins self._attr_plugins_by_family = {} - discover_result = DiscoverResult() + discover_result = DiscoverResult(pyblish.api.Plugin) plugins_with_defs = [] plugins_by_targets = [] plugins_mismatch_targets = [] diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 50623e5110..5f95c6695e 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -270,7 +270,7 @@ def publish_plugins_discover(paths=None): """ # The only difference with `pyblish.api.discover` - result = DiscoverResult() + result = DiscoverResult(pyblish.api.Plugin) plugins = dict() plugin_names = [] From 70ab3cd2ab75d29e6ceb60e793a9ffc85ecb3f5c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 14:14:45 +0100 Subject: [PATCH 354/356] fix newly added creators on refresh --- openpype/tools/publisher/widgets/create_widget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index dbf075c216..ef9c5b98fe 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -457,13 +457,14 @@ class CreateWidget(QtWidgets.QWidget): # TODO add details about creator new_creators.add(identifier) if identifier in existing_items: + is_new = False item = existing_items[identifier] else: + is_new = True item = QtGui.QStandardItem() item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - self._creators_model.appendRow(item) item.setData(creator_item.label, QtCore.Qt.DisplayRole) item.setData(creator_item.show_order, CREATOR_SORT_ROLE) @@ -473,6 +474,8 @@ class CreateWidget(QtWidgets.QWidget): CREATOR_THUMBNAIL_ENABLED_ROLE ) item.setData(creator_item.family, FAMILY_ROLE) + if is_new: + self._creators_model.appendRow(item) # Remove families that are no more available for identifier in (old_creators - new_creators): From a305de03f0116e06e490266d8f34c967f12d32f6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 16 Feb 2023 15:31:33 +0000 Subject: [PATCH 355/356] Lower tolerance for framerate difference. --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4c8b11ecd3..b920428b20 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3422,7 +3422,7 @@ def convert_to_maya_fps(fps): min_difference = min(differences) min_index = differences.index(min_difference) supported_framerate = float_framerates[min_index] - if round(min_difference) != 0: + if min_difference > 0.1: raise ValueError( "Framerate \"{}\" strays too far from any supported framerate" " in Maya. Closest supported framerate is \"{}\"".format( From 8a5831c6fe6e4bf434d45af457da013df4def88a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Feb 2023 17:11:00 +0100 Subject: [PATCH 356/356] check for 'pyside6' when filling kwargs for file dialog --- openpype/tools/workfiles/files_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 765d32b3d5..18be746d49 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -621,7 +621,7 @@ class FilesWidget(QtWidgets.QWidget): "caption": "Work Files", "filter": ext_filter } - if qtpy.API in ("pyside", "pyside2"): + if qtpy.API in ("pyside", "pyside2", "pyside6"): kwargs["dir"] = self._workfiles_root else: kwargs["directory"] = self._workfiles_root