From 12766377b43e9391509e52af68a95d1121411a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 14 Feb 2022 18:34:36 +0100 Subject: [PATCH 01/67] fix case with single mesh and prefixes --- .../create/create_unreal_staticmesh.py | 1 + .../hosts/maya/plugins/load/load_reference.py | 3 ++- .../publish/collect_unreal_staticmesh.py | 17 ++++++++++-- .../publish/extract_unreal_staticmesh.py | 27 ++++++++++++++----- .../validate_unreal_staticmesh_naming.py | 9 ++++++- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py index 9ad560ab7c..1fe7e57abc 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py @@ -33,6 +33,7 @@ class CreateUnrealStaticMesh(plugin.Creator): def process(self): with lib.undo_chunk(): + self.name = "{}_{}".format(self.family, self.name) instance = super(CreateUnrealStaticMesh, self).process() content = cmds.sets(instance, query=True) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 0565b0b95c..7cdd91a7ea 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -20,7 +20,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): "camera", "rig", "camerarig", - "xgen"] + "xgen", + "unrealStaticMesh"] representations = ["ma", "abc", "fbx", "mb"] label = "Reference" diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index b1fb0542f2..8d9b88ed32 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from maya import cmds import pyblish.api +from avalon.api import Session +from openpype.api import get_project_settings class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): @@ -16,10 +18,21 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): families = ["unrealStaticMesh"] def process(self, instance): + project_settings = get_project_settings(Session["AVALON_PROJECT"]) + sm_prefix = ( + project_settings + ["maya"] + ["create"] + ["CreateUnrealStaticMesh"] + ["static_mesh_prefix"] + ) # add fbx family to trigger fbx extractor instance.data["families"].append("fbx") - # take the name from instance (without the `S_` prefix) - instance.data["staticMeshCombinedName"] = instance.name[2:] + # take the name from instance (without the `unrealStaticMesh_` prefix) + instance.data["staticMeshCombinedName"] = "{}_{}".format( + sm_prefix, + instance.name[len(instance.data.get("family"))+3:] + ) geometry_set = [i for i in instance if i == "geometry_SET"] instance.data["membersToCombine"] = cmds.sets( diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 32dc9d1d1c..f46360e34a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -15,13 +15,24 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): def process(self, instance): to_combine = instance.data.get("membersToCombine") static_mesh_name = instance.data.get("staticMeshCombinedName") - self.log.info( - "merging {} into {}".format( - " + ".join(to_combine), static_mesh_name)) - duplicates = cmds.duplicate(to_combine, ic=True) - cmds.polyUnite( - *duplicates, - n=static_mesh_name, ch=False) + duplicates = [] + + # if we have more objects, combine them into one + # or just duplicate the single one + if len(to_combine) > 1: + self.log.info( + "merging {} into {}".format( + " + ".join(to_combine), static_mesh_name)) + duplicates = cmds.duplicate(to_combine, ic=True) + cmds.polyUnite( + *duplicates, + n=static_mesh_name, ch=False) + else: + self.log.info( + "duplicating {} to {} for export".format( + to_combine[0], static_mesh_name) + ) + cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) if not instance.data.get("cleanNodes"): instance.data["cleanNodes"] = [] @@ -31,3 +42,5 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): instance.data["setMembers"] = [static_mesh_name] instance.data["setMembers"] += instance.data["collisionMembers"] + + self.log.debug(instance.data["setMembers"]) diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 901a2ec75e..b886e7da75 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -71,6 +71,13 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): ["CreateUnrealStaticMesh"] ["collision_prefixes"] ) + static_mesh_prefix = ( + project_settings + ["maya"] + ["create"] + ["CreateUnrealStaticMesh"] + ["static_mesh_prefix"] + ) combined_geometry_name = instance.data.get( "staticMeshCombinedName", None) @@ -107,7 +114,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): else: expected_collision = "{}_{}".format( cl_m.group("prefix"), - combined_geometry_name + combined_geometry_name[len(static_mesh_prefix)+1:] ) if not obj.startswith(expected_collision): From 1c0601518a3488b450b9c97d30e7c9d3011b1fb4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 15 Feb 2022 15:58:47 +0100 Subject: [PATCH 02/67] remove debug print --- .../hosts/maya/plugins/publish/extract_unreal_staticmesh.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index f46360e34a..0799d574a2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -42,5 +42,3 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): instance.data["setMembers"] = [static_mesh_name] instance.data["setMembers"] += instance.data["collisionMembers"] - - self.log.debug(instance.data["setMembers"]) From 3f7602ce8c7a463ff0472c33ac0e7a3df177cf80 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 15 Feb 2022 16:01:18 +0100 Subject: [PATCH 03/67] fix prefix --- openpype/settings/defaults/project_settings/maya.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 24e8e4a29b..4aaf6c705a 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -52,7 +52,7 @@ "", "_Main" ], - "static_mesh_prefix": "S_", + "static_mesh_prefix": "S", "collision_prefixes": [ "UBX", "UCP", From 3cf48636b1a885704e8e30ddf7104595d1d23df9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 17 Feb 2022 13:18:44 +0100 Subject: [PATCH 04/67] rename family to staticMesh --- .../maya/plugins/create/create_unreal_staticmesh.py | 5 ++--- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- .../maya/plugins/publish/collect_unreal_staticmesh.py | 4 ++-- .../maya/plugins/publish/extract_unreal_staticmesh.py | 2 +- .../publish/validate_unreal_mesh_triangulated.py | 2 +- .../publish/validate_unreal_staticmesh_naming.py | 2 +- .../maya/plugins/publish/validate_unreal_up_axis.py | 2 +- openpype/plugins/publish/integrate_new.py | 3 ++- .../settings/defaults/project_anatomy/templates.json | 2 +- .../settings/defaults/project_settings/global.json | 11 +++++++++++ 10 files changed, 23 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py index 1fe7e57abc..f62d15fe62 100644 --- a/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py @@ -10,7 +10,7 @@ class CreateUnrealStaticMesh(plugin.Creator): """Unreal Static Meshes with collisions.""" name = "staticMeshMain" label = "Unreal - Static Mesh" - family = "unrealStaticMesh" + family = "staticMesh" icon = "cube" dynamic_subset_keys = ["asset"] @@ -28,12 +28,11 @@ class CreateUnrealStaticMesh(plugin.Creator): variant, task_name, asset_id, project_name, host_name ) dynamic_data["asset"] = Session.get("AVALON_ASSET") - return dynamic_data def process(self): + self.name = "{}_{}".format(self.family, self.name) with lib.undo_chunk(): - self.name = "{}_{}".format(self.family, self.name) instance = super(CreateUnrealStaticMesh, self).process() content = cmds.sets(instance, query=True) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 7cdd91a7ea..8713182d3f 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -21,7 +21,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): "rig", "camerarig", "xgen", - "unrealStaticMesh"] + "staticMesh"] representations = ["ma", "abc", "fbx", "mb"] label = "Reference" diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 8d9b88ed32..604aa58b50 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -15,7 +15,7 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.2 label = "Collect Unreal Static Meshes" - families = ["unrealStaticMesh"] + families = ["staticMesh"] def process(self, instance): project_settings = get_project_settings(Session["AVALON_PROJECT"]) @@ -28,7 +28,7 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): ) # add fbx family to trigger fbx extractor instance.data["families"].append("fbx") - # take the name from instance (without the `unrealStaticMesh_` prefix) + # take the name from instance (without the `staticMesh_` prefix) instance.data["staticMeshCombinedName"] = "{}_{}".format( sm_prefix, instance.name[len(instance.data.get("family"))+3:] diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 0799d574a2..6153417de4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -10,7 +10,7 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): order = pyblish.api.ExtractorOrder - 0.1 label = "Extract Unreal Static Mesh" - families = ["unrealStaticMesh"] + families = ["staticMesh"] def process(self, instance): to_combine = instance.data.get("membersToCombine") 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 b2ef174374..737664ffd3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -10,7 +10,7 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): order = openpype.api.ValidateMeshOrder hosts = ["maya"] - families = ["unrealStaticMesh"] + families = ["staticMesh"] category = "geometry" label = "Mesh is Triangulated" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index b886e7da75..89769a3421 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -52,7 +52,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): optional = True order = openpype.api.ValidateContentsOrder hosts = ["maya"] - families = ["unrealStaticMesh"] + families = ["staticMesh"] label = "Unreal StaticMesh Name" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] regex_mesh = r"(?P.*))" diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py index 5a8c29c22d..b3af643048 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py @@ -11,7 +11,7 @@ class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): optional = True order = openpype.api.ValidateContentsOrder hosts = ["maya"] - families = ["unrealStaticMesh"] + families = ["staticMesh"] label = "Unreal Up-Axis check" actions = [openpype.api.RepairAction] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index bf214d9139..9ced6a1d7d 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -100,7 +100,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "redshiftproxy", "effect", "xgen", - "hda" + "hda", + "staticMesh" ] exclude_families = ["clip"] db_representation_context_keys = [ diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index d46d449c77..2ab3ff5c54 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -28,7 +28,7 @@ }, "delivery": {}, "unreal": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}", "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", "path": "{@folder}/{@file}" }, diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index f08bee8b2d..93aba808db 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -192,6 +192,17 @@ "task_types": [], "tasks": [], "template_name": "render" + }, + { + "families": [ + "staticMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "tasks": [], + "template_name": "unreal" } ], "subset_grouping_profiles": [ From 487b273a09e3cc67bc0386d047bfb4309db66439 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 18 Feb 2022 18:05:07 +0100 Subject: [PATCH 05/67] refactor fbx extractor --- openpype/hosts/maya/api/fbx.py | 208 ++++++++++++++++++ .../hosts/maya/plugins/publish/clean_nodes.py | 31 --- .../publish/collect_unreal_staticmesh.py | 2 - .../hosts/maya/plugins/publish/extract_fbx.py | 196 ++--------------- .../publish/extract_unreal_staticmesh.py | 89 ++++++-- 5 files changed, 287 insertions(+), 239 deletions(-) create mode 100644 openpype/hosts/maya/api/fbx.py delete mode 100644 openpype/hosts/maya/plugins/publish/clean_nodes.py diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py new file mode 100644 index 0000000000..3a8ae19ff7 --- /dev/null +++ b/openpype/hosts/maya/api/fbx.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +"""Tools to work with FBX.""" +import os +import logging + +from pyblish.api import Instance + +from maya import cmds # noqa +import maya.mel as mel # noqa + + +class FBXExtractor: + """Extract FBX from Maya. + + This extracts reproducible FBX exports ignoring any of the settings set + on the local machine in the FBX export options window. + + All export settings are applied with the `FBXExport*` commands prior + to the `FBXExport` call itself. The options can be overridden with + their + nice names as seen in the "options" property on this class. + + For more information on FBX exports see: + - https://knowledge.autodesk.com/support/maya/learn-explore/caas + /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4 + -9CB19C28F4E0-htm.html + - http://forums.cgsociety.org/archive/index.php?t-1032853.html + - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE + /LKs9hakE28kJ + + """ + @property + def options(self): + """Overridable options for FBX Export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + return { + "cameras": bool, + "smoothingGroups": bool, + "hardEdges": bool, + "tangents": bool, + "smoothMesh": bool, + "instances": bool, + # "referencedContainersContent": bool, # deprecated in Maya 2016+ + "bakeComplexAnimation": int, + "bakeComplexStart": int, + "bakeComplexEnd": int, + "bakeComplexStep": int, + "bakeResampleAnimation": bool, + "animationOnly": bool, + "useSceneName": bool, + "quaternion": str, # "euler" + "shapes": bool, + "skins": bool, + "constraints": bool, + "lights": bool, + "embeddedTextures": bool, + "inputConnections": bool, + "upAxis": str, # x, y or z, + "triangulate": bool + } + + @property + def default_options(self): + """The default options for FBX extraction. + + This includes shapes, skins, constraints, lights and incoming + connections and exports with the Y-axis as up-axis. + + By default this uses the time sliders start and end time. + + """ + + start_frame = int(cmds.playbackOptions(query=True, + animationStartTime=True)) + end_frame = int(cmds.playbackOptions(query=True, + animationEndTime=True)) + + return { + "cameras": False, + "smoothingGroups": False, + "hardEdges": False, + "tangents": False, + "smoothMesh": False, + "instances": False, + "bakeComplexAnimation": True, + "bakeComplexStart": start_frame, + "bakeComplexEnd": end_frame, + "bakeComplexStep": 1, + "bakeResampleAnimation": True, + "animationOnly": False, + "useSceneName": False, + "quaternion": "euler", + "shapes": True, + "skins": True, + "constraints": False, + "lights": True, + "embeddedTextures": True, + "inputConnections": True, + "upAxis": "y", + "triangulate": False + } + + def __init__(self, log=None): + # Ensure FBX plug-in is loaded + self.log = log or logging.getLogger(__class__.__name__) + cmds.loadPlugin("fbxmaya", quiet=True) + + def parse_overrides(self, instance, options): + """Inspect data of instance to determine overridden options + + An instance may supply any of the overridable options + as data, the option is then added to the extraction. + + """ + + for key in instance.data: + if key not in self.options: + continue + + # Ensure the data is of correct type + value = instance.data[key] + if not isinstance(value, self.options[key]): + self.log.warning( + "Overridden attribute {key} was of " + "the wrong type: {invalid_type} " + "- should have been {valid_type}".format( + key=key, + invalid_type=type(value).__name__, + valid_type=self.options[key].__name__)) + continue + + options[key] = value + + return options + + def set_options_from_instance(self, instance): + # type: (Instance) -> None + """Sets FBX export options from data in the instance. + + Args: + instance (Instance): Instance data. + + """ + # Parse export options + options = self.default_options + options = self.parse_overrides(instance, options) + self.log.info("Export options: {0}".format(options)) + + # Collect the start and end including handles + # TODO: Move this to library function (pypeclub/OpenPype#2648) + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + handle_start = instance.data.get("handleStart", 0) + handle_end = instance.data.get("handleEnd", 0) + if handle_start: + start -= handle_start + if handle_end: + end += handle_end + + options['bakeComplexStart'] = start + options['bakeComplexEnd'] = end + + # First apply the default export settings to be fully consistent + # each time for successive publishes + mel.eval("FBXResetExport") + + # Apply the FBX overrides through MEL since the commands + # only work correctly in MEL according to online + # available discussions on the topic + _iteritems = getattr(options, "iteritems", options.items) + for option, value in _iteritems(): + key = option[0].upper() + option[1:] # uppercase first letter + + # Boolean must be passed as lower-case strings + # as to MEL standards + if isinstance(value, bool): + value = str(value).lower() + + template = "FBXExport{0} {1}" if key == "UpAxis" else \ + "FBXExport{0} -v {1}" # noqa + cmd = template.format(key, value) + self.log.info(cmd) + mel.eval(cmd) + + # Never show the UI or generate a log + mel.eval("FBXExportShowUI -v false") + mel.eval("FBXExportGenerateLog -v false") + + @staticmethod + def export(members, path): + # type: (list, str) -> None + """Export members as FBX with given path. + + Args: + members (list): List of members to export. + path (str): Path to use for export. + + """ + cmds.select(members, r=1, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/plugins/publish/clean_nodes.py b/openpype/hosts/maya/plugins/publish/clean_nodes.py deleted file mode 100644 index 03995cdabe..0000000000 --- a/openpype/hosts/maya/plugins/publish/clean_nodes.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -"""Cleanup leftover nodes.""" -from maya import cmds # noqa -import pyblish.api - - -class CleanNodesUp(pyblish.api.InstancePlugin): - """Cleans up the staging directory after a successful publish. - - This will also clean published renders and delete their parent directories. - - """ - - order = pyblish.api.IntegratorOrder + 10 - label = "Clean Nodes" - optional = True - active = True - - def process(self, instance): - if not instance.data.get("cleanNodes"): - self.log.info("Nothing to clean.") - return - - nodes_to_clean = instance.data.pop("cleanNodes", []) - self.log.info("Removing {} nodes".format(len(nodes_to_clean))) - for node in nodes_to_clean: - try: - cmds.delete(node) - except ValueError: - # object might be already deleted, don't complain about it - pass diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 604aa58b50..1a0a561efd 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -26,8 +26,6 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): ["CreateUnrealStaticMesh"] ["static_mesh_prefix"] ) - # add fbx family to trigger fbx extractor - instance.data["families"].append("fbx") # take the name from instance (without the `staticMesh_` prefix) instance.data["staticMeshCombinedName"] = "{}_{}".format( sm_prefix, diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py index 844084b9ab..fbbe8e06b0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -5,152 +5,29 @@ from maya import cmds # noqa import maya.mel as mel # noqa import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import ( - root_parent, - maintained_selection -) +from openpype.hosts.maya.api.lib import maintained_selection + +from openpype.hosts.maya.api import fbx class ExtractFBX(openpype.api.Extractor): """Extract FBX from Maya. - This extracts reproducible FBX exports ignoring any of the settings set - on the local machine in the FBX export options window. - - All export settings are applied with the `FBXExport*` commands prior - to the `FBXExport` call itself. The options can be overridden with their - nice names as seen in the "options" property on this class. - - For more information on FBX exports see: - - https://knowledge.autodesk.com/support/maya/learn-explore/caas - /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4 - -9CB19C28F4E0-htm.html - - http://forums.cgsociety.org/archive/index.php?t-1032853.html - - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE - /LKs9hakE28kJ + This extracts reproducible FBX exports ignoring any of the + settings set on the local machine in the FBX export options window. """ - order = pyblish.api.ExtractorOrder label = "Extract FBX" families = ["fbx"] - @property - def options(self): - """Overridable options for FBX Export - - Given in the following format - - {NAME: EXPECTED TYPE} - - If the overridden option's type does not match, - the option is not included and a warning is logged. - - """ - - return { - "cameras": bool, - "smoothingGroups": bool, - "hardEdges": bool, - "tangents": bool, - "smoothMesh": bool, - "instances": bool, - # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": int, - "bakeComplexStart": int, - "bakeComplexEnd": int, - "bakeComplexStep": int, - "bakeResampleAnimation": bool, - "animationOnly": bool, - "useSceneName": bool, - "quaternion": str, # "euler" - "shapes": bool, - "skins": bool, - "constraints": bool, - "lights": bool, - "embeddedTextures": bool, - "inputConnections": bool, - "upAxis": str, # x, y or z, - "triangulate": bool - } - - @property - def default_options(self): - """The default options for FBX extraction. - - This includes shapes, skins, constraints, lights and incoming - connections and exports with the Y-axis as up-axis. - - By default this uses the time sliders start and end time. - - """ - - start_frame = int(cmds.playbackOptions(query=True, - animationStartTime=True)) - end_frame = int(cmds.playbackOptions(query=True, - animationEndTime=True)) - - return { - "cameras": False, - "smoothingGroups": False, - "hardEdges": False, - "tangents": False, - "smoothMesh": False, - "instances": False, - "bakeComplexAnimation": True, - "bakeComplexStart": start_frame, - "bakeComplexEnd": end_frame, - "bakeComplexStep": 1, - "bakeResampleAnimation": True, - "animationOnly": False, - "useSceneName": False, - "quaternion": "euler", - "shapes": True, - "skins": True, - "constraints": False, - "lights": True, - "embeddedTextures": True, - "inputConnections": True, - "upAxis": "y", - "triangulate": False - } - - def parse_overrides(self, instance, options): - """Inspect data of instance to determine overridden options - - An instance may supply any of the overridable options - as data, the option is then added to the extraction. - - """ - - for key in instance.data: - if key not in self.options: - continue - - # Ensure the data is of correct type - value = instance.data[key] - if not isinstance(value, self.options[key]): - self.log.warning( - "Overridden attribute {key} was of " - "the wrong type: {invalid_type} " - "- should have been {valid_type}".format( - key=key, - invalid_type=type(value).__name__, - valid_type=self.options[key].__name__)) - continue - - options[key] = value - - return options - def process(self, instance): - - # Ensure FBX plug-in is loaded - cmds.loadPlugin("fbxmaya", quiet=True) + fbx_exporter = fbx.FBXExtractor(log=self.log) # Define output path - stagingDir = self.staging_dir(instance) + staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) - path = os.path.join(stagingDir, filename) + path = os.path.join(staging_dir, filename) # The export requires forward slashes because we need # to format it into a string in a mel expression @@ -162,58 +39,13 @@ class ExtractFBX(openpype.api.Extractor): self.log.info("Members: {0}".format(members)) self.log.info("Instance: {0}".format(instance[:])) - # Parse export options - options = self.default_options - options = self.parse_overrides(instance, options) - self.log.info("Export options: {0}".format(options)) - - # Collect the start and end including handles - start = instance.data["frameStart"] - end = instance.data["frameEnd"] - handles = instance.data.get("handles", 0) - if handles: - start -= handles - end += handles - - options['bakeComplexStart'] = start - options['bakeComplexEnd'] = end - - # First apply the default export settings to be fully consistent - # each time for successive publishes - mel.eval("FBXResetExport") - - # Apply the FBX overrides through MEL since the commands - # only work correctly in MEL according to online - # available discussions on the topic - _iteritems = getattr(options, "iteritems", options.items) - for option, value in _iteritems(): - key = option[0].upper() + option[1:] # uppercase first letter - - # Boolean must be passed as lower-case strings - # as to MEL standards - if isinstance(value, bool): - value = str(value).lower() - - template = "FBXExport{0} {1}" if key == "UpAxis" else "FBXExport{0} -v {1}" # noqa - cmd = template.format(key, value) - self.log.info(cmd) - mel.eval(cmd) - - # Never show the UI or generate a log - mel.eval("FBXExportShowUI -v false") - mel.eval("FBXExportGenerateLog -v false") + fbx_exporter.set_options_from_instance(instance) # Export - if "unrealStaticMesh" in instance.data["families"]: - with maintained_selection(): - with root_parent(members): - self.log.info("Un-parenting: {}".format(members)) - cmds.select(members, r=1, noExpand=True) - mel.eval('FBXExport -f "{}" -s'.format(path)) - else: - with maintained_selection(): - cmds.select(members, r=1, noExpand=True) - mel.eval('FBXExport -f "{}" -s'.format(path)) + with maintained_selection(): + fbx_exporter.export(members, path) + cmds.select(members, r=1, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) if "representations" not in instance.data: instance.data["representations"] = [] @@ -222,7 +54,7 @@ class ExtractFBX(openpype.api.Extractor): 'name': 'fbx', 'ext': 'fbx', 'files': filename, - "stagingDir": stagingDir, + "stagingDir": staging_dir, } instance.data["representations"].append(representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 6153417de4..d3d491594a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -1,9 +1,18 @@ # -*- coding: utf-8 -*- """Create Unreal Static Mesh data to be extracted as FBX.""" -import openpype.api -import pyblish.api +import os + from maya import cmds # noqa +import pyblish.api +import openpype.api +from openpype.hosts.maya.api.lib import ( + root_parent, + maintained_selection, + delete_after +) +from openpype.hosts.maya.api import fbx + class ExtractUnrealStaticMesh(openpype.api.Extractor): """Extract FBX from Maya. """ @@ -13,32 +22,64 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): families = ["staticMesh"] def process(self, instance): + fbx_exporter = fbx.FBXExtractor(log=self.log) to_combine = instance.data.get("membersToCombine") static_mesh_name = instance.data.get("staticMeshCombinedName") duplicates = [] - # if we have more objects, combine them into one - # or just duplicate the single one - if len(to_combine) > 1: - self.log.info( - "merging {} into {}".format( - " + ".join(to_combine), static_mesh_name)) - duplicates = cmds.duplicate(to_combine, ic=True) - cmds.polyUnite( - *duplicates, - n=static_mesh_name, ch=False) - else: - self.log.info( - "duplicating {} to {} for export".format( - to_combine[0], static_mesh_name) - ) - cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) + # delete created temporary nodes after extraction + with delete_after() as delete_bin: + # if we have more objects, combine them into one + # or just duplicate the single one + if len(to_combine) > 1: + self.log.info( + "merging {} into {}".format( + " + ".join(to_combine), static_mesh_name)) + duplicates = cmds.duplicate(to_combine, ic=True) + cmds.polyUnite( + *duplicates, + n=static_mesh_name, ch=False) + else: + self.log.info( + "duplicating {} to {} for export".format( + to_combine[0], static_mesh_name) + ) + cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) - if not instance.data.get("cleanNodes"): - instance.data["cleanNodes"] = [] + delete_bin.extend(static_mesh_name) + delete_bin.extend(duplicates) - instance.data["cleanNodes"].append(static_mesh_name) - instance.data["cleanNodes"] += duplicates + members = [static_mesh_name] + members += instance.data["collisionMembers"] - instance.data["setMembers"] = [static_mesh_name] - instance.data["setMembers"] += instance.data["collisionMembers"] + fbx_exporter = fbx.FBXExtractor() + + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.info("Extracting FBX to: {0}".format(path)) + self.log.info("Members: {0}".format(members)) + self.log.info("Instance: {0}".format(instance[:])) + + fbx_exporter.set_options_from_instance(instance) + + with maintained_selection(): + with root_parent(members): + self.log.info("Un-parenting: {}".format(members)) + fbx_exporter.export(members, path) + + representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.info("Extract FBX successful to: {0}".format(path)) \ No newline at end of file From 9604dca2593745380fc24741b7b7f5cf45f76ba8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 18 Feb 2022 18:40:21 +0100 Subject: [PATCH 06/67] fix logging --- openpype/hosts/maya/api/fbx.py | 2 +- .../maya/plugins/publish/extract_unreal_staticmesh.py | 8 +++++--- .../plugins/publish/validate_unreal_staticmesh_naming.py | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 3a8ae19ff7..659f456e1a 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -110,7 +110,7 @@ class FBXExtractor: def __init__(self, log=None): # Ensure FBX plug-in is loaded - self.log = log or logging.getLogger(__class__.__name__) + self.log = log or logging.getLogger(self.__class__.__name__) cmds.loadPlugin("fbxmaya", quiet=True) def parse_overrides(self, instance, options): diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index d3d491594a..c5d2710dc2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -22,7 +22,6 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): families = ["staticMesh"] def process(self, instance): - fbx_exporter = fbx.FBXExtractor(log=self.log) to_combine = instance.data.get("membersToCombine") static_mesh_name = instance.data.get("staticMeshCombinedName") duplicates = [] @@ -46,13 +45,13 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): ) cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) - delete_bin.extend(static_mesh_name) + delete_bin.extend([static_mesh_name]) delete_bin.extend(duplicates) members = [static_mesh_name] members += instance.data["collisionMembers"] - fbx_exporter = fbx.FBXExtractor() + fbx_exporter = fbx.FBXExtractor(log=self.log) # Define output path staging_dir = self.staging_dir(instance) @@ -74,6 +73,9 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): self.log.info("Un-parenting: {}".format(members)) fbx_exporter.export(members, path) + if "representations" not in instance.data: + instance.data["representations"] = [] + representation = { 'name': 'fbx', 'ext': 'fbx', diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 89769a3421..e233fd190c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -79,6 +79,9 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): ["static_mesh_prefix"] ) + to_combine = instance.data.get("membersToCombine") + if not to_combine: + raise ValueError("Missing geometry to export.") combined_geometry_name = instance.data.get( "staticMeshCombinedName", None) if cls.validate_mesh: From 3a42aa5c943e3a1a9a03a9f5c34cde0dbe8263d7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 18 Feb 2022 18:42:19 +0100 Subject: [PATCH 07/67] fix family name in defaults --- openpype/settings/defaults/project_settings/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 93aba808db..efed25287a 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -298,7 +298,7 @@ }, { "families": [ - "unrealStaticMesh" + "staticMesh" ], "hosts": [ "maya" From f5087f4e47588d31bd335e51c648c8eb6bb33425 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 18 Feb 2022 19:04:24 +0100 Subject: [PATCH 08/67] =?UTF-8?q?fix=20hound=20=F0=9F=90=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/maya/api/fbx.py | 1 - .../hosts/maya/plugins/publish/collect_unreal_staticmesh.py | 2 +- .../hosts/maya/plugins/publish/extract_unreal_staticmesh.py | 2 +- .../maya/plugins/publish/validate_unreal_staticmesh_naming.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 659f456e1a..00c58153af 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Tools to work with FBX.""" -import os import logging from pyblish.api import Instance diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 1a0a561efd..2c0bec2c1a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -29,7 +29,7 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): # take the name from instance (without the `staticMesh_` prefix) instance.data["staticMeshCombinedName"] = "{}_{}".format( sm_prefix, - instance.name[len(instance.data.get("family"))+3:] + instance.name[len(instance.data.get("family")) + 3:] ) geometry_set = [i for i in instance if i == "geometry_SET"] diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index c5d2710dc2..0c7d61f8f5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -84,4 +84,4 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extract FBX successful to: {0}".format(path)) \ No newline at end of file + self.log.info("Extract FBX successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index e233fd190c..fd19e3d2af 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -117,7 +117,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): else: expected_collision = "{}_{}".format( cl_m.group("prefix"), - combined_geometry_name[len(static_mesh_prefix)+1:] + combined_geometry_name[len(static_mesh_prefix) + 1:] ) if not obj.startswith(expected_collision): From 1121bc8eaa42a0f8fcdda1001500908e42c7308e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 1 Mar 2022 14:36:43 +0100 Subject: [PATCH 09/67] disable unnecessary plugins --- .../maya/plugins/publish/validate_unreal_mesh_triangulated.py | 1 + .../maya/plugins/publish/validate_unreal_staticmesh_naming.py | 4 ++-- .../hosts/maya/plugins/publish/validate_unreal_up_axis.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) 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 737664ffd3..c05121a1b0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -14,6 +14,7 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): category = "geometry" label = "Mesh is Triangulated" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + active = False @classmethod def get_invalid(cls, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index fd19e3d2af..d15d52f3bd 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -53,7 +53,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["staticMesh"] - label = "Unreal StaticMesh Name" + label = "Unreal Static Mesh Name" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] regex_mesh = r"(?P.*))" regex_collision = r"(?P.*)" @@ -101,7 +101,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cls.log.warning("No collision objects to validate.") return False - regex_collision = "{}{}".format( + regex_collision = "{}{}_(\\d+)".format( "(?P({}))_".format( "|".join("{0}".format(p) for p in collision_prefixes) ) or "", cls.regex_collision diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py index b3af643048..5e1b04889f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py @@ -9,6 +9,7 @@ class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): """Validate if Z is set as up axis in Maya""" optional = True + active = False order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["staticMesh"] From 691f23d72eab7ab0114efc6a9890ddcdce89f0da Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 1 Mar 2022 14:38:48 +0100 Subject: [PATCH 10/67] unify handles --- openpype/hosts/maya/api/fbx.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 00c58153af..7980cd029c 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -154,15 +154,8 @@ class FBXExtractor: self.log.info("Export options: {0}".format(options)) # Collect the start and end including handles - # TODO: Move this to library function (pypeclub/OpenPype#2648) - start = instance.data["frameStart"] - end = instance.data["frameEnd"] - handle_start = instance.data.get("handleStart", 0) - handle_end = instance.data.get("handleEnd", 0) - if handle_start: - start -= handle_start - if handle_end: - end += handle_end + start = instance.data["frameStartHandle"] + end = instance.data["frameEndHandle"] options['bakeComplexStart'] = start options['bakeComplexEnd'] = end From 901df528d4107e9423e7bc81aa80108024af143f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 1 Mar 2022 14:42:36 +0100 Subject: [PATCH 11/67] remove ftrack submodules from old location --- .gitmodules | 8 +------- .../modules/default_modules/ftrack/python2_vendor/arrow | 1 - .../ftrack/python2_vendor/ftrack-python-api | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 160000 openpype/modules/default_modules/ftrack/python2_vendor/arrow delete mode 160000 openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api diff --git a/.gitmodules b/.gitmodules index e1b0917e9d..67b820a247 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,10 +3,4 @@ url = https://github.com/pypeclub/avalon-core.git [submodule "repos/avalon-unreal-integration"] path = repos/avalon-unreal-integration - url = https://github.com/pypeclub/avalon-unreal-integration.git -[submodule "openpype/modules/default_modules/ftrack/python2_vendor/arrow"] - path = openpype/modules/default_modules/ftrack/python2_vendor/arrow - url = https://github.com/arrow-py/arrow.git -[submodule "openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api"] - path = openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api - url = https://bitbucket.org/ftrack/ftrack-python-api.git \ No newline at end of file + url = https://github.com/pypeclub/avalon-unreal-integration.git \ No newline at end of file diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e From 28e11a5b2d847d0db971fe8a7d5c707064ac17fd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 1 Mar 2022 15:18:38 +0100 Subject: [PATCH 12/67] fix frame handling and collision name determination --- openpype/hosts/maya/api/fbx.py | 6 ++++-- .../hosts/maya/plugins/publish/collect_unreal_staticmesh.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 7980cd029c..92683da51b 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -154,8 +154,10 @@ class FBXExtractor: self.log.info("Export options: {0}".format(options)) # Collect the start and end including handles - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] + start = instance.data.get("frameStartHandle") or \ + instance.context.data.get("frameStartHandle") + end = instance.data.get("frameEndHandle") or \ + instance.context.data.get("frameEndHandle") options['bakeComplexStart'] = start options['bakeComplexEnd'] = end diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 2c0bec2c1a..ddcc3f691f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -29,7 +29,7 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): # take the name from instance (without the `staticMesh_` prefix) instance.data["staticMeshCombinedName"] = "{}_{}".format( sm_prefix, - instance.name[len(instance.data.get("family")) + 3:] + instance.name[len(instance.data.get("family")) + 1:] ) geometry_set = [i for i in instance if i == "geometry_SET"] From bd5478731cc6f6839d7dcbbeb1fd6d9d37e7cff0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 1 Mar 2022 22:39:53 +0100 Subject: [PATCH 13/67] change default templates --- .../defaults/project_anatomy/templates.json | 15 ++++++++++++--- .../defaults/project_settings/global.json | 9 ++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 2ab3ff5c54..7d01248653 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -28,9 +28,18 @@ }, "delivery": {}, "unreal": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}", - "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", + "folder": "{root[work]}/{project[name]}/unreal/{task[name]}", + "file": "{project[code]}_{asset}", "path": "{@folder}/{@file}" }, - "others": {} + "others": { + "maya2unreal": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}", + "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", + "path": "{@folder}/{@file}" + }, + "__dynamic_keys_labels__": { + "maya2unreal": "Maya to Unreal" + } + } } \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index efed25287a..86786cc9ed 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -202,7 +202,7 @@ ], "task_types": [], "tasks": [], - "template_name": "unreal" + "template_name": "maya2unreal" } ], "subset_grouping_profiles": [ @@ -315,6 +315,13 @@ "task_types": [], "hosts": [], "workfile_template": "work" + }, + { + "task_types": [], + "hosts": [ + "unreal" + ], + "workfile_template": "unreal" } ], "last_workfile_on_startup": [ From 8c0d7ed1ee09c84b233cb7ec4068312afa27507a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 4 Mar 2022 18:38:26 +0100 Subject: [PATCH 14/67] initial work on skeletal mesh support --- .../create/create_unreal_skeletalmesh.py | 50 ++++++++++++++++ .../publish/collect_unreal_skeletalmesh.py | 23 ++++++++ .../publish/collect_unreal_staticmesh.py | 7 +-- .../publish/extract_unreal_skeletalmesh.py | 57 +++++++++++++++++++ .../publish/extract_unreal_staticmesh.py | 2 +- openpype/plugins/publish/integrate_new.py | 3 +- .../defaults/project_settings/global.json | 14 ++++- .../defaults/project_settings/maya.json | 5 ++ .../schemas/schema_maya_create.json | 26 +++++++++ 9 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py create mode 100644 openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py diff --git a/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py new file mode 100644 index 0000000000..a6deeeee2e --- /dev/null +++ b/openpype/hosts/maya/plugins/create/create_unreal_skeletalmesh.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +"""Creator for Unreal Skeletal Meshes.""" +from openpype.hosts.maya.api import plugin, lib +from avalon.api import Session +from maya import cmds # noqa + + +class CreateUnrealSkeletalMesh(plugin.Creator): + """Unreal Static Meshes with collisions.""" + name = "staticMeshMain" + label = "Unreal - Skeletal Mesh" + family = "skeletalMesh" + icon = "thumbs-up" + dynamic_subset_keys = ["asset"] + + joint_hints = [] + + def __init__(self, *args, **kwargs): + """Constructor.""" + super(CreateUnrealSkeletalMesh, self).__init__(*args, **kwargs) + + @classmethod + def get_dynamic_data( + cls, variant, task_name, asset_id, project_name, host_name + ): + dynamic_data = super(CreateUnrealSkeletalMesh, cls).get_dynamic_data( + variant, task_name, asset_id, project_name, host_name + ) + dynamic_data["asset"] = Session.get("AVALON_ASSET") + return dynamic_data + + def process(self): + self.name = "{}_{}".format(self.family, self.name) + with lib.undo_chunk(): + instance = super(CreateUnrealSkeletalMesh, self).process() + content = cmds.sets(instance, query=True) + + # empty set and process its former content + cmds.sets(content, rm=instance) + geometry_set = cmds.sets(name="geometry_SET", empty=True) + joints_set = cmds.sets(name="joints_SET", empty=True) + + cmds.sets([geometry_set, joints_set], forceElement=instance) + members = cmds.ls(content) or [] + + for node in members: + if node in self.joint_hints: + cmds.sets(node, forceElement=joints_set) + else: + cmds.sets(node, forceElement=geometry_set) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py new file mode 100644 index 0000000000..4b1de865c5 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from maya import cmds # noqa +import pyblish.api +from avalon.api import Session +from openpype.api import get_project_settings + + +class CollectUnrealSkeletalMesh(pyblish.api.InstancePlugin): + """Collect Unreal Skeletal Mesh.""" + + order = pyblish.api.CollectorOrder + 0.2 + label = "Collect Unreal Skeletal Meshes" + families = ["skeletalMesh"] + + def process(self, instance): + # set fbx overrides on instance + instance.data["smoothingGroups"] = True + instance.data["smoothMesh"] = True + instance.data["triangulate"] = True + + frame = cmds.currentTime(query=True) + instance.data["frameStart"] = frame + instance.data["frameEnd"] = frame diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index ddcc3f691f..59f8df1ef1 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -6,12 +6,7 @@ from openpype.api import get_project_settings class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): - """Collect Unreal Static Mesh - - Ensures always only a single frame is extracted (current frame). This - also sets correct FBX options for later extraction. - - """ + """Collect Unreal Static Mesh.""" order = pyblish.api.CollectorOrder + 0.2 label = "Collect Unreal Static Meshes" diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py new file mode 100644 index 0000000000..0ad1a92292 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""Create Unreal Skeletal Mesh data to be extracted as FBX.""" +import os + +from maya import cmds # noqa + +import pyblish.api +import openpype.api +from openpype.hosts.maya.api.lib import ( + root_parent, + maintained_selection, + delete_after +) +from openpype.hosts.maya.api import fbx + + +class ExtractUnrealSkeletalMesh(openpype.api.Extractor): + """Extract Unreal Skeletal Mesh as FBX from Maya. """ + + order = pyblish.api.ExtractorOrder - 0.1 + label = "Extract Unreal Skeletal Mesh" + families = ["skeletalMesh"] + + def process(self, instance): + fbx_exporter = fbx.FBXExtractor(log=self.log) + + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.info("Extracting FBX to: {0}".format(path)) + self.log.info("Members: {0}".format(instance)) + self.log.info("Instance: {0}".format(instance[:])) + + fbx_exporter.set_options_from_instance(instance) + with maintained_selection(): + with root_parent(instance): + self.log.info("Un-parenting: {}".format(instance)) + fbx_exporter.export(instance, path) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.info("Extract FBX successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 0c7d61f8f5..22a3af3059 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -15,7 +15,7 @@ from openpype.hosts.maya.api import fbx class ExtractUnrealStaticMesh(openpype.api.Extractor): - """Extract FBX from Maya. """ + """Extract Unreal Static Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 label = "Extract Unreal Static Mesh" diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 48b87c697b..fce8c73d1d 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -102,7 +102,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "xgen", "hda", "usd", - "staticMesh" + "staticMesh", + "skeletalMesh" ] exclude_families = ["clip"] db_representation_context_keys = [ diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 86786cc9ed..b7f6414cfb 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -195,7 +195,8 @@ }, { "families": [ - "staticMesh" + "staticMesh", + "skeletalMesh" ], "hosts": [ "maya" @@ -306,6 +307,17 @@ "task_types": [], "tasks": [], "template": "S_{asset}{variant}" + }, + { + "families": [ + "skeletalMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "tasks": [], + "template": "SK_{asset}{variant}" } ] }, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 362835e558..6317d30b3f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -60,6 +60,11 @@ "UCX" ] }, + "CreateUnrealSkeletalMesh": { + "enabled": true, + "defaults": [], + "joint_hints": "jnt_org" + }, "CreateAnimation": { "enabled": true, "defaults": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 0544b4bab7..6dc10ed2a5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -97,6 +97,32 @@ } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateUnrealSkeletalMesh", + "label": "Create Unreal - Skeletal Mesh", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + }, + { + "type": "text", + "key": "joint_hints", + "label": "Joint root hint" + } + ] + }, { "type": "schema_template", From 5e5f6e0879a470688072c3125145bdc544f10cb6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 8 Mar 2022 17:19:57 +0100 Subject: [PATCH 15/67] fix un-parenting --- .../publish/collect_unreal_skeletalmesh.py | 23 +++++++++++++++++++ .../publish/extract_unreal_skeletalmesh.py | 14 +++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py index 4b1de865c5..7d479b706c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py @@ -21,3 +21,26 @@ class CollectUnrealSkeletalMesh(pyblish.api.InstancePlugin): frame = cmds.currentTime(query=True) instance.data["frameStart"] = frame instance.data["frameEnd"] = frame + + geo_sets = [ + i for i in instance[:] + if i.lower().startswith("geometry_set") + ] + + joint_sets = [ + i for i in instance[:] + if i.lower().startswith("joints_set") + ] + + instance.data["geometry"] = [] + instance.data["joints"] = [] + + for geo_set in geo_sets: + geo_content = cmds.ls(cmds.sets(geo_set, query=True), long=True) + if geo_content: + instance.data["geometry"] += geo_content + + for join_set in joint_sets: + join_content = cmds.ls(cmds.sets(join_set, query=True), long=True) + if join_content: + instance.data["joints"] += join_content diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 0ad1a92292..c0b408c3f0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -29,19 +29,25 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) + geo = instance.data.get("geometry") + joints = instance.data.get("joints") + + to_extract = geo + joints + # The export requires forward slashes because we need # to format it into a string in a mel expression path = path.replace('\\', '/') self.log.info("Extracting FBX to: {0}".format(path)) - self.log.info("Members: {0}".format(instance)) + self.log.info("Members: {0}".format(to_extract)) self.log.info("Instance: {0}".format(instance[:])) fbx_exporter.set_options_from_instance(instance) with maintained_selection(): - with root_parent(instance): - self.log.info("Un-parenting: {}".format(instance)) - fbx_exporter.export(instance, path) + with root_parent(to_extract): + rooted = [i.split("|")[-1] for i in to_extract] + self.log.info("Un-parenting: {}".format(to_extract)) + fbx_exporter.export(rooted, path) if "representations" not in instance.data: instance.data["representations"] = [] From 592c95b1a66e0067b797b7a6d7458fa8127ac03a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 10 Mar 2022 01:14:21 +0100 Subject: [PATCH 16/67] fixes static mesh side of things --- .../plugins/publish/collect_unreal_staticmesh.py | 15 ++++++++++++--- .../plugins/publish/extract_unreal_staticmesh.py | 2 +- .../publish/validate_unreal_staticmesh_naming.py | 7 +++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 59f8df1ef1..02f21187b3 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- -from maya import cmds +from maya import cmds # noqa import pyblish.api from avalon.api import Session from openpype.api import get_project_settings +from pprint import pformat class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): @@ -24,17 +25,25 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): # take the name from instance (without the `staticMesh_` prefix) instance.data["staticMeshCombinedName"] = "{}_{}".format( sm_prefix, - instance.name[len(instance.data.get("family")) + 1:] - ) + instance.data.get("subset")[len(sm_prefix) + 1:]) + + self.log.info("joined mesh name: {}".format( + instance.data.get("staticMeshCombinedName"))) geometry_set = [i for i in instance if i == "geometry_SET"] instance.data["membersToCombine"] = cmds.sets( geometry_set, query=True) + self.log.info("joining meshes: {}".format( + pformat(instance.data.get("membersToCombine")))) + collision_set = [i for i in instance if i == "collisions_SET"] instance.data["collisionMembers"] = cmds.sets( collision_set, query=True) + self.log.info("collisions: {}".format( + pformat(instance.data.get("collisionMembers")))) + # set fbx overrides on instance instance.data["smoothingGroups"] = True instance.data["smoothMesh"] = True diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 22a3af3059..987370d395 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -46,7 +46,7 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) delete_bin.extend([static_mesh_name]) - delete_bin.extend(duplicates) + # delete_bin.extend(duplicates) members = [static_mesh_name] members += instance.data["collisionMembers"] diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index d15d52f3bd..1ecf436582 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -115,9 +115,12 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cls.log.error("{} is invalid".format(obj)) invalid.append(obj) else: + un_prefixed = combined_geometry_name[ + len(static_mesh_prefix) + 1: + ] expected_collision = "{}_{}".format( cl_m.group("prefix"), - combined_geometry_name[len(static_mesh_prefix) + 1:] + un_prefixed ) if not obj.startswith(expected_collision): @@ -130,7 +133,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cl_m.group("prefix"), cl_m.group("renderName"), cl_m.group("prefix"), - combined_geometry_name, + un_prefixed, )) invalid.append(obj) From bc596899f5b96e27c9b1e3937384d18c4462ff3e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 10 Mar 2022 23:45:40 +0100 Subject: [PATCH 17/67] fixed fbx settings --- openpype/hosts/maya/api/fbx.py | 6 +++--- .../maya/plugins/publish/collect_unreal_skeletalmesh.py | 5 ----- .../hosts/maya/plugins/publish/collect_unreal_staticmesh.py | 5 ----- openpype/plugins/publish/collect_resources_path.py | 5 ++++- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 92683da51b..f8fe189589 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -84,10 +84,10 @@ class FBXExtractor: return { "cameras": False, - "smoothingGroups": False, + "smoothingGroups": True, "hardEdges": False, "tangents": False, - "smoothMesh": False, + "smoothMesh": True, "instances": False, "bakeComplexAnimation": True, "bakeComplexStart": start_frame, @@ -101,7 +101,7 @@ class FBXExtractor: "skins": True, "constraints": False, "lights": True, - "embeddedTextures": True, + "embeddedTextures": False, "inputConnections": True, "upAxis": "y", "triangulate": False diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py index 7d479b706c..2b176e3a6d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py @@ -13,11 +13,6 @@ class CollectUnrealSkeletalMesh(pyblish.api.InstancePlugin): families = ["skeletalMesh"] def process(self, instance): - # set fbx overrides on instance - instance.data["smoothingGroups"] = True - instance.data["smoothMesh"] = True - instance.data["triangulate"] = True - frame = cmds.currentTime(query=True) instance.data["frameStart"] = frame instance.data["frameEnd"] = frame diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 02f21187b3..faa5880e43 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -44,11 +44,6 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): self.log.info("collisions: {}".format( pformat(instance.data.get("collisionMembers")))) - # set fbx overrides on instance - instance.data["smoothingGroups"] = True - instance.data["smoothMesh"] = True - instance.data["triangulate"] = True - frame = cmds.currentTime(query=True) instance.data["frameStart"] = frame instance.data["frameEnd"] = frame diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index fa181301ee..1f509365c7 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -53,7 +53,10 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "textures", "action", "background", - "effect" + "effect", + "staticMesh", + "skeletalMesh" + ] def process(self, instance): From 52dd76158fab048bd5ca8b9b1435b483aaa3c205 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Mar 2022 19:08:52 +0100 Subject: [PATCH 18/67] simplify whole process --- .../publish/collect_unreal_staticmesh.py | 24 +------ .../publish/extract_unreal_staticmesh.py | 62 ++++++------------- .../validate_unreal_staticmesh_naming.py | 14 +---- 3 files changed, 24 insertions(+), 76 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index faa5880e43..728a26931b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from maya import cmds # noqa import pyblish.api -from avalon.api import Session -from openpype.api import get_project_settings from pprint import pformat @@ -14,28 +12,12 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): families = ["staticMesh"] def process(self, instance): - project_settings = get_project_settings(Session["AVALON_PROJECT"]) - sm_prefix = ( - project_settings - ["maya"] - ["create"] - ["CreateUnrealStaticMesh"] - ["static_mesh_prefix"] - ) - # take the name from instance (without the `staticMesh_` prefix) - instance.data["staticMeshCombinedName"] = "{}_{}".format( - sm_prefix, - instance.data.get("subset")[len(sm_prefix) + 1:]) - - self.log.info("joined mesh name: {}".format( - instance.data.get("staticMeshCombinedName"))) - geometry_set = [i for i in instance if i == "geometry_SET"] - instance.data["membersToCombine"] = cmds.sets( + instance.data["geometryMembers"] = cmds.sets( geometry_set, query=True) - self.log.info("joining meshes: {}".format( - pformat(instance.data.get("membersToCombine")))) + self.log.info("geometry: {}".format( + pformat(instance.data.get("geometryMembers")))) collision_set = [i for i in instance if i == "collisions_SET"] instance.data["collisionMembers"] = cmds.sets( diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 987370d395..02dd5dc572 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -22,56 +22,30 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): families = ["staticMesh"] def process(self, instance): - to_combine = instance.data.get("membersToCombine") - static_mesh_name = instance.data.get("staticMeshCombinedName") - duplicates = [] + geo = instance.data.get("geometryMembers", []) + members = geo + instance.data.get("collisionMembers", []) - # delete created temporary nodes after extraction - with delete_after() as delete_bin: - # if we have more objects, combine them into one - # or just duplicate the single one - if len(to_combine) > 1: - self.log.info( - "merging {} into {}".format( - " + ".join(to_combine), static_mesh_name)) - duplicates = cmds.duplicate(to_combine, ic=True) - cmds.polyUnite( - *duplicates, - n=static_mesh_name, ch=False) - else: - self.log.info( - "duplicating {} to {} for export".format( - to_combine[0], static_mesh_name) - ) - cmds.duplicate(to_combine[0], name=static_mesh_name, ic=True) + fbx_exporter = fbx.FBXExtractor(log=self.log) - delete_bin.extend([static_mesh_name]) - # delete_bin.extend(duplicates) + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(staging_dir, filename) - members = [static_mesh_name] - members += instance.data["collisionMembers"] + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') - fbx_exporter = fbx.FBXExtractor(log=self.log) + self.log.info("Extracting FBX to: {0}".format(path)) + self.log.info("Members: {0}".format(members)) + self.log.info("Instance: {0}".format(instance[:])) - # Define output path - staging_dir = self.staging_dir(instance) - filename = "{0}.fbx".format(instance.name) - path = os.path.join(staging_dir, filename) + fbx_exporter.set_options_from_instance(instance) - # The export requires forward slashes because we need - # to format it into a string in a mel expression - path = path.replace('\\', '/') - - self.log.info("Extracting FBX to: {0}".format(path)) - self.log.info("Members: {0}".format(members)) - self.log.info("Instance: {0}".format(instance[:])) - - fbx_exporter.set_options_from_instance(instance) - - with maintained_selection(): - with root_parent(members): - self.log.info("Un-parenting: {}".format(members)) - fbx_exporter.export(members, path) + with maintained_selection(): + with root_parent(members): + self.log.info("Un-parenting: {}".format(members)) + fbx_exporter.export(members, path) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 1ecf436582..920e0982dc 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -79,18 +79,13 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): ["static_mesh_prefix"] ) - to_combine = instance.data.get("membersToCombine") - if not to_combine: - raise ValueError("Missing geometry to export.") - combined_geometry_name = instance.data.get( - "staticMeshCombinedName", None) if cls.validate_mesh: # compile regex for testing names regex_mesh = "{}{}".format( ("_" + cls.static_mesh_prefix) or "", cls.regex_mesh ) sm_r = re.compile(regex_mesh) - if not sm_r.match(combined_geometry_name): + if not sm_r.match(instance.data.get("subset")): cls.log.error("Mesh doesn't comply with name validation.") return True @@ -115,12 +110,9 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cls.log.error("{} is invalid".format(obj)) invalid.append(obj) else: - un_prefixed = combined_geometry_name[ - len(static_mesh_prefix) + 1: - ] expected_collision = "{}_{}".format( cl_m.group("prefix"), - un_prefixed + instance.data.get("subset") ) if not obj.startswith(expected_collision): @@ -133,7 +125,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cl_m.group("prefix"), cl_m.group("renderName"), cl_m.group("prefix"), - un_prefixed, + instance.data.get("subset"), )) invalid.append(obj) From 87b256387d453ce760a13fd6151a67436e7b6ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 14 Mar 2022 22:30:26 +0100 Subject: [PATCH 19/67] fix for multiple subsets --- .../maya/plugins/publish/collect_unreal_staticmesh.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py index 728a26931b..79d0856fa0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py @@ -12,14 +12,20 @@ class CollectUnrealStaticMesh(pyblish.api.InstancePlugin): families = ["staticMesh"] def process(self, instance): - geometry_set = [i for i in instance if i == "geometry_SET"] + geometry_set = [ + i for i in instance + if i.startswith("geometry_SET") + ] instance.data["geometryMembers"] = cmds.sets( geometry_set, query=True) self.log.info("geometry: {}".format( pformat(instance.data.get("geometryMembers")))) - collision_set = [i for i in instance if i == "collisions_SET"] + collision_set = [ + i for i in instance + if i.startswith("collisions_SET") + ] instance.data["collisionMembers"] = cmds.sets( collision_set, query=True) From d31bee0c3e1a1c368dc099cc420a199c67919f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 15 Mar 2022 00:41:46 +0100 Subject: [PATCH 20/67] fix parenting for skeletal meshes --- openpype/hosts/maya/api/fbx.py | 2 +- openpype/hosts/maya/api/lib.py | 20 ++++++++++++++++--- .../publish/extract_unreal_skeletalmesh.py | 19 ++++++++++++------ .../publish/extract_unreal_staticmesh.py | 4 ++-- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index f8fe189589..260241f5fc 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -198,5 +198,5 @@ class FBXExtractor: path (str): Path to use for export. """ - cmds.select(members, r=1, noExpand=True) + cmds.select(members, r=True, noExpand=True) mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 41c67a6209..a5199d8443 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3085,11 +3085,20 @@ def set_colorspace(): @contextlib.contextmanager -def root_parent(nodes): - # type: (list) -> list +def parent_nodes(nodes, parent=None): + # type: (list, str) -> list """Context manager to un-parent provided nodes and return them back.""" import pymel.core as pm # noqa + parent_node = None + delete_parent = False + + if parent: + if not cmds.objExists(parent): + parent_node = pm.createNode("transform", n=parent, ss=False) + delete_parent = True + else: + parent_node = pm.PyNode(parent) node_parents = [] for node in nodes: n = pm.PyNode(node) @@ -3100,9 +3109,14 @@ def root_parent(nodes): node_parents.append((n, root)) try: for node in node_parents: - node[0].setParent(world=True) + if not parent: + node[0].setParent(world=True) + else: + node[0].setParent(parent_node) yield finally: for node in node_parents: if node[1]: node[0].setParent(node[1]) + if delete_parent: + pm.delete(parent_node) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index c0b408c3f0..6f4c70fc07 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -7,9 +7,8 @@ from maya import cmds # noqa import pyblish.api import openpype.api from openpype.hosts.maya.api.lib import ( - root_parent, - maintained_selection, - delete_after + parent_nodes, + maintained_selection ) from openpype.hosts.maya.api import fbx @@ -43,10 +42,18 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): self.log.info("Instance: {0}".format(instance[:])) fbx_exporter.set_options_from_instance(instance) + + parent = "{}{}".format( + instance.data["asset"], + instance.data.get("variant", "") + ) with maintained_selection(): - with root_parent(to_extract): - rooted = [i.split("|")[-1] for i in to_extract] - self.log.info("Un-parenting: {}".format(to_extract)) + with parent_nodes(to_extract, parent=parent): + rooted = [ + "{}|{}".format(parent, i.split("|")[-1]) + for i in to_extract + ] + self.log.info("Un-parenting: {}".format(rooted, path)) fbx_exporter.export(rooted, path) if "representations" not in instance.data: diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 02dd5dc572..c3cc322a29 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -7,7 +7,7 @@ from maya import cmds # noqa import pyblish.api import openpype.api from openpype.hosts.maya.api.lib import ( - root_parent, + parent_nodes, maintained_selection, delete_after ) @@ -43,7 +43,7 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): fbx_exporter.set_options_from_instance(instance) with maintained_selection(): - with root_parent(members): + with parent_nodes(members): self.log.info("Un-parenting: {}".format(members)) fbx_exporter.export(members, path) From 08370f53e564174c87f1dd9316ecb2dc53898f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 15 Mar 2022 01:01:14 +0100 Subject: [PATCH 21/67] fix getting correct name with prefix --- .../plugins/publish/validate_unreal_staticmesh_naming.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 920e0982dc..c0eeb82688 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -104,6 +104,9 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): cl_r = re.compile(regex_collision) + mesh_name = "{}{}".format(instance.data["asset"], + instance.data.get("variant", [])) + for obj in collision_set: cl_m = cl_r.match(obj) if not cl_m: @@ -112,7 +115,7 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): else: expected_collision = "{}_{}".format( cl_m.group("prefix"), - instance.data.get("subset") + mesh_name ) if not obj.startswith(expected_collision): @@ -121,11 +124,11 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): "Collision object name doesn't match " "static mesh name" ) - cls.log.error("{}_{} != {}_{}".format( + cls.log.error("{}_{} != {}_{}*".format( cl_m.group("prefix"), cl_m.group("renderName"), cl_m.group("prefix"), - instance.data.get("subset"), + mesh_name, )) invalid.append(obj) From cc7a5e0a6fddfb7d0d55dae0f3445251f4d9c5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 22 Mar 2022 15:03:25 +0100 Subject: [PATCH 22/67] node renaming wip --- .../publish/extract_unreal_skeletalmesh.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 6f4c70fc07..58154638e6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Create Unreal Skeletal Mesh data to be extracted as FBX.""" import os +from contextlib import contextmanager from maya import cmds # noqa @@ -12,6 +13,16 @@ from openpype.hosts.maya.api.lib import ( ) from openpype.hosts.maya.api import fbx +@contextmanager +def renamed(original_name, renamed_name): + # type: (str, str) -> None + try: + cmds.rename(original_name, renamed_name) + yield + finally: + cmds.rename(renamed_name, original_name) + yield + class ExtractUnrealSkeletalMesh(openpype.api.Extractor): """Extract Unreal Skeletal Mesh as FBX from Maya. """ @@ -48,13 +59,14 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): instance.data.get("variant", "") ) with maintained_selection(): - with parent_nodes(to_extract, parent=parent): - rooted = [ - "{}|{}".format(parent, i.split("|")[-1]) - for i in to_extract - ] - self.log.info("Un-parenting: {}".format(rooted, path)) - fbx_exporter.export(rooted, path) + with renamed() + with parent_nodes(to_extract, parent=parent): + rooted = [ + "{}|{}".format(parent, i.split("|")[-1]) + for i in to_extract + ] + self.log.info("Un-parenting: {}".format(rooted, path)) + fbx_exporter.export(rooted, path) if "representations" not in instance.data: instance.data["representations"] = [] From 7651ebd8521fd7f2408aaebb262a877ca8840d81 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Mar 2022 16:40:11 +0100 Subject: [PATCH 23/67] added option to save as to current context --- openpype/tools/workfiles/files_widget.py | 82 +++++++++++++++++------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index d2b8a76952..74729e5346 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -155,18 +155,33 @@ class FilesWidget(QtWidgets.QWidget): # Home Page # Build buttons widget for files widget btns_widget = QtWidgets.QWidget(self) - btn_save = QtWidgets.QPushButton("Save As", btns_widget) - btn_browse = QtWidgets.QPushButton("Browse", btns_widget) - btn_open = QtWidgets.QPushButton("Open", btns_widget) - btn_view_published = QtWidgets.QPushButton("View", btns_widget) + workarea_btns_widget = QtWidgets.QWidget(btns_widget) + btn_save = QtWidgets.QPushButton("Save As", workarea_btns_widget) + btn_browse = QtWidgets.QPushButton("Browse", workarea_btns_widget) + btn_open = QtWidgets.QPushButton("Open", workarea_btns_widget) + + workarea_btns_layout = QtWidgets.QHBoxLayout(workarea_btns_widget) + workarea_btns_layout.setContentsMargins(0, 0, 0, 0) + workarea_btns_layout.addWidget(btn_open, 1) + workarea_btns_layout.addWidget(btn_browse, 1) + workarea_btns_layout.addWidget(btn_save, 1) + + publish_btns_widget = QtWidgets.QWidget(btns_widget) + btn_view_published = QtWidgets.QPushButton("View", publish_btns_widget) + btn_save_as_published = QtWidgets.QPushButton( + "Save As", publish_btns_widget + ) + + publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) + publish_btns_layout.setContentsMargins(0, 0, 0, 0) + publish_btns_layout.addWidget(btn_view_published, 1) + publish_btns_layout.addWidget(btn_save_as_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) - btns_layout.addWidget(btn_open, 1) - btns_layout.addWidget(btn_browse, 1) - btns_layout.addWidget(btn_save, 1) - btns_layout.addWidget(btn_view_published, 1) + btns_layout.addWidget(workarea_btns_widget, 1) + btns_layout.addWidget(publish_btns_widget, 1) # Build files widgets for home page main_layout = QtWidgets.QVBoxLayout(self) @@ -189,13 +204,16 @@ class FilesWidget(QtWidgets.QWidget): self.on_file_select ) publish_files_view.doubleClickedLeft.connect( - self._on_view_published_pressed + self._on_published_view_pressed ) btn_open.pressed.connect(self._on_workarea_open_pressed) btn_browse.pressed.connect(self.on_browse_pressed) - btn_save.pressed.connect(self.on_save_as_pressed) - btn_view_published.pressed.connect(self._on_view_published_pressed) + btn_save.pressed.connect(self._on_save_as_pressed) + btn_view_published.pressed.connect(self._on_published_view_pressed) + btn_save_as_published.pressed.connect( + self._on_published_save_as_pressed + ) # Store attributes self._published_checkbox = published_checkbox @@ -211,7 +229,8 @@ class FilesWidget(QtWidgets.QWidget): self._publish_files_model = publish_files_model self._publish_proxy_model = publish_proxy_model - self._btns_widget = btns_widget + self._workarea_btns_widget = workarea_btns_widget + self._publish_btns_widget = publish_btns_widget self._btn_open = btn_open self._btn_browse = btn_browse self._btn_save = btn_save @@ -222,7 +241,7 @@ class FilesWidget(QtWidgets.QWidget): # Hide publish files widgets publish_files_view.setVisible(False) - btn_view_published.setVisible(False) + publish_btns_widget.setVisible(False) @property def published_enabled(self): @@ -232,12 +251,10 @@ class FilesWidget(QtWidgets.QWidget): published_enabled = self.published_enabled self._workarea_files_view.setVisible(not published_enabled) - self._btn_open.setVisible(not published_enabled) - self._btn_browse.setVisible(not published_enabled) - self._btn_save.setVisible(not published_enabled) + self._workarea_btns_widget.setVisible(not published_enabled) self._publish_files_view.setVisible(published_enabled) - self._btn_view_published.setVisible(published_enabled) + self._publish_btns_widget.setVisible(published_enabled) self._update_filtering() self._update_asset_task() @@ -462,11 +479,16 @@ class FilesWidget(QtWidgets.QWidget): if work_file: self.open_file(work_file) - def on_save_as_pressed(self): + def _on_save_as_pressed(self): + self._save_as_with_dialog() + + def _save_as_with_dialog(self): work_filename = self.get_filename() if not work_filename: return + src_path = self._get_selected_filepath() + # Trigger before save event emit_event( "workfile.save.before", @@ -486,13 +508,20 @@ class FilesWidget(QtWidgets.QWidget): log.debug("Initializing Work Directory: %s", self._workfiles_root) os.makedirs(self._workfiles_root) - # Update session if context has changed - self._enter_session() # Prepare full path to workfile and save it filepath = os.path.join( os.path.normpath(self._workfiles_root), work_filename ) - self.host.save_file(filepath) + + # Update session if context has changed + self._enter_session() + + if not self.published_enabled: + self.host.save_file(filepath) + else: + shutil.copy(src_path, filepath) + self.host.open_file(filepath) + # Create extra folders create_workdir_extra_folders( self._workdir_path, @@ -510,9 +539,12 @@ class FilesWidget(QtWidgets.QWidget): self.workfile_created.emit(filepath) # Refresh files model - self.refresh() + if self.published_enabled: + self._published_checkbox.setChecked(False) + else: + self.refresh() - def _on_view_published_pressed(self): + def _on_published_view_pressed(self): filepath = self._get_selected_filepath() if not filepath or not os.path.exists(filepath): return @@ -522,6 +554,10 @@ class FilesWidget(QtWidgets.QWidget): # Change state back to workarea self._published_checkbox.setChecked(False) + def _on_published_save_as_pressed(self): + self._save_as_with_dialog() + + def on_file_select(self): self.file_selected.emit(self._get_selected_filepath()) From bf0bc2436e1f0a085ca30381e2b5285cadf70320 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Mar 2022 16:46:38 +0100 Subject: [PATCH 24/67] added option to save as to context --- openpype/tools/workfiles/files_widget.py | 81 +++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 74729e5346..1faafe2bdb 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -172,11 +172,23 @@ class FilesWidget(QtWidgets.QWidget): btn_save_as_published = QtWidgets.QPushButton( "Save As", publish_btns_widget ) + btn_save_as_to_published = QtWidgets.QPushButton( + "Save As (to context)", publish_btns_widget + ) + btn_select_context_published = QtWidgets.QPushButton( + "Select context", publish_btns_widget + ) + btn_cancel_published = QtWidgets.QPushButton( + "Cancel", publish_btns_widget + ) publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) publish_btns_layout.setContentsMargins(0, 0, 0, 0) publish_btns_layout.addWidget(btn_view_published, 1) publish_btns_layout.addWidget(btn_save_as_published, 1) + publish_btns_layout.addWidget(btn_save_as_to_published, 1) + publish_btns_layout.addWidget(btn_cancel_published, 1) + publish_btns_layout.addWidget(btn_select_context_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) @@ -214,6 +226,15 @@ class FilesWidget(QtWidgets.QWidget): btn_save_as_published.pressed.connect( self._on_published_save_as_pressed ) + btn_save_as_to_published.pressed.connect( + self._on_publish_save_as_to_pressed + ) + btn_select_context_published.pressed.connect( + self._on_publish_select_context_pressed + ) + btn_cancel_published.pressed.connect( + self._on_publish_cancel_pressed + ) # Store attributes self._published_checkbox = published_checkbox @@ -234,7 +255,12 @@ class FilesWidget(QtWidgets.QWidget): self._btn_open = btn_open self._btn_browse = btn_browse self._btn_save = btn_save + self._btn_view_published = btn_view_published + self._btn_save_as_published = btn_save_as_published + self._btn_save_as_to_published = btn_save_as_to_published + self._btn_select_context_published = btn_select_context_published + self._btn_cancel_published = btn_cancel_published # Create a proxy widget for files widget self.setFocusProxy(btn_open) @@ -242,6 +268,10 @@ class FilesWidget(QtWidgets.QWidget): # Hide publish files widgets publish_files_view.setVisible(False) publish_btns_widget.setVisible(False) + btn_select_context_published.setVisible(False) + btn_cancel_published.setVisible(False) + + self._publish_context_select_mode = False @property def published_enabled(self): @@ -285,12 +315,15 @@ class FilesWidget(QtWidgets.QWidget): self._update_asset_task() def _update_asset_task(self): - if self.published_enabled: + if self.published_enabled and not self._publish_context_select_mode: self._publish_files_model.set_context( self._asset_id, self._task_name ) has_valid_items = self._publish_files_model.has_valid_items() self._btn_view_published.setEnabled(has_valid_items) + self._btn_save_as_published.setEnabled(has_valid_items) + self._btn_save_as_to_published.setEnabled(has_valid_items) + else: # Define a custom session so we can query the work root # for a "Work area" that is not our current Session. @@ -308,6 +341,13 @@ class FilesWidget(QtWidgets.QWidget): has_valid_items = self._workarea_files_model.has_valid_items() self._btn_browse.setEnabled(has_valid_items) self._btn_open.setEnabled(has_valid_items) + + if self._publish_context_select_mode: + self._btn_select_context_published.setEnabled( + bool(self._asset_id) and bool(self._task_name) + ) + return + # Manually trigger file selection if not has_valid_items: self.on_file_select() @@ -557,6 +597,45 @@ class FilesWidget(QtWidgets.QWidget): def _on_published_save_as_pressed(self): self._save_as_with_dialog() + def _set_publish_context_select_mode(self, enabled): + self._publish_context_select_mode = enabled + + # Show buttons related to context selection + self._btn_cancel_published.setVisible(enabled) + self._btn_select_context_published.setVisible(enabled) + # Change enabled state based on select context + self._btn_select_context_published.setEnabled( + bool(self._asset_id) and bool(self._task_name) + ) + + self._btn_view_published.setVisible(not enabled) + self._btn_save_as_published.setVisible(not enabled) + self._btn_save_as_to_published.setVisible(not enabled) + + # Change views and disable workarea view if enabled + self._workarea_files_view.setEnabled(not enabled) + if self.published_enabled: + self._workarea_files_view.setVisible(enabled) + self._publish_files_view.setVisible(not enabled) + else: + self._workarea_files_view.setVisible(True) + self._publish_files_view.setVisible(False) + + # Disable filter widgets + self._published_checkbox.setEnabled(not enabled) + self._filter_input.setEnabled(not enabled) + + def _on_publish_save_as_to_pressed(self): + self._set_publish_context_select_mode(True) + + def _on_publish_select_context_pressed(self): + self._save_as_with_dialog() + self._set_publish_context_select_mode(False) + self._update_asset_task() + + def _on_publish_cancel_pressed(self): + self._set_publish_context_select_mode(False) + self._update_asset_task() def on_file_select(self): self.file_selected.emit(self._get_selected_filepath()) From 8f77e92d6f456e06c98326c4d0c55b6cb4f70208 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Mar 2022 16:48:16 +0100 Subject: [PATCH 25/67] rename top node for variants --- .../publish/extract_unreal_skeletalmesh.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 58154638e6..5b0eb5a3bc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -13,6 +13,7 @@ from openpype.hosts.maya.api.lib import ( ) from openpype.hosts.maya.api import fbx + @contextmanager def renamed(original_name, renamed_name): # type: (str, str) -> None @@ -21,7 +22,6 @@ def renamed(original_name, renamed_name): yield finally: cmds.rename(renamed_name, original_name) - yield class ExtractUnrealSkeletalMesh(openpype.api.Extractor): @@ -42,6 +42,8 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): geo = instance.data.get("geometry") joints = instance.data.get("joints") + joints_parent = cmds.listRelatives(joints, p=True) + to_extract = geo + joints # The export requires forward slashes because we need @@ -54,19 +56,30 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): fbx_exporter.set_options_from_instance(instance) + # This magic is done for variants. To let Unreal merge correctly + # existing data, top node must have the same name. So for every + # variant we extract we need to rename top node of the rig correctly. + # It is finally done in context manager so it won't affect current + # scene. parent = "{}{}".format( instance.data["asset"], instance.data.get("variant", "") ) - with maintained_selection(): - with renamed() - with parent_nodes(to_extract, parent=parent): - rooted = [ - "{}|{}".format(parent, i.split("|")[-1]) - for i in to_extract - ] - self.log.info("Un-parenting: {}".format(rooted, path)) - fbx_exporter.export(rooted, path) + + renamed_to_extract = [] + for node in to_extract: + node_path = node.split("|") + node_path[1] = parent + renamed_to_extract.append("|".join(node_path)) + + with renamed(joints_parent, parent): + with parent_nodes(renamed_to_extract, parent=parent): + rooted = [ + "{}|{}".format(parent, i.split("|")[-1]) + for i in renamed_to_extract + ] + self.log.info("Un-parenting: {}".format(rooted, path)) + fbx_exporter.export(rooted, path) if "representations" not in instance.data: instance.data["representations"] = [] From 76bc7799f1dbcc993b5dbd2868f9309bd3e7e234 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 25 Mar 2022 10:52:54 +0100 Subject: [PATCH 26/67] add top node validator --- .../publish/extract_unreal_staticmesh.py | 5 +-- .../help/validate_skeletalmesh_hierarchy.xml | 14 ++++++++ .../validate_skeletalmesh_hierarchy.py | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/help/validate_skeletalmesh_hierarchy.xml create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index c3cc322a29..92fa1b5933 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -22,8 +22,9 @@ class ExtractUnrealStaticMesh(openpype.api.Extractor): families = ["staticMesh"] def process(self, instance): - geo = instance.data.get("geometryMembers", []) - members = geo + instance.data.get("collisionMembers", []) + members = instance.data.get("geometryMembers", []) + if instance.data.get("collisionMembers"): + members = members + instance.data.get("collisionMembers") fbx_exporter = fbx.FBXExtractor(log=self.log) diff --git a/openpype/hosts/maya/plugins/publish/help/validate_skeletalmesh_hierarchy.xml b/openpype/hosts/maya/plugins/publish/help/validate_skeletalmesh_hierarchy.xml new file mode 100644 index 0000000000..d30c4cb69d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/help/validate_skeletalmesh_hierarchy.xml @@ -0,0 +1,14 @@ + + + +Skeletal Mesh Top Node +## Skeletal meshes needs common root + +Skeletal meshes and their joints must be under one common root. + +### How to repair? + +Make sure all geometry and joints resides under same root. + + + diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py new file mode 100644 index 0000000000..dda7e063f6 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import pyblish.api +import openpype.api +from openpype.pipeline import PublishXmlValidationError + +from maya import cmds + + +class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): + """Adheres to the content of 'model' family + + - Must have one top group. (configurable) + - Must only contain: transforms, meshes and groups + + """ + + order = openpype.api.ValidateContentsOrder + hosts = ["maya"] + families = ["skeletalMesh"] + label = "Skeletal Mesh Top Node" + + def process(self, instance): + geo = instance.data.get("geometry") + joints = instance.data.get("joints") + joints_parents = cmds.ls(joints, long=True)[0].split("|")[1:-1] + geo_parents = cmds.ls(geo, long=True)[0].split("|")[1:-1] + + self.log.info(joints_parents) + self.log.info(geo_parents) + self.log.info(set(joints_parents + geo_parents)) + + if len(set(joints_parents + geo_parents)) != 1: + raise PublishXmlValidationError( + self, + "Multiple roots on geometry or joints." + ) From b711ba51745768809cdbc0c13c8fad0d67da71b9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Sun, 27 Mar 2022 23:48:24 +0200 Subject: [PATCH 27/67] fix validator --- .../publish/validate_skeletalmesh_hierarchy.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py index dda7e063f6..cffbd13834 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -22,14 +22,17 @@ class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): def process(self, instance): geo = instance.data.get("geometry") joints = instance.data.get("joints") - joints_parents = cmds.ls(joints, long=True)[0].split("|")[1:-1] - geo_parents = cmds.ls(geo, long=True)[0].split("|")[1:-1] + # joints_parents = cmds.ls(joints, long=True)[0].split("|")[1:-1] + # geo_parents = cmds.ls(geo, long=True)[0].split("|")[1:-1] - self.log.info(joints_parents) - self.log.info(geo_parents) - self.log.info(set(joints_parents + geo_parents)) + joints_parents = cmds.ls(joints, long=True) + geo_parents = cmds.ls(geo, long=True) - if len(set(joints_parents + geo_parents)) != 1: + parents_set = { + parent.split("|")[1] for parent in (joints_parents + geo_parents) + } + + if len(set(parents_set)) != 1: raise PublishXmlValidationError( self, "Multiple roots on geometry or joints." From 3a55b806345ec823177f30796ba018d860aea33d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 28 Mar 2022 00:07:15 +0200 Subject: [PATCH 28/67] fix docstring and remove unused code --- .../plugins/publish/validate_skeletalmesh_hierarchy.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py index cffbd13834..54a86d27cf 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeletalmesh_hierarchy.py @@ -7,12 +7,7 @@ from maya import cmds class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): - """Adheres to the content of 'model' family - - - Must have one top group. (configurable) - - Must only contain: transforms, meshes and groups - - """ + """Validates that nodes has common root.""" order = openpype.api.ValidateContentsOrder hosts = ["maya"] @@ -22,8 +17,6 @@ class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin): def process(self, instance): geo = instance.data.get("geometry") joints = instance.data.get("joints") - # joints_parents = cmds.ls(joints, long=True)[0].split("|")[1:-1] - # geo_parents = cmds.ls(geo, long=True)[0].split("|")[1:-1] joints_parents = cmds.ls(joints, long=True) geo_parents = cmds.ls(geo, long=True) From 228d3cfc004b21398ee0fb381ed8ea5fa28e6826 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 28 Mar 2022 01:54:40 +0200 Subject: [PATCH 29/67] fix hierarchy --- .../publish/extract_unreal_skeletalmesh.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 5b0eb5a3bc..98dbf117dc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -66,20 +66,22 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): instance.data.get("variant", "") ) + joints_parents = cmds.ls(joints, long=True) + geo_parents = cmds.ls(geo, long=True) + + parent_node = { + parent.split("|")[1] for parent in (joints_parents + geo_parents) + }.pop() + renamed_to_extract = [] for node in to_extract: node_path = node.split("|") node_path[1] = parent renamed_to_extract.append("|".join(node_path)) - with renamed(joints_parent, parent): - with parent_nodes(renamed_to_extract, parent=parent): - rooted = [ - "{}|{}".format(parent, i.split("|")[-1]) - for i in renamed_to_extract - ] - self.log.info("Un-parenting: {}".format(rooted, path)) - fbx_exporter.export(rooted, path) + with renamed(parent_node, parent): + self.log.info("Extracting: {}".format(renamed_to_extract, path)) + fbx_exporter.export(renamed_to_extract, path) if "representations" not in instance.data: instance.data["representations"] = [] From 461bf75d660cc60aeae3401bf413496cd71b7e17 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 29 Mar 2022 23:11:29 +0200 Subject: [PATCH 30/67] texture publishing initial commit --- .../validate_simple_unreal_texture_naming.py | 15 +++++++++++++++ openpype/plugins/publish/integrate_new.py | 3 ++- .../defaults/project_anatomy/templates.json | 11 ++++++++++- .../defaults/project_settings/global.json | 11 +++++++++++ .../project_settings/standalonepublisher.json | 11 ++++++++++- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py new file mode 100644 index 0000000000..158a749075 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +"""Validator for correct file naming.""" +import pyblish.api +import openpype.api +from openpype.pipeline import PublishXmlValidationError + + +class ValidateSimpleUnrealTextureNaming(pyblish.api.InstancePlugin): + label = "Validate Unreal Texture Names" + hosts = ["standalonepublisher"] + families = ["simpleUnrealTexture"] + order = openpype.api.ValidateContentsOrder + + def process(self, instance): + ... \ No newline at end of file diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 2304f98713..4025f18cb2 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -107,7 +107,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "hda", "usd", "usdComposition", - "usdOverride" + "usdOverride", + "simpleUnrealTexture" ] exclude_families = ["clip"] db_representation_context_keys = [ diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index d46d449c77..45611f55b1 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -32,5 +32,14 @@ "file": "{subset}_{@version}<_{output}><.{@frame}>.{ext}", "path": "{@folder}/{@file}" }, - "others": {} + "others": { + "simpleUnrealTexture": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}", + "file": "{original_file}", + "path": "{@folder}/{@file}" + }, + "__dynamic_keys_labels__": { + "simpleUnrealTexture": "Simple Unreal Texture" + } + } } \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 30a71b044a..f0fa09957e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -192,6 +192,17 @@ "task_types": [], "tasks": [], "template_name": "render" + }, + { + "families": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "tasks": [], + "template_name": "simpleUnrealTexture" } ], "subset_grouping_profiles": [ diff --git a/openpype/settings/defaults/project_settings/standalonepublisher.json b/openpype/settings/defaults/project_settings/standalonepublisher.json index 6858c4f34d..bc91a5ea8a 100644 --- a/openpype/settings/defaults/project_settings/standalonepublisher.json +++ b/openpype/settings/defaults/project_settings/standalonepublisher.json @@ -133,6 +133,14 @@ ], "help": "Texture files with UDIM together with worfile" }, + "create_simple_unreal_texture": { + "name": "simple_unreal_texture", + "label": "Simple Unreal Texture", + "family": "simpleUnrealTexture", + "icon": "Image", + "defaults": [], + "help": "Texture files with Unreal naming convention" + }, "__dynamic_keys_labels__": { "create_workfile": "Workfile", "create_model": "Model", @@ -145,7 +153,8 @@ "create_matchmove": "Matchmove", "create_render": "Render", "create_mov_batch": "Batch Mov", - "create_texture_batch": "Batch Texture" + "create_texture_batch": "Batch Texture", + "create_simple_unreal_texture": "Simple Unreal Texture" } }, "publish": { From 10673544a481e34ebde96dccc3480fb03ff47452 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 11:51:32 +0200 Subject: [PATCH 31/67] Fix broken links --- website/docs/manager_ftrack.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/manager_ftrack.md b/website/docs/manager_ftrack.md index defbb4b48f..1b1c220c18 100644 --- a/website/docs/manager_ftrack.md +++ b/website/docs/manager_ftrack.md @@ -4,7 +4,7 @@ title: Ftrack sidebar_label: Project Manager --- -Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://ftrack.rtd.ftrack.com/en/stable/). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://help.ftrack.com/en/articles/1040483-creating-a-new-project). ## Project management Setting project attributes is the key to properly working pipeline. @@ -31,7 +31,7 @@ This process describes how data from Ftrack will get into Avalon database. ### How to synchronize You can trigger synchronization manually using [Sync To Avalon](manager_ftrack_actions.md#sync-to-avalon) action. -Synchronization can also be automated with OpenPype's [event server](#event-server) and synchronization events. If your Ftrack is [prepared for OpenPype](#prepare-ftrack-for-openpype), the project should have custom attribute `Avalon auto-sync`. Check the custom attribute to allow auto-updates with event server. +Synchronization can also be automated with OpenPype's [event server](#event-server) and synchronization events. If your Ftrack is [prepared for OpenPype](module_ftrack.md#prepare-ftrack-for-openpype), the project should have custom attribute `Avalon auto-sync`. Check the custom attribute to allow auto-updates with event server. :::tip Always use `Sync To Avalon` action before you enable `Avalon auto-sync`! From 62e98546e1826bb96bac90c365bb92ffadfd1d43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 30 Mar 2022 16:03:16 +0200 Subject: [PATCH 32/67] Update website/docs/manager_ftrack.md - add https to url Co-authored-by: Petr Kalis --- website/docs/manager_ftrack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/manager_ftrack.md b/website/docs/manager_ftrack.md index 1b1c220c18..730c57d1f9 100644 --- a/website/docs/manager_ftrack.md +++ b/website/docs/manager_ftrack.md @@ -4,7 +4,7 @@ title: Ftrack sidebar_label: Project Manager --- -Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](http://help.ftrack.com/en/articles/1040483-creating-a-new-project). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](https://help.ftrack.com/en/articles/1040483-creating-a-new-project). ## Project management Setting project attributes is the key to properly working pipeline. From 053748ac2319468a4071db41e3818d4923e105c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 30 Mar 2022 18:54:05 +0200 Subject: [PATCH 33/67] handling validation and hero versions --- .../publish/collect_original_basename.py | 18 ++++++++++++++++++ .../help/validate_simple_texture_naming.xml | 17 +++++++++++++++++ .../validate_simple_unreal_texture_naming.py | 10 +++++++++- .../plugins/publish/integrate_hero_version.py | 5 +++++ openpype/plugins/publish/integrate_new.py | 13 +++++++++++++ .../defaults/project_anatomy/templates.json | 10 ++++++++-- .../defaults/project_settings/global.json | 17 +++++++++++++++-- 7 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py create mode 100644 openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py new file mode 100644 index 0000000000..b83a924d33 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_original_basename.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Collect original base name for use in templates.""" +from pathlib import Path + +import pyblish.api + + +class CollectOriginalBasename(pyblish.api.InstancePlugin): + """Collect original file base name.""" + + order = pyblish.api.CollectorOrder + 0.498 + label = "Collect Base Name" + hosts = ["standalonepublisher"] + families = ["simpleUnrealTexture"] + + def process(self, instance): + file_name = Path(instance.data["representations"][0]["files"]) + instance.data["originalBasename"] = file_name.stem diff --git a/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml b/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml new file mode 100644 index 0000000000..1818748407 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml @@ -0,0 +1,17 @@ + + + +Invalid texture name + +## Source files not found + +Submitted file has invalid name: +'{invalid_file}' + +### How to repair? + + Texture file must adhere to naming conventions for Unreal: + T_[ASSET_NAME}_*.ext + + + \ No newline at end of file diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py index 158a749075..05f38159c1 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py @@ -2,6 +2,7 @@ """Validator for correct file naming.""" import pyblish.api import openpype.api +import re from openpype.pipeline import PublishXmlValidationError @@ -10,6 +11,13 @@ class ValidateSimpleUnrealTextureNaming(pyblish.api.InstancePlugin): hosts = ["standalonepublisher"] families = ["simpleUnrealTexture"] order = openpype.api.ValidateContentsOrder + regex = "^T_{asset}.*" def process(self, instance): - ... \ No newline at end of file + file_name = instance.data.get("originalBasename") + self.log.info(file_name) + pattern = self.regex.format(asset=instance.data.get("asset")) + if not re.match(pattern, file_name): + msg = f"Invalid file name {file_name}" + raise PublishXmlValidationError( + self, msg, formatting_data={"invalid_file": file_name}) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index d6df6535d8..ded149bdd0 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -485,6 +485,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): anatomy = instance.context.data["anatomy"] template_data = copy.deepcopy(instance.data["anatomyData"]) + if "originalBasename" in instance.data: + template_data.update({ + "originalBasename": instance.data.get("originalBasename") + }) + if "folder" in anatomy.templates[template_key]: anatomy_filled = anatomy.format(template_data) publish_folder = anatomy_filled[template_key]["folder"] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4025f18cb2..a3b9f0ef4a 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -356,6 +356,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if profile: template_name = profile["template_name"] + + published_representations = {} for idx, repre in enumerate(instance.data["representations"]): # reset transfers for next representation @@ -384,6 +386,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if resolution_width: template_data["fps"] = fps + if "originalBasename" in instance.data: + template_data.update({ + "originalBasename": instance.data.get("originalBasename") + }) + files = repre['files'] if repre.get('stagingDir'): stagingdir = repre['stagingDir'] @@ -555,6 +562,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre['published_path'] = dst self.log.debug("__ dst: {}".format(dst)) + if not instance.data.get("publishDir"): + instance.data["publishDir"] = ( + anatomy_filled + [template_name] + ["folder"] + ) if repre.get("udim"): repre_context["udim"] = repre.get("udim") # store list diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 45611f55b1..4be923da11 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -33,12 +33,18 @@ "path": "{@folder}/{@file}" }, "others": { + "simpleUnrealTextureHero": { + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/hero", + "file": "{originalBasename}.{ext}", + "path": "{@folder}/{@file}" + }, "simpleUnrealTexture": { - "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}", - "file": "{original_file}", + "folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{@version}", + "file": "{originalBasename}_{@version}.{ext}", "path": "{@folder}/{@file}" }, "__dynamic_keys_labels__": { + "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", "simpleUnrealTexture": "Simple Unreal Texture" } } diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0bfd571a79..5443293c93 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -213,9 +213,22 @@ "animation", "setdress", "layout", - "mayaScene" + "mayaScene", + "simpleUnrealTexture" ], - "template_name_profiles": [] + "template_name_profiles": [ + { + "families": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "simpleUnrealTextureHero" + } + ] }, "CleanUp": { "paterns": [], From 34f5f7d60c2692d99448d535ba0c4a688b844b59 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 31 Mar 2022 10:13:13 +0200 Subject: [PATCH 34/67] fix validator message --- .../plugins/publish/help/validate_simple_texture_naming.xml | 4 ++-- .../plugins/publish/validate_simple_unreal_texture_naming.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml b/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml index 1818748407..b65d274fe5 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml +++ b/openpype/hosts/standalonepublisher/plugins/publish/help/validate_simple_texture_naming.xml @@ -3,7 +3,7 @@ Invalid texture name -## Source files not found +## Invalid file name Submitted file has invalid name: '{invalid_file}' @@ -11,7 +11,7 @@ Submitted file has invalid name: ### How to repair? Texture file must adhere to naming conventions for Unreal: - T_[ASSET_NAME}_*.ext + T_{asset}_*.ext \ No newline at end of file diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py index 05f38159c1..ef8da9f280 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_simple_unreal_texture_naming.py @@ -20,4 +20,7 @@ class ValidateSimpleUnrealTextureNaming(pyblish.api.InstancePlugin): if not re.match(pattern, file_name): msg = f"Invalid file name {file_name}" raise PublishXmlValidationError( - self, msg, formatting_data={"invalid_file": file_name}) + self, msg, formatting_data={ + "invalid_file": file_name, + "asset": instance.data.get("asset") + }) From f486cef9251f18dcdfb390e8cfbaa840c6cda461 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 31 Mar 2022 12:03:48 +0200 Subject: [PATCH 35/67] Update manager_ftrack.md Update Ftrack Documentation URL --- website/docs/manager_ftrack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/manager_ftrack.md b/website/docs/manager_ftrack.md index 730c57d1f9..b5ca167838 100644 --- a/website/docs/manager_ftrack.md +++ b/website/docs/manager_ftrack.md @@ -4,7 +4,7 @@ title: Ftrack sidebar_label: Project Manager --- -Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](https://help.ftrack.com/en/articles/1040483-creating-a-new-project). +Ftrack is currently the main project management option for OpenPype. This documentation assumes that you are familiar with Ftrack and it's basic principles. If you're new to Ftrack, we recommend having a thorough look at [Ftrack Official Documentation](https://help.ftrack.com/en/). ## Project management Setting project attributes is the key to properly working pipeline. From eac1394be8b1644468f8b6427c5abe37bedd3f97 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:12:56 +0200 Subject: [PATCH 36/67] hide published checkbox if save is not enabled --- openpype/tools/workfiles/files_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 1faafe2bdb..1f93d15e22 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -305,6 +305,9 @@ class FilesWidget(QtWidgets.QWidget): def set_save_enabled(self, enabled): self._btn_save.setEnabled(enabled) + if not enabled and self._published_checkbox.isChecked(): + self._published_checkbox.setChecked(False) + self._published_checkbox.setVisible(enabled) def set_asset_task(self, asset_id, task_name, task_type): if asset_id != self._asset_id: From 8e382b9c52feb0bcc45c235a5121088552e7d01c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:17:19 +0200 Subject: [PATCH 37/67] removed View option from published files --- openpype/tools/workfiles/files_widget.py | 46 +--- openpype/tools/workfiles/lib.py | 272 ----------------------- openpype/tools/workfiles/window.py | 65 ++---- 3 files changed, 27 insertions(+), 356 deletions(-) delete mode 100644 openpype/tools/workfiles/lib.py diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 1f93d15e22..80a94cc1bd 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -26,7 +26,6 @@ from .model import ( DATE_MODIFIED_ROLE, ) from .save_as_dialog import SaveAsDialog -from .lib import TempPublishFiles log = logging.getLogger(__name__) @@ -49,7 +48,6 @@ class FilesWidget(QtWidgets.QWidget): """A widget displaying files that allows to save and open files.""" file_selected = QtCore.Signal(str) file_opened = QtCore.Signal() - publish_file_viewed = QtCore.Signal() workfile_created = QtCore.Signal(str) published_visible_changed = QtCore.Signal(bool) @@ -71,9 +69,6 @@ class FilesWidget(QtWidgets.QWidget): self._workfiles_root = None self._workdir_path = None self.host = api.registered_host() - temp_publish_files = TempPublishFiles() - temp_publish_files.cleanup() - self._temp_publish_files = temp_publish_files # Whether to automatically select the latest modified # file on a refresh of the files model. @@ -168,15 +163,14 @@ class FilesWidget(QtWidgets.QWidget): workarea_btns_layout.addWidget(btn_save, 1) publish_btns_widget = QtWidgets.QWidget(btns_widget) - btn_view_published = QtWidgets.QPushButton("View", publish_btns_widget) btn_save_as_published = QtWidgets.QPushButton( - "Save As", publish_btns_widget + "Copy & Open", publish_btns_widget ) - btn_save_as_to_published = QtWidgets.QPushButton( - "Save As (to context)", publish_btns_widget + btn_change_context = QtWidgets.QPushButton( + "Choose different context", publish_btns_widget ) btn_select_context_published = QtWidgets.QPushButton( - "Select context", publish_btns_widget + "Copy & Open", publish_btns_widget ) btn_cancel_published = QtWidgets.QPushButton( "Cancel", publish_btns_widget @@ -184,9 +178,8 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget) publish_btns_layout.setContentsMargins(0, 0, 0, 0) - publish_btns_layout.addWidget(btn_view_published, 1) publish_btns_layout.addWidget(btn_save_as_published, 1) - publish_btns_layout.addWidget(btn_save_as_to_published, 1) + publish_btns_layout.addWidget(btn_change_context, 1) publish_btns_layout.addWidget(btn_cancel_published, 1) publish_btns_layout.addWidget(btn_select_context_published, 1) @@ -215,19 +208,15 @@ class FilesWidget(QtWidgets.QWidget): workarea_files_view.selectionModel().selectionChanged.connect( self.on_file_select ) - publish_files_view.doubleClickedLeft.connect( - self._on_published_view_pressed - ) btn_open.pressed.connect(self._on_workarea_open_pressed) btn_browse.pressed.connect(self.on_browse_pressed) btn_save.pressed.connect(self._on_save_as_pressed) - btn_view_published.pressed.connect(self._on_published_view_pressed) btn_save_as_published.pressed.connect( self._on_published_save_as_pressed ) - btn_save_as_to_published.pressed.connect( - self._on_publish_save_as_to_pressed + btn_change_context.pressed.connect( + self._on_publish_change_context_pressed ) btn_select_context_published.pressed.connect( self._on_publish_select_context_pressed @@ -256,9 +245,8 @@ class FilesWidget(QtWidgets.QWidget): self._btn_browse = btn_browse self._btn_save = btn_save - self._btn_view_published = btn_view_published self._btn_save_as_published = btn_save_as_published - self._btn_save_as_to_published = btn_save_as_to_published + self._btn_change_context = btn_change_context self._btn_select_context_published = btn_select_context_published self._btn_cancel_published = btn_cancel_published @@ -323,9 +311,8 @@ class FilesWidget(QtWidgets.QWidget): self._asset_id, self._task_name ) has_valid_items = self._publish_files_model.has_valid_items() - self._btn_view_published.setEnabled(has_valid_items) self._btn_save_as_published.setEnabled(has_valid_items) - self._btn_save_as_to_published.setEnabled(has_valid_items) + self._btn_change_context.setEnabled(has_valid_items) else: # Define a custom session so we can query the work root @@ -587,16 +574,6 @@ class FilesWidget(QtWidgets.QWidget): else: self.refresh() - def _on_published_view_pressed(self): - filepath = self._get_selected_filepath() - if not filepath or not os.path.exists(filepath): - return - item = self._temp_publish_files.add_file(filepath) - self.host.open_file(item.filepath) - self.publish_file_viewed.emit() - # Change state back to workarea - self._published_checkbox.setChecked(False) - def _on_published_save_as_pressed(self): self._save_as_with_dialog() @@ -611,9 +588,8 @@ class FilesWidget(QtWidgets.QWidget): bool(self._asset_id) and bool(self._task_name) ) - self._btn_view_published.setVisible(not enabled) self._btn_save_as_published.setVisible(not enabled) - self._btn_save_as_to_published.setVisible(not enabled) + self._btn_change_context.setVisible(not enabled) # Change views and disable workarea view if enabled self._workarea_files_view.setEnabled(not enabled) @@ -628,7 +604,7 @@ class FilesWidget(QtWidgets.QWidget): self._published_checkbox.setEnabled(not enabled) self._filter_input.setEnabled(not enabled) - def _on_publish_save_as_to_pressed(self): + def _on_publish_change_context_pressed(self): self._set_publish_context_select_mode(True) def _on_publish_select_context_pressed(self): diff --git a/openpype/tools/workfiles/lib.py b/openpype/tools/workfiles/lib.py deleted file mode 100644 index 21a7485b7b..0000000000 --- a/openpype/tools/workfiles/lib.py +++ /dev/null @@ -1,272 +0,0 @@ -import os -import shutil -import uuid -import time -import json -import logging -import contextlib - -import appdirs - - -class TempPublishFilesItem(object): - """Object representing copied workfile in app temp folder. - - Args: - item_id (str): Id of item used as subfolder. - data (dict): Metadata about temp files. - directory (str): Path to directory where files are copied to. - """ - - def __init__(self, item_id, data, directory): - self._id = item_id - self._directory = directory - self._filepath = os.path.join(directory, data["filename"]) - - @property - def directory(self): - return self._directory - - @property - def filepath(self): - return self._filepath - - @property - def id(self): - return self._id - - @property - def size(self): - if os.path.exists(self.filepath): - s = os.stat(self.filepath) - return s.st_size - return 0 - - -class TempPublishFiles(object): - """Directory where published workfiles are copied when opened. - - Directory is located in appdirs on the machine. Folder contains file - with metadata about stored files. Each item in metadata has id, filename - and expiration time. When expiration time is higher then current time the - item is removed from metadata and it's files are deleted. Files of items - are stored in subfolder named by item's id. - - Metadata file can be in theory opened and modified by multiple processes, - threads at one time. For those cases is created simple lock file which - is created before modification begins and is removed when modification - ends. Existence of the file means that it should not be modified by - any other process at the same time. - - Metadata example: - ``` - { - "96050b4a-8974-4fca-8179-7c446c478d54": { - "created": 1647880725.555, - "expiration": 1647884325.555, - "filename": "cg_pigeon_workfileModeling_v025.ma" - }, - ... - } - ``` - - ## Why is this needed - Combination of more issues. Temp files are not automatically removed by - OS on windows so using tempfiles in TEMP would lead to kill disk space of - machine. There are also cases when someone wants to open multiple files - in short period of time and want to manually remove those files so keeping - track of temporary copied files in pre-defined structure is needed. - """ - minute_in_seconds = 60 - hour_in_seconds = 60 * minute_in_seconds - day_in_seconds = 24 * hour_in_seconds - - def __init__(self): - root_dir = appdirs.user_data_dir( - "published_workfiles_temp", "openpype" - ) - if not os.path.exists(root_dir): - os.makedirs(root_dir) - - metadata_path = os.path.join(root_dir, "metadata.json") - lock_path = os.path.join(root_dir, "lock.json") - - self._root_dir = root_dir - self._metadata_path = metadata_path - self._lock_path = lock_path - self._log = None - - @property - def log(self): - if self._log is None: - self._log = logging.getLogger(self.__class__.__name__) - return self._log - - @property - def life_time(self): - """How long will be new item kept in temp in seconds. - - Returns: - int: Lifetime of temp item. - """ - return int(self.hour_in_seconds) - - @property - def size(self): - """File size of existing items.""" - size = 0 - for item in self.get_items(): - size += item.size - return size - - def add_file(self, src_path): - """Add workfile to temp directory. - - This will create new item and source path is copied to it's directory. - """ - filename = os.path.basename(src_path) - - item_id = str(uuid.uuid4()) - dst_dirpath = os.path.join(self._root_dir, item_id) - if not os.path.exists(dst_dirpath): - os.makedirs(dst_dirpath) - - dst_path = os.path.join(dst_dirpath, filename) - shutil.copy(src_path, dst_path) - - now = time.time() - item_data = { - "filename": filename, - "expiration": now + self.life_time, - "created": now - } - with self._modify_data() as data: - data[item_id] = item_data - - return TempPublishFilesItem(item_id, item_data, dst_dirpath) - - @contextlib.contextmanager - def _modify_data(self): - """Create lock file when data in metadata file are modified.""" - start_time = time.time() - timeout = 3 - while os.path.exists(self._lock_path): - time.sleep(0.01) - if start_time > timeout: - self.log.warning(( - "Waited for {} seconds to free lock file. Overriding lock." - ).format(timeout)) - - with open(self._lock_path, "w") as stream: - json.dump({"pid": os.getpid()}, stream) - - try: - data = self._get_data() - yield data - with open(self._metadata_path, "w") as stream: - json.dump(data, stream) - - finally: - os.remove(self._lock_path) - - def _get_data(self): - output = {} - if not os.path.exists(self._metadata_path): - return output - - try: - with open(self._metadata_path, "r") as stream: - output = json.load(stream) - except Exception: - self.log.warning("Failed to read metadata file.", exc_info=True) - return output - - def cleanup(self, check_expiration=True): - """Cleanup files based on metadata. - - Items that passed expiration are removed when this is called. Or all - files are removed when `check_expiration` is set to False. - - Args: - check_expiration (bool): All items and files are removed when set - to True. - """ - data = self._get_data() - now = time.time() - remove_ids = set() - all_ids = set() - for item_id, item_data in data.items(): - all_ids.add(item_id) - if check_expiration and now < item_data["expiration"]: - continue - - remove_ids.add(item_id) - - for item_id in remove_ids: - try: - self.remove_id(item_id) - except Exception: - self.log.warning( - "Failed to remove temp publish item \"{}\"".format( - item_id - ), - exc_info=True - ) - - # Remove unknown folders/files - for filename in os.listdir(self._root_dir): - if filename in all_ids: - continue - - full_path = os.path.join(self._root_dir, filename) - if full_path in (self._metadata_path, self._lock_path): - continue - - try: - shutil.rmtree(full_path) - except Exception: - self.log.warning( - "Couldn't remove arbitrary path \"{}\"".format(full_path), - exc_info=True - ) - - def clear(self): - self.cleanup(False) - - def get_items(self): - """Receive all items from metadata file. - - Returns: - list: Info about each item in metadata. - """ - output = [] - data = self._get_data() - for item_id, item_data in data.items(): - item_path = os.path.join(self._root_dir, item_id) - output.append(TempPublishFilesItem(item_id, item_data, item_path)) - return output - - def remove_id(self, item_id): - """Remove files of item and then remove the item from metadata.""" - filepath = os.path.join(self._root_dir, item_id) - if os.path.exists(filepath): - shutil.rmtree(filepath) - - with self._modify_data() as data: - data.pop(item_id, None) - - -def file_size_to_string(file_size): - size = 0 - size_ending_mapping = { - "KB": 1024 ** 1, - "MB": 1024 ** 2, - "GB": 1024 ** 3 - } - ending = "B" - for _ending, _size in size_ending_mapping.items(): - if file_size < _size: - break - size = file_size / _size - ending = _ending - return "{:.2f} {}".format(size, ending) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 8654a18036..73e63d30b5 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -14,7 +14,22 @@ from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget from openpype.tools.utils.tasks_widget import TasksWidget from .files_widget import FilesWidget -from .lib import TempPublishFiles, file_size_to_string + + +def file_size_to_string(file_size): + size = 0 + size_ending_mapping = { + "KB": 1024 ** 1, + "MB": 1024 ** 2, + "GB": 1024 ** 3 + } + ending = "B" + for _ending, _size in size_ending_mapping.items(): + if file_size < _size: + break + size = file_size / _size + ending = _ending + return "{:.2f} {}".format(size, ending) class SidePanelWidget(QtWidgets.QWidget): @@ -44,67 +59,25 @@ class SidePanelWidget(QtWidgets.QWidget): btn_note_save, 0, alignment=QtCore.Qt.AlignRight ) - publish_temp_widget = QtWidgets.QWidget(self) - publish_temp_info_label = QtWidgets.QLabel( - self.published_workfile_message.format( - file_size_to_string(0) - ), - publish_temp_widget - ) - publish_temp_info_label.setWordWrap(True) - - btn_clear_temp = QtWidgets.QPushButton( - "Clear temp", publish_temp_widget - ) - - publish_temp_layout = QtWidgets.QVBoxLayout(publish_temp_widget) - publish_temp_layout.setContentsMargins(0, 0, 0, 0) - publish_temp_layout.addWidget(publish_temp_info_label, 0) - publish_temp_layout.addWidget( - btn_clear_temp, 0, alignment=QtCore.Qt.AlignRight - ) - main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(details_label, 0) main_layout.addWidget(details_input, 1) main_layout.addWidget(artist_note_widget, 1) - main_layout.addWidget(publish_temp_widget, 0) note_input.textChanged.connect(self._on_note_change) btn_note_save.clicked.connect(self._on_save_click) - btn_clear_temp.clicked.connect(self._on_clear_temp_click) self._details_input = details_input self._artist_note_widget = artist_note_widget self._note_input = note_input self._btn_note_save = btn_note_save - self._publish_temp_info_label = publish_temp_info_label - self._publish_temp_widget = publish_temp_widget - self._orig_note = "" self._workfile_doc = None - publish_temp_widget.setVisible(False) - def set_published_visible(self, published_visible): self._artist_note_widget.setVisible(not published_visible) - self._publish_temp_widget.setVisible(published_visible) - if published_visible: - self.refresh_publish_temp_sizes() - - def refresh_publish_temp_sizes(self): - temp_publish_files = TempPublishFiles() - text = self.published_workfile_message.format( - file_size_to_string(temp_publish_files.size) - ) - self._publish_temp_info_label.setText(text) - - def _on_clear_temp_click(self): - temp_publish_files = TempPublishFiles() - temp_publish_files.clear() - self.refresh_publish_temp_sizes() def _on_note_change(self): text = self._note_input.toPlainText() @@ -225,9 +198,6 @@ class Window(QtWidgets.QMainWindow): files_widget.file_selected.connect(self.on_file_select) files_widget.workfile_created.connect(self.on_workfile_create) files_widget.file_opened.connect(self._on_file_opened) - files_widget.publish_file_viewed.connect( - self._on_publish_file_viewed - ) files_widget.published_visible_changed.connect( self._on_published_change ) @@ -292,9 +262,6 @@ class Window(QtWidgets.QMainWindow): def _on_file_opened(self): self.close() - def _on_publish_file_viewed(self): - self.side_panel.refresh_publish_temp_sizes() - def _on_published_change(self, visible): self.side_panel.set_published_visible(visible) From 34656d9b79536155eca3a5729cf6d0b449c6633d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:29:29 +0200 Subject: [PATCH 38/67] fix ampresand in button label --- openpype/tools/workfiles/files_widget.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 80a94cc1bd..f29223b321 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -95,7 +95,7 @@ class FilesWidget(QtWidgets.QWidget): extensions = set(self.host.file_extensions()) views_widget = QtWidgets.QWidget(self) - # Workarea view + # --- Workarea view --- workarea_files_model = WorkAreaFilesModel(extensions) # Create proxy model for files to be able sort and filter @@ -113,13 +113,14 @@ class FilesWidget(QtWidgets.QWidget): # Date modified delegate workarea_time_delegate = PrettyTimeDelegate() workarea_files_view.setItemDelegateForColumn(1, workarea_time_delegate) - workarea_files_view.setIndentation(3) # smaller indentation + # smaller indentation + workarea_files_view.setIndentation(3) # Default to a wider first filename column it is what we mostly care # about and the date modified is relatively small anyway. workarea_files_view.setColumnWidth(0, 330) - # Publish files view + # --- Publish files view --- publish_files_model = PublishFilesModel(extensions, io, self.anatomy) publish_proxy_model = QtCore.QSortFilterProxyModel() @@ -136,7 +137,8 @@ class FilesWidget(QtWidgets.QWidget): # Date modified delegate publish_time_delegate = PrettyTimeDelegate() publish_files_view.setItemDelegateForColumn(1, publish_time_delegate) - publish_files_view.setIndentation(3) # smaller indentation + # smaller indentation + publish_files_view.setIndentation(3) # Default to a wider first filename column it is what we mostly care # about and the date modified is relatively small anyway. @@ -164,13 +166,13 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_widget = QtWidgets.QWidget(btns_widget) btn_save_as_published = QtWidgets.QPushButton( - "Copy & Open", publish_btns_widget + "Copy && Open", publish_btns_widget ) btn_change_context = QtWidgets.QPushButton( "Choose different context", publish_btns_widget ) btn_select_context_published = QtWidgets.QPushButton( - "Copy & Open", publish_btns_widget + "Copy && Open", publish_btns_widget ) btn_cancel_published = QtWidgets.QPushButton( "Cancel", publish_btns_widget From aa8438a6ad2e947038af43bef93d1dd686267aea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 12:29:50 +0200 Subject: [PATCH 39/67] define extensions of save as dialog with argument --- openpype/tools/workfiles/files_widget.py | 7 +++++++ openpype/tools/workfiles/save_as_dialog.py | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index f29223b321..6e90dea982 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -449,11 +449,18 @@ class FilesWidget(QtWidgets.QWidget): """ session = self._get_session() + if self.published_enabled: + filepath = self._get_selected_filepath() + extensions = [os.path.splitext(filepath)[1]] + else: + extensions = self.host.file_extensions() + window = SaveAsDialog( parent=self, root=self._workfiles_root, anatomy=self.anatomy, template_key=self.template_key, + extensions=extensions, session=session ) window.exec_() diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index e616a325cc..f5ae393d0f 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -193,7 +193,9 @@ class SaveAsDialog(QtWidgets.QDialog): """ - def __init__(self, parent, root, anatomy, template_key, session=None): + def __init__( + self, parent, root, anatomy, template_key, extensions, session=None + ): super(SaveAsDialog, self).__init__(parent=parent) self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) @@ -201,6 +203,7 @@ class SaveAsDialog(QtWidgets.QDialog): self.host = api.registered_host() self.root = root self.work_file = None + self._extensions = extensions if not session: # Fallback to active session @@ -257,7 +260,7 @@ class SaveAsDialog(QtWidgets.QDialog): # Add styled delegate to use stylesheets ext_delegate = QtWidgets.QStyledItemDelegate() ext_combo.setItemDelegate(ext_delegate) - ext_combo.addItems(self.host.file_extensions()) + ext_combo.addItems(self._extensions) # Build inputs inputs_layout = QtWidgets.QFormLayout(inputs_widget) @@ -336,7 +339,7 @@ class SaveAsDialog(QtWidgets.QDialog): def get_existing_comments(self): matcher = CommentMatcher(self.anatomy, self.template_key, self.data) - host_extensions = set(self.host.file_extensions()) + host_extensions = set(self._extensions) comments = set() if os.path.isdir(self.root): for fname in os.listdir(self.root): @@ -392,7 +395,7 @@ class SaveAsDialog(QtWidgets.QDialog): return anatomy_filled[self.template_key]["file"] def refresh(self): - extensions = self.host.file_extensions() + extensions = list(self._extensions) extension = self.data["ext"] if extension is None: # Define saving file extension From 37900da59d1c5ba718c20cc3266c3bb62c9ee987 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:32:40 +0200 Subject: [PATCH 40/67] added overlay guiding to select context --- openpype/style/style.css | 8 ++++++ openpype/tools/workfiles/files_widget.py | 31 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/openpype/style/style.css b/openpype/style/style.css index df83600973..b5f6962eee 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -1269,6 +1269,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: #21252B; } +/* Workfiles */ +#WorkfilesPublishedContextSelect { + background: rgba(0, 0, 0, 127); +} +#WorkfilesPublishedContextSelect QLabel { + font-size: 17pt; +} + /* Tray */ #TrayRestartButton { background: {color:restart-btn-bg}; diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 6e90dea982..55abd39b36 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -44,6 +44,31 @@ class FilesView(QtWidgets.QTreeView): return super(FilesView, self).mouseDoubleClickEvent(event) +class SelectContextOverlay(QtWidgets.QFrame): + def __init__(self, parent): + super(SelectContextOverlay, self).__init__(parent) + + self.setObjectName("WorkfilesPublishedContextSelect") + label_widget = QtWidgets.QLabel( + "Please select context on Left side
<", + self + ) + label_widget.setAlignment(QtCore.Qt.AlignCenter) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(label_widget, 1, QtCore.Qt.AlignCenter) + + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + parent.installEventFilter(self) + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.Resize: + self.resize(obj.size()) + + return super(SelectContextOverlay, self).eventFilter(obj, event) + + class FilesWidget(QtWidgets.QWidget): """A widget displaying files that allows to save and open files.""" file_selected = QtCore.Signal(str) @@ -144,6 +169,9 @@ class FilesWidget(QtWidgets.QWidget): # about and the date modified is relatively small anyway. publish_files_view.setColumnWidth(0, 330) + publish_context_overlay = SelectContextOverlay(views_widget) + publish_context_overlay.setVisible(False) + views_layout = QtWidgets.QHBoxLayout(views_widget) views_layout.setContentsMargins(0, 0, 0, 0) views_layout.addWidget(workarea_files_view, 1) @@ -241,6 +269,8 @@ class FilesWidget(QtWidgets.QWidget): self._publish_files_model = publish_files_model self._publish_proxy_model = publish_proxy_model + self._publish_context_overlay = publish_context_overlay + self._workarea_btns_widget = workarea_btns_widget self._publish_btns_widget = publish_btns_widget self._btn_open = btn_open @@ -590,6 +620,7 @@ class FilesWidget(QtWidgets.QWidget): self._publish_context_select_mode = enabled # Show buttons related to context selection + self._publish_context_overlay.setVisible(enabled) self._btn_cancel_published.setVisible(enabled) self._btn_select_context_published.setVisible(enabled) # Change enabled state based on select context From 14da7f74c6ddedc4eeaa9fc3fb5b093bf630b2e8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:33:02 +0200 Subject: [PATCH 41/67] don't cancel save as publishing on cancel save as dialog --- openpype/tools/workfiles/files_widget.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 55abd39b36..6cfd3dd651 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -554,7 +554,7 @@ class FilesWidget(QtWidgets.QWidget): def _save_as_with_dialog(self): work_filename = self.get_filename() if not work_filename: - return + return None src_path = self._get_selected_filepath() @@ -612,6 +612,7 @@ class FilesWidget(QtWidgets.QWidget): self._published_checkbox.setChecked(False) else: self.refresh() + return filepath def _on_published_save_as_pressed(self): self._save_as_with_dialog() @@ -648,9 +649,10 @@ class FilesWidget(QtWidgets.QWidget): self._set_publish_context_select_mode(True) def _on_publish_select_context_pressed(self): - self._save_as_with_dialog() - self._set_publish_context_select_mode(False) - self._update_asset_task() + result = self._save_as_with_dialog() + if result is not None: + self._set_publish_context_select_mode(False) + self._update_asset_task() def _on_publish_cancel_pressed(self): self._set_publish_context_select_mode(False) From 6e592e4df62dd5f8969fbe8e29a730ce999a8d57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 31 Mar 2022 14:56:07 +0200 Subject: [PATCH 42/67] changed label --- 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 6cfd3dd651..edfcb17722 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -50,7 +50,7 @@ class SelectContextOverlay(QtWidgets.QFrame): self.setObjectName("WorkfilesPublishedContextSelect") label_widget = QtWidgets.QLabel( - "Please select context on Left side
<", + "Please choose context on the left
<", self ) label_widget.setAlignment(QtCore.Qt.AlignCenter) From f80bc13cc309e3d2769da17acc73e64cedaeb134 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 31 Mar 2022 20:26:08 +0200 Subject: [PATCH 43/67] adding limitations for pyright speeding up development --- pyproject.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 479cd731fe..e42fd75db2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,3 +136,19 @@ hash = "de63a8bf7f6c45ff59ecafeba13123f710c2cbc1783ec9e0b938e980d4f5c37f" [openpype.thirdparty.oiio.darwin] url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz" hash = "sha256:..." + +[tool.pyright] +include = [ + "igniter", + "openpype", + "repos", + "vendor" +] +exclude = [ + "**/node_modules", + "**/__pycache__" +] +ignore = ["website", "docs", ".git"] + +reportMissingImports = true +reportMissingTypeStubs = false \ No newline at end of file From bc0054cd88ca6494305dde5bc992b370de59592b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 11:05:15 +0200 Subject: [PATCH 44/67] nuke | general: removing redundant review representation --- .../deadline/plugins/publish/submit_publish_job.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index fad4d14ea0..6730c6a7dd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -509,8 +509,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): most cases, but if not - we create representation from each of them. Arguments: - instance (pyblish.plugin.Instance): instance for which we are - setting representations + instance (dict): instance data for which we are + setting representations exp_files (list): list of expected files Returns: @@ -528,6 +528,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # preview video rendering for app in self.aov_filter.keys(): if os.environ.get("AVALON_APP", "") == app: + # no need to add review if baking in nuke present + if ( + app == "nuke" + and instance.get("bakingNukeScripts") + ): + break + + # iteratre all aov filters for aov in self.aov_filter[app]: if re.match( aov, From 35e6b8e42d14188650b5fcf58a5891498514721c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 11:07:22 +0200 Subject: [PATCH 45/67] ftrack: improving asset name if multiple reviewable representation --- .../publish/integrate_ftrack_instances.py | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index cff7cd32cb..f157f0db22 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -2,7 +2,7 @@ import os import json import copy import pyblish.api - +from pprint import pformat class IntegrateFtrackInstance(pyblish.api.InstancePlugin): """Collect ftrack component data (not integrate yet). @@ -168,7 +168,31 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Change asset name of each new component for review is_first_review_repre = True not_first_components = [] + extended_asset_name = False for repre in review_representations: + # Create copy of base comp item and append it + review_item = copy.deepcopy(base_component_item) + + # condition for multiple reviewable representations + # expand name to better label componenst + if is_first_review_repre and len(review_representations) > 1: + asset_name = review_item["asset_data"]["name"] + # define new extended name + extended_asset_name = "_".join( + (asset_name, repre["name"]) + ) + review_item["asset_data"]["name"] = extended_asset_name + # and rename all already created components + for _ci in component_list: + _ci["asset_data"]["name"] = extended_asset_name + + # and rename all already created src components + for _sci in src_components_to_add: + _sci["asset_data"]["name"] = extended_asset_name + + first_thumbnail_component[ + "asset_data"]["name"] = extended_asset_name + frame_start = repre.get("frameStartFtrack") frame_end = repre.get("frameEndFtrack") if frame_start is None or frame_end is None: @@ -184,8 +208,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if fps is None: fps = instance_fps - # Create copy of base comp item and append it - review_item = copy.deepcopy(base_component_item) # Change location review_item["component_path"] = repre["published_path"] # Change component data @@ -200,8 +222,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): }) } } - # Create copy of item before setting location or changing asset - src_components_to_add.append(copy.deepcopy(review_item)) + + # rename asset name only if multiple reviewable repre if is_first_review_repre: is_first_review_repre = False else: @@ -212,6 +234,9 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): ) not_first_components.append(review_item) + # Create copy of item before setting location + src_components_to_add.append(copy.deepcopy(review_item)) + # Set location review_item["component_location"] = ftrack_server_location # Add item to component list @@ -249,6 +274,11 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): continue # Create copy of base comp item and append it other_item = copy.deepcopy(base_component_item) + + # add extended name if any + if extended_asset_name: + other_item["asset_data"]["name"] = extended_asset_name + other_item["component_data"] = { "name": repre["name"] } From b69032e9f57c8efcb1b4e88ed26fbbf57c1a59bd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 11:19:15 +0200 Subject: [PATCH 46/67] hound catch --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index f157f0db22..c0d188c6ab 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -2,7 +2,7 @@ import os import json import copy import pyblish.api -from pprint import pformat + class IntegrateFtrackInstance(pyblish.api.InstancePlugin): """Collect ftrack component data (not integrate yet). From 61d5f32fe868f118aa84722b3ccb07594caff580 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 12:56:18 +0200 Subject: [PATCH 47/67] ftrack: improve conditional rename --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index c0d188c6ab..b1a7da58f9 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -190,8 +190,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): for _sci in src_components_to_add: _sci["asset_data"]["name"] = extended_asset_name - first_thumbnail_component[ - "asset_data"]["name"] = extended_asset_name + # rename also first thumbnail component if any + if first_thumbnail_component is not None: + first_thumbnail_component[ + "asset_data"]["name"] = extended_asset_name frame_start = repre.get("frameStartFtrack") frame_end = repre.get("frameEndFtrack") From d63a0aad71cbc40aac8a8baa46b3dc183eeac977 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 14:42:39 +0200 Subject: [PATCH 48/67] Nuke: adding concurrent tasks attribute to job submission also adding to settings --- .../plugins/publish/submit_nuke_deadline.py | 2 ++ .../defaults/project_settings/deadline.json | 1 + .../projects_schema/schema_project_deadline.json | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index d6bd11620d..055d3c8a2c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -27,6 +27,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # presets priority = 50 chunk_size = 1 + concurent_task = 1 primary_pool = "" secondary_pool = "" group = "" @@ -177,6 +178,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Priority": priority, "ChunkSize": chunk_size, + "ConcurrentTasks": self.concurent_task, "Department": self.department, "Pool": self.primary_pool, diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5bb0a4022e..cfbb92e590 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -62,6 +62,7 @@ "use_published": true, "priority": 50, "chunk_size": 10, + "concurent_task": 1, "primary_pool": "", "secondary_pool": "", "group": "", 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 e6097a2b14..700c3863fb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -192,6 +192,9 @@ "key": "use_published", "label": "Use Published scene" }, + { + "type": "splitter" + }, { "type": "number", "key": "priority", @@ -202,6 +205,14 @@ "key": "chunk_size", "label": "Chunk Size" }, + { + "type": "number", + "key": "concurent_task", + "label": "Number of concurent tasks" + }, + { + "type": "splitter" + }, { "type": "text", "key": "primary_pool", @@ -217,6 +228,9 @@ "key": "group", "label": "Group" }, + { + "type": "splitter" + }, { "type": "text", "key": "department", From 5014dfddf9b2a2c657d557871ae1e137b9415726 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 16:40:14 +0200 Subject: [PATCH 49/67] nuke: adding node knob for concurrent tasks - and fixing misspelling --- openpype/hosts/nuke/api/lib.py | 8 ++++++-- .../deadline/plugins/publish/submit_nuke_deadline.py | 10 ++++++++-- .../settings/defaults/project_settings/deadline.json | 2 +- .../projects_schema/schema_project_deadline.json | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index c22488f728..9601244d1d 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1048,12 +1048,16 @@ def add_review_knob(node): def add_deadline_tab(node): node.addKnob(nuke.Tab_Knob("Deadline")) + knob = nuke.Int_Knob("deadlinePriority", "Priority") + knob.setValue(50) + node.addKnob(knob) + knob = nuke.Int_Knob("deadlineChunkSize", "Chunk Size") knob.setValue(0) node.addKnob(knob) - knob = nuke.Int_Knob("deadlinePriority", "Priority") - knob.setValue(50) + knob = nuke.Int_Knob("deadlineConcurrentTasks", "Concurrent tasks") + knob.setValue(0) node.addKnob(knob) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 055d3c8a2c..442fcc1ddf 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -27,7 +27,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # presets priority = 50 chunk_size = 1 - concurent_task = 1 + concurrent_tasks = 1 primary_pool = "" secondary_pool = "" group = "" @@ -154,6 +154,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): if chunk_size == 0 and self.chunk_size: chunk_size = self.chunk_size + # define chunk and priority + concurrent_tasks = instance.data.get("deadlineConcurrentTasks") + if concurrent_tasks == 0 and self.concurrent_tasks: + concurrent_tasks = self.concurrent_tasks + priority = instance.data.get("deadlinePriority") if not priority: priority = self.priority @@ -178,7 +183,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Priority": priority, "ChunkSize": chunk_size, - "ConcurrentTasks": self.concurent_task, + "ConcurrentTasks": concurrent_tasks, + "Department": self.department, "Pool": self.primary_pool, diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index cfbb92e590..efaaa07be6 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -62,7 +62,7 @@ "use_published": true, "priority": 50, "chunk_size": 10, - "concurent_task": 1, + "concurrent_tasks": 1, "primary_pool": "", "secondary_pool": "", "group": "", 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 700c3863fb..ea1173313b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -207,8 +207,8 @@ }, { "type": "number", - "key": "concurent_task", - "label": "Number of concurent tasks" + "key": "concurrent_tasks", + "label": "Number of concurrent tasks" }, { "type": "splitter" From 6916cc73d3ca4a8edf9aa59a8e3e1066f0cfc4ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 16:42:16 +0200 Subject: [PATCH 50/67] Nuke: fixing unicode type detection in effect loaders --- openpype/hosts/nuke/plugins/load/load_effects.py | 3 ++- openpype/hosts/nuke/plugins/load/load_effects_ip.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index 1ed32996e1..56c5acbb0a 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -1,6 +1,7 @@ import json from collections import OrderedDict import nuke +import six from avalon import io @@ -333,7 +334,7 @@ class LoadEffects(load.LoaderPlugin): for key, value in input.items()} elif isinstance(input, list): return [self.byteify(element) for element in input] - elif isinstance(input, str): + elif isinstance(input, six.text_type): return str(input) else: return input diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 383776111f..0bc5f5a514 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -1,6 +1,6 @@ import json from collections import OrderedDict - +import six import nuke from avalon import io @@ -353,7 +353,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin): for key, value in input.items()} elif isinstance(input, list): return [self.byteify(element) for element in input] - elif isinstance(input, str): + elif isinstance(input, six.text_type): return str(input) else: return input From 4fbf9c305087cd3b8e2795ed65ba30347354342d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 17:08:22 +0200 Subject: [PATCH 51/67] nuke: gizmo loader was also having unicode type tests --- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index df52a22364..46134afcf0 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,5 +1,5 @@ import nuke - +import six from avalon import io from openpype.pipeline import ( @@ -243,8 +243,8 @@ class LoadGizmoInputProcess(load.LoaderPlugin): for key, value in input.items()} elif isinstance(input, list): return [self.byteify(element) for element in input] - elif isinstance(input, unicode): - return input.encode('utf-8') + elif isinstance(input, six.text_type): + return str(input) else: return input From fd420ff0dad0fe9c6e658613f587dfea65132340 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 17:40:54 +0200 Subject: [PATCH 52/67] nuke: adding concurrent task knob input to instance data --- .../nuke/plugins/publish/precollect_writes.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 85e98db7ed..4826b2788f 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -128,13 +128,17 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): } group_node = [x for x in instance if x.Class() == "Group"][0] - deadlineChunkSize = 1 + dl_chunk_size = 1 if "deadlineChunkSize" in group_node.knobs(): - deadlineChunkSize = group_node["deadlineChunkSize"].value() + dl_chunk_size = group_node["deadlineChunkSize"].value() - deadlinePriority = 50 + dl_priority = 50 if "deadlinePriority" in group_node.knobs(): - deadlinePriority = group_node["deadlinePriority"].value() + dl_priority = group_node["deadlinePriority"].value() + + dl_concurrent_tasks = 0 + if "deadlineConcurrentTasks" in group_node.knobs(): + dl_concurrent_tasks = group_node["deadlineConcurrentTasks"].value() instance.data.update({ "versionData": version_data, @@ -144,8 +148,9 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "label": label, "outputType": output_type, "colorspace": colorspace, - "deadlineChunkSize": deadlineChunkSize, - "deadlinePriority": deadlinePriority + "deadlineChunkSize": dl_chunk_size, + "deadlinePriority": dl_priority, + "deadlineConcurrentTasks": dl_concurrent_tasks }) if self.is_prerender(_families_test): From 23b31bb23bdc1188e25324deef0fd746f8b984b4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 Apr 2022 18:00:50 +0200 Subject: [PATCH 53/67] ftrack: make nicer name only if set in settings --- .../plugins/publish/integrate_ftrack_instances.py | 12 ++++++++++-- .../settings/defaults/project_settings/ftrack.json | 3 ++- .../projects_schema/schema_project_ftrack.json | 6 ++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index b1a7da58f9..5c0b414d86 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -35,6 +35,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "image": "img", "reference": "reference" } + nicer_asset_name = False def process(self, instance): self.log.debug("instance {}".format(instance)) @@ -175,7 +176,11 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # condition for multiple reviewable representations # expand name to better label componenst - if is_first_review_repre and len(review_representations) > 1: + if ( + self.nicer_asset_name is not False + and is_first_review_repre + and len(review_representations) > 1 + ): asset_name = review_item["asset_data"]["name"] # define new extended name extended_asset_name = "_".join( @@ -278,7 +283,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): other_item = copy.deepcopy(base_component_item) # add extended name if any - if extended_asset_name: + if ( + self.nicer_asset_name is not False + and extended_asset_name is not False + ): other_item["asset_data"]["name"] = extended_asset_name other_item["component_data"] = { diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 89bb41a164..e97258b750 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -395,7 +395,8 @@ "vrayproxy": "cache", "redshiftproxy": "cache", "usd": "usd" - } + }, + "nicer_asset_name": false } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index cb59e9d67e..702b1812a2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -784,6 +784,12 @@ "object_type": { "type": "text" } + }, + { + "type": "boolean", + "key": "nicer_asset_name", + "label": "Nicer Asset name if multiple reviewable", + "default": false } ] } From 85e2601022e0f5bf4596293c30f0bc653992013a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 1 Apr 2022 18:36:29 +0200 Subject: [PATCH 54/67] =?UTF-8?q?fix=20=F0=9F=90=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../maya/plugins/publish/collect_unreal_skeletalmesh.py | 2 -- .../maya/plugins/publish/extract_unreal_skeletalmesh.py | 6 ------ .../maya/plugins/publish/extract_unreal_staticmesh.py | 3 +-- .../plugins/publish/validate_unreal_staticmesh_naming.py | 9 +-------- 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py index 2b176e3a6d..79693bb35e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_unreal_skeletalmesh.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from maya import cmds # noqa import pyblish.api -from avalon.api import Session -from openpype.api import get_project_settings class CollectUnrealSkeletalMesh(pyblish.api.InstancePlugin): diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 98dbf117dc..4dcad47e8c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -7,10 +7,6 @@ from maya import cmds # noqa import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import ( - parent_nodes, - maintained_selection -) from openpype.hosts.maya.api import fbx @@ -42,8 +38,6 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): geo = instance.data.get("geometry") joints = instance.data.get("joints") - joints_parent = cmds.listRelatives(joints, p=True) - to_extract = geo + joints # The export requires forward slashes because we need diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 92fa1b5933..69d51f9ff1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -8,8 +8,7 @@ import pyblish.api import openpype.api from openpype.hosts.maya.api.lib import ( parent_nodes, - maintained_selection, - delete_after + maintained_selection ) from openpype.hosts.maya.api import fbx diff --git a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index c0eeb82688..43f6c85827 100644 --- a/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- - +"""Validator for correct naming of Static Meshes.""" from maya import cmds # noqa import pyblish.api import openpype.api @@ -71,13 +71,6 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin): ["CreateUnrealStaticMesh"] ["collision_prefixes"] ) - static_mesh_prefix = ( - project_settings - ["maya"] - ["create"] - ["CreateUnrealStaticMesh"] - ["static_mesh_prefix"] - ) if cls.validate_mesh: # compile regex for testing names From f186b99e22ab82d58e89fee7308356e6479042b4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Apr 2022 11:16:59 +0200 Subject: [PATCH 55/67] nuke: add comment to code --- openpype/hosts/nuke/api/lib.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 9601244d1d..b1717ea7ff 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1057,12 +1057,19 @@ def add_deadline_tab(node): node.addKnob(knob) knob = nuke.Int_Knob("deadlineConcurrentTasks", "Concurrent tasks") + # zero as default will trigger value from Setting during collection + # look to precollect_write.py knob.setValue(0) node.addKnob(knob) def get_deadline_knob_names(): - return ["Deadline", "deadlineChunkSize", "deadlinePriority"] + return [ + "Deadline", + "deadlineChunkSize", + "deadlinePriority", + "deadlineConcurrentTasks" + ] def create_backdrop(label="", color=None, layer=0, From 71384d8cd6b049f5341167b583a3c830d7104c11 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Apr 2022 11:32:39 +0200 Subject: [PATCH 56/67] deadline: adding strict method for getting --- .../deadline/plugins/publish/submit_nuke_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 442fcc1ddf..9b5800c33f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -150,16 +150,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): pass # define chunk and priority - chunk_size = instance.data.get("deadlineChunkSize") + chunk_size = instance.data["deadlineChunkSize"] if chunk_size == 0 and self.chunk_size: chunk_size = self.chunk_size # define chunk and priority - concurrent_tasks = instance.data.get("deadlineConcurrentTasks") + concurrent_tasks = instance.data["deadlineConcurrentTasks"] if concurrent_tasks == 0 and self.concurrent_tasks: concurrent_tasks = self.concurrent_tasks - priority = instance.data.get("deadlinePriority") + priority = instance.data["deadlinePriority"] if not priority: priority = self.priority From 97dca92bbe930b2063119dceba2c306a0ea69e6c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Apr 2022 11:56:42 +0200 Subject: [PATCH 57/67] global | nuke: generalizing attribute detection --- .../hosts/nuke/plugins/publish/extract_review_data_mov.py | 1 + .../modules/deadline/plugins/publish/submit_publish_job.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index 31a8ff18ee..22b371d8e9 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -123,6 +123,7 @@ class ExtractReviewDataMov(openpype.api.Extractor): if generated_repres: # assign to representations instance.data["representations"] += generated_repres + instance.data["hasReviewableRepresentations"] = True else: instance.data["families"].remove("review") self.log.info(( diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 6730c6a7dd..5755619292 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -528,11 +528,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # preview video rendering for app in self.aov_filter.keys(): if os.environ.get("AVALON_APP", "") == app: - # no need to add review if baking in nuke present - if ( - app == "nuke" - and instance.get("bakingNukeScripts") - ): + # no need to add review if `hasReviewableRepresentations` + if instance.get("hasReviewableRepresentations"): break # iteratre all aov filters From c9edd81e17cf5605890a237fff9cc6d2f12addc9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Apr 2022 12:04:56 +0200 Subject: [PATCH 58/67] ftrack: renameing attribute to `keep_first_subset_name_for_review` --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 6 +++--- openpype/settings/defaults/project_settings/ftrack.json | 2 +- .../schemas/projects_schema/schema_project_ftrack.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 5c0b414d86..f79bdb31d7 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -35,7 +35,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "image": "img", "reference": "reference" } - nicer_asset_name = False + keep_first_subset_name_for_review = True def process(self, instance): self.log.debug("instance {}".format(instance)) @@ -177,7 +177,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # condition for multiple reviewable representations # expand name to better label componenst if ( - self.nicer_asset_name is not False + not self.keep_first_subset_name_for_review and is_first_review_repre and len(review_representations) > 1 ): @@ -284,7 +284,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # add extended name if any if ( - self.nicer_asset_name is not False + not self.keep_first_subset_name_for_review and extended_asset_name is not False ): other_item["asset_data"]["name"] = extended_asset_name diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index e97258b750..ca1cfe1e12 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -396,7 +396,7 @@ "redshiftproxy": "cache", "usd": "usd" }, - "nicer_asset_name": false + "keep_first_subset_name_for_review": true } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 702b1812a2..fb384882c6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -787,9 +787,9 @@ }, { "type": "boolean", - "key": "nicer_asset_name", - "label": "Nicer Asset name if multiple reviewable", - "default": false + "key": "keep_first_subset_name_for_review", + "label": "Make subset name as first asset name", + "default": true } ] } From c32b0ec7703e2821204667afb354a18186e36c13 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Apr 2022 12:21:17 +0200 Subject: [PATCH 59/67] ftrack: simplification of logic --- .../publish/integrate_ftrack_instances.py | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index f79bdb31d7..c11d5b9c68 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -169,7 +169,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Change asset name of each new component for review is_first_review_repre = True not_first_components = [] - extended_asset_name = False + extended_asset_name = "" + multiple_reviewable = len(review_representations) > 1 for repre in review_representations: # Create copy of base comp item and append it review_item = copy.deepcopy(base_component_item) @@ -178,8 +179,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # expand name to better label componenst if ( not self.keep_first_subset_name_for_review - and is_first_review_repre - and len(review_representations) > 1 + and multiple_reviewable ): asset_name = review_item["asset_data"]["name"] # define new extended name @@ -187,18 +187,21 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): (asset_name, repre["name"]) ) review_item["asset_data"]["name"] = extended_asset_name - # and rename all already created components - for _ci in component_list: - _ci["asset_data"]["name"] = extended_asset_name - # and rename all already created src components - for _sci in src_components_to_add: - _sci["asset_data"]["name"] = extended_asset_name + # rename asset name only if multiple reviewable repre + if is_first_review_repre: + # and rename all already created components + for _ci in component_list: + _ci["asset_data"]["name"] = extended_asset_name - # rename also first thumbnail component if any - if first_thumbnail_component is not None: - first_thumbnail_component[ - "asset_data"]["name"] = extended_asset_name + # and rename all already created src components + for _sci in src_components_to_add: + _sci["asset_data"]["name"] = extended_asset_name + + # rename also first thumbnail component if any + if first_thumbnail_component is not None: + first_thumbnail_component[ + "asset_data"]["name"] = extended_asset_name frame_start = repre.get("frameStartFtrack") frame_end = repre.get("frameEndFtrack") @@ -230,15 +233,9 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): } } - # rename asset name only if multiple reviewable repre if is_first_review_repre: is_first_review_repre = False else: - # Add representation name to asset name of "not first" review - asset_name = review_item["asset_data"]["name"] - review_item["asset_data"]["name"] = "_".join( - (asset_name, repre["name"]) - ) not_first_components.append(review_item) # Create copy of item before setting location @@ -283,10 +280,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): other_item = copy.deepcopy(base_component_item) # add extended name if any - if ( - not self.keep_first_subset_name_for_review - and extended_asset_name is not False - ): + if extended_asset_name: other_item["asset_data"]["name"] = extended_asset_name other_item["component_data"] = { From 582e35a4829f6494c9d145cbf027e4f8446f6ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 4 Apr 2022 16:11:04 +0200 Subject: [PATCH 60/67] Update openpype/hosts/nuke/api/lib.py Co-authored-by: Roy Nieterau --- openpype/hosts/nuke/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index b1717ea7ff..e05c6aecbd 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1057,8 +1057,8 @@ def add_deadline_tab(node): node.addKnob(knob) knob = nuke.Int_Knob("deadlineConcurrentTasks", "Concurrent tasks") - # zero as default will trigger value from Setting during collection - # look to precollect_write.py + # zero as default will get value from Settings during collection + # instead of being an explicit user override, see precollect_write.py knob.setValue(0) node.addKnob(knob) From ee7ae9edfb9657320e1b2e0610ae9df07f0182bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:27:47 +0200 Subject: [PATCH 61/67] fix registration of creator paths --- openpype/hosts/traypublisher/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/api/pipeline.py b/openpype/hosts/traypublisher/api/pipeline.py index a39e5641ae..24175883d9 100644 --- a/openpype/hosts/traypublisher/api/pipeline.py +++ b/openpype/hosts/traypublisher/api/pipeline.py @@ -7,7 +7,7 @@ from avalon import io import avalon.api import pyblish.api -from openpype.pipeline import BaseCreator +from openpype.pipeline import register_creator_plugin_path ROOT_DIR = os.path.dirname(os.path.dirname( os.path.abspath(__file__) @@ -169,7 +169,7 @@ def install(): pyblish.api.register_host("traypublisher") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(BaseCreator, CREATE_PATH) + register_creator_plugin_path(CREATE_PATH) def set_project_name(project_name): From 38652d2c6a620634d5b5ec3ebc58169de94beed3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:28:03 +0200 Subject: [PATCH 62/67] added some titles to validation errors --- .../plugins/publish/validate_workfile.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_workfile.py b/openpype/hosts/traypublisher/plugins/publish/validate_workfile.py index e8eeb46065..7501051669 100644 --- a/openpype/hosts/traypublisher/plugins/publish/validate_workfile.py +++ b/openpype/hosts/traypublisher/plugins/publish/validate_workfile.py @@ -14,11 +14,22 @@ class ValidateWorkfilePath(pyblish.api.InstancePlugin): def process(self, instance): filepath = instance.data["sourceFilepath"] if not filepath: - raise PublishValidationError(( - "Filepath of 'workfile' instance \"{}\" is not set" - ).format(instance.data["name"])) + raise PublishValidationError( + ( + "Filepath of 'workfile' instance \"{}\" is not set" + ).format(instance.data["name"]), + "File not filled", + "## Missing file\nYou are supposed to fill the path." + ) if not os.path.exists(filepath): - raise PublishValidationError(( - "Filepath of 'workfile' instance \"{}\" does not exist: {}" - ).format(instance.data["name"], filepath)) + raise PublishValidationError( + ( + "Filepath of 'workfile' instance \"{}\" does not exist: {}" + ).format(instance.data["name"], filepath), + "File not found", + ( + "## File was not found\nFile \"{}\" was not found." + " Check if the path is still available." + ).format(filepath) + ) From dded71a5d41d90c55bace8e06732a8c8f558a667 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:29:28 +0200 Subject: [PATCH 63/67] moved "published" checkbox after filter --- 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 edfcb17722..b4ff830459 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -113,8 +113,8 @@ class FilesWidget(QtWidgets.QWidget): filter_layout = QtWidgets.QHBoxLayout(filter_widget) filter_layout.setContentsMargins(0, 0, 0, 0) - filter_layout.addWidget(published_checkbox, 0) filter_layout.addWidget(filter_input, 1) + filter_layout.addWidget(published_checkbox, 0) # Create the Files models extensions = set(self.host.file_extensions()) From 1bb36035f94487abaff9abd9b4efd3423c4c9e83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Apr 2022 10:29:51 +0200 Subject: [PATCH 64/67] swapped cancel and copy & open buttons --- 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 b4ff830459..56af7752da 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -210,8 +210,8 @@ class FilesWidget(QtWidgets.QWidget): publish_btns_layout.setContentsMargins(0, 0, 0, 0) publish_btns_layout.addWidget(btn_save_as_published, 1) publish_btns_layout.addWidget(btn_change_context, 1) - publish_btns_layout.addWidget(btn_cancel_published, 1) publish_btns_layout.addWidget(btn_select_context_published, 1) + publish_btns_layout.addWidget(btn_cancel_published, 1) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) From 0437d0a1304347d9bf803083900a78f8e8a8a1fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Apr 2022 13:54:26 +0200 Subject: [PATCH 65/67] general: adding `hasReviewableRepresentations` to skeleton data --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 5755619292..a8f4fec563 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -729,7 +729,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionWidth": data.get("resolutionWidth", 1920), "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), - "jobBatchName": data.get("jobBatchName", "") + "jobBatchName": data.get("jobBatchName", ""), + "hasReviewableRepresentations": data.get( + "hasReviewableRepresentations") } if "prerender" in instance.data["families"]: From 6893fa22e90e5d36d6a0bcd5726636534a903bec Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Apr 2022 14:01:35 +0200 Subject: [PATCH 66/67] ftrack: improving code logic it was not correctly distributing components --- .../publish/integrate_ftrack_instances.py | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index c11d5b9c68..b54db918a6 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -175,33 +175,40 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create copy of base comp item and append it review_item = copy.deepcopy(base_component_item) - # condition for multiple reviewable representations - # expand name to better label componenst + # get asset name and define extended name variant + asset_name = review_item["asset_data"]["name"] + extended_asset_name = "_".join( + (asset_name, repre["name"]) + ) + + # reset extended if no need for extended asset name if ( - not self.keep_first_subset_name_for_review - and multiple_reviewable + self.keep_first_subset_name_for_review + and is_first_review_repre ): - asset_name = review_item["asset_data"]["name"] - # define new extended name - extended_asset_name = "_".join( - (asset_name, repre["name"]) - ) - review_item["asset_data"]["name"] = extended_asset_name + extended_asset_name = "" + else: + # only rename if multiple reviewable + if multiple_reviewable: + review_item["asset_data"]["name"] = extended_asset_name + else: + extended_asset_name = "" - # rename asset name only if multiple reviewable repre - if is_first_review_repre: - # and rename all already created components - for _ci in component_list: - _ci["asset_data"]["name"] = extended_asset_name + # rename all already created components + # only if first repre and extended name available + if is_first_review_repre and extended_asset_name: + # and rename all already created components + for _ci in component_list: + _ci["asset_data"]["name"] = extended_asset_name - # and rename all already created src components - for _sci in src_components_to_add: - _sci["asset_data"]["name"] = extended_asset_name + # and rename all already created src components + for _sci in src_components_to_add: + _sci["asset_data"]["name"] = extended_asset_name - # rename also first thumbnail component if any - if first_thumbnail_component is not None: - first_thumbnail_component[ - "asset_data"]["name"] = extended_asset_name + # rename also first thumbnail component if any + if first_thumbnail_component is not None: + first_thumbnail_component[ + "asset_data"]["name"] = extended_asset_name frame_start = repre.get("frameStartFtrack") frame_end = repre.get("frameEndFtrack") @@ -236,6 +243,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): if is_first_review_repre: is_first_review_repre = False else: + # later detection for thumbnail duplication not_first_components.append(review_item) # Create copy of item before setting location @@ -280,7 +288,10 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): other_item = copy.deepcopy(base_component_item) # add extended name if any - if extended_asset_name: + if ( + not self.keep_first_subset_name_for_review + and extended_asset_name + ): other_item["asset_data"]["name"] = extended_asset_name other_item["component_data"] = { From 557aafdae326e5799f9258f02e5f27ef5ecb5f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 5 Apr 2022 14:36:11 +0200 Subject: [PATCH 67/67] fixed skeletal root --- .../publish/extract_unreal_skeletalmesh.py | 16 +++++----------- openpype/plugins/publish/integrate_new.py | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 4dcad47e8c..7ef7f2f181 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -55,25 +55,19 @@ class ExtractUnrealSkeletalMesh(openpype.api.Extractor): # variant we extract we need to rename top node of the rig correctly. # It is finally done in context manager so it won't affect current # scene. - parent = "{}{}".format( - instance.data["asset"], - instance.data.get("variant", "") - ) - joints_parents = cmds.ls(joints, long=True) - geo_parents = cmds.ls(geo, long=True) + # we rely on hierarchy under one root. + original_parent = to_extract[0].split("|")[1] - parent_node = { - parent.split("|")[1] for parent in (joints_parents + geo_parents) - }.pop() + parent_node = instance.data.get("asset") renamed_to_extract = [] for node in to_extract: node_path = node.split("|") - node_path[1] = parent + node_path[1] = parent_node renamed_to_extract.append("|".join(node_path)) - with renamed(parent_node, parent): + with renamed(original_parent, parent_node): self.log.info("Extracting: {}".format(renamed_to_extract, path)) fbx_exporter.export(renamed_to_extract, path) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index afa4e0a9cf..acdb05dd93 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -107,7 +107,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "hda", "usd", "staticMesh", - "skeletalMesh" + "skeletalMesh", "usdComposition", "usdOverride" ]