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/29] 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 9ad560ab7c4..1fe7e57abc1 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 @@ def get_dynamic_data( 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 0565b0b95c1..7cdd91a7eac 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 b1fb0542f27..8d9b88ed326 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 32dc9d1d1c7..f46360e34ad 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 @@ def process(self, instance): 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 901a2ec75e7..b886e7da75d 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 @@ def get_invalid(cls, instance): ["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 @@ def get_invalid(cls, instance): 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/29] 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 f46360e34ad..0799d574a2e 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 @@ def process(self, instance): 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/29] 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 24e8e4a29b1..4aaf6c705a5 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/29] 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 1fe7e57abc1..f62d15fe621 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 @@ def 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(): - 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 7cdd91a7eac..8713182d3f1 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 8d9b88ed326..604aa58b504 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 @@ def process(self, instance): ) # 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 0799d574a2e..6153417de4e 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 b2ef1743742..737664ffd3d 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 b886e7da75d..89769a34212 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 5a8c29c22d0..b3af6430487 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 bf214d9139a..9ced6a1d7d7 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 d46d449c776..2ab3ff5c54c 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 f08bee8b2d9..93aba808db7 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/29] 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 | 95 +++++--- 5 files changed, 290 insertions(+), 242 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 00000000000..3a8ae19ff74 --- /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 03995cdabee..00000000000 --- 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 604aa58b504..1a0a561efdb 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 @@ def process(self, instance): ["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 844084b9ab2..fbbe8e06b04 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -5,152 +5,29 @@ 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 @@ def process(self, instance): 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 @@ def process(self, instance): '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 6153417de4e..d3d491594ab 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) - - if not instance.data.get("cleanNodes"): - instance.data["cleanNodes"] = [] - - instance.data["cleanNodes"].append(static_mesh_name) - instance.data["cleanNodes"] += duplicates - - instance.data["setMembers"] = [static_mesh_name] - instance.data["setMembers"] += instance.data["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) + + delete_bin.extend(static_mesh_name) + delete_bin.extend(duplicates) + + members = [static_mesh_name] + members += 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/29] 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 3a8ae19ff74..659f456e1ad 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -110,7 +110,7 @@ def default_options(self): 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 d3d491594ab..c5d2710dc2f 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 @@ def process(self, instance): ) 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 @@ def process(self, instance): 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 89769a34212..e233fd190c3 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 @@ def get_invalid(cls, instance): ["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/29] 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 93aba808db7..efed25287a2 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/29] =?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 659f456e1ad..00c58153af5 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 1a0a561efdb..2c0bec2c1a5 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 @@ def process(self, instance): # 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 c5d2710dc2f..0c7d61f8f53 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 @@ def process(self, instance): } 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 e233fd190c3..fd19e3d2af0 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 @@ def get_invalid(cls, instance): 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/29] 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 737664ffd3d..c05121a1b03 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 fd19e3d2af0..d15d52f3bd2 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 @@ def get_invalid(cls, instance): 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 b3af6430487..5e1b04889f5 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/29] 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 00c58153af5..7980cd029c9 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -154,15 +154,8 @@ def set_options_from_instance(self, instance): 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/29] 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 e1b0917e9df..67b820a2473 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 b746fedf728..00000000000 --- 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 d277f474ab0..00000000000 --- 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/29] 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 7980cd029c9..92683da51bc 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -154,8 +154,10 @@ def set_options_from_instance(self, instance): 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 2c0bec2c1a5..ddcc3f691f7 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 @@ def process(self, instance): # 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/29] 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 2ab3ff5c54c..7d01248653f 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 efed25287a2..86786cc9ed0 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/29] 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 00000000000..a6deeeee2e4 --- /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 00000000000..4b1de865c51 --- /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 ddcc3f691f7..59f8df1ef19 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 @@ 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 00000000000..0ad1a92292f --- /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 0c7d61f8f53..22a3af30598 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 @@ 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 48b87c697ba..fce8c73d1d4 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 86786cc9ed0..b7f6414cfbf 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 362835e5581..6317d30b3fb 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 0544b4bab70..6dc10ed2a55 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/29] 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 4b1de865c51..7d479b706c2 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 @@ def process(self, instance): 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 0ad1a92292f..c0b408c3f0e 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 @@ def process(self, instance): 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/29] 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 59f8df1ef19..02f21187b32 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 @@ def process(self, instance): # 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 22a3af30598..987370d3959 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 @@ def process(self, instance): 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 d15d52f3bd2..1ecf4365824 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 @@ def get_invalid(cls, instance): 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 @@ def get_invalid(cls, instance): 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/29] 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 92683da51bc..f8fe1895891 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -84,10 +84,10 @@ def default_options(self): 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 @@ def default_options(self): "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 7d479b706c2..2b176e3a6d4 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 02f21187b32..faa5880e437 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 @@ def process(self, instance): 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 fa181301ee5..1f509365c77 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/29] 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 faa5880e437..728a26931b8 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 987370d3959..02dd5dc5729 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 1ecf4365824..920e0982dc3 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 @@ def get_invalid(cls, instance): ["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 @@ def get_invalid(cls, instance): 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 @@ def get_invalid(cls, instance): 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/29] 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 728a26931b8..79d0856fa00 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/29] 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 f8fe1895891..260241f5fc3 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -198,5 +198,5 @@ def export(members, path): 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 41c67a6209f..a5199d84437 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3085,11 +3085,20 @@ def _colormanage(**kwargs): @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 c0b408c3f0e..6f4c70fc072 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 @@ 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 @@ def process(self, instance): 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 02dd5dc5729..c3cc322a29d 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 @@ 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 @@ def process(self, instance): 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/29] 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 920e0982dc3..c0eeb826885 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 @@ def get_invalid(cls, instance): 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 @@ def get_invalid(cls, instance): else: expected_collision = "{}_{}".format( cl_m.group("prefix"), - instance.data.get("subset") + mesh_name ) if not obj.startswith(expected_collision): @@ -121,11 +124,11 @@ def get_invalid(cls, instance): "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/29] 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 6f4c70fc072..58154638e6b 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 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 @@ def process(self, instance): 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 8f77e92d6f456e06c98326c4d0c55b6cb4f70208 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 22 Mar 2022 16:48:16 +0100 Subject: [PATCH 23/29] 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 58154638e6b..5b0eb5a3bcb 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 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 @@ def process(self, instance): 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 @@ def process(self, instance): 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 24/29] 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 c3cc322a29d..92fa1b5933e 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 00000000000..d30c4cb69df --- /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 00000000000..dda7e063f69 --- /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 25/29] 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 dda7e063f69..cffbd138342 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 26/29] 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 cffbd138342..54a86d27cfb 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 @@ 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 27/29] 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 5b0eb5a3bcb..98dbf117dc8 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 @@ def process(self, instance): 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 85e2601022e0f5bf4596293c30f0bc653992013a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 1 Apr 2022 18:36:29 +0200 Subject: [PATCH 28/29] =?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 2b176e3a6d4..79693bb35e4 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 98dbf117dc8..4dcad47e8c1 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 @@ 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 @@ def process(self, instance): 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 92fa1b5933e..69d51f9ff1d 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 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 c0eeb826885..43f6c85827c 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 @@ def get_invalid(cls, instance): ["CreateUnrealStaticMesh"] ["collision_prefixes"] ) - static_mesh_prefix = ( - project_settings - ["maya"] - ["create"] - ["CreateUnrealStaticMesh"] - ["static_mesh_prefix"] - ) if cls.validate_mesh: # compile regex for testing names 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 29/29] 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 4dcad47e8c1..7ef7f2f1817 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 @@ def process(self, instance): # 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 afa4e0a9cf8..acdb05dd93d 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" ]