From e19e7e1f23ad9c0206305ecb4853cc9d7586be3f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Jun 2023 17:10:55 +0100 Subject: [PATCH 001/244] initial working version of abc options. --- openpype/hosts/maya/api/lib.py | 144 +++++++-- .../maya/plugins/create/create_animation.py | 87 +++-- .../maya/plugins/create/create_pointcache.py | 76 +++-- .../plugins/publish/extract_pointcache.py | 43 ++- .../defaults/project_settings/maya.json | 66 +++- .../schemas/schema_maya_create.json | 296 ++++++++++++++++-- 6 files changed, 582 insertions(+), 130 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b02d3c9b39..fb65bc1072 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -98,6 +98,7 @@ _alembic_options = { "renderableOnly": bool, "step": float, "stripNamespaces": bool, + "verbose": bool, "uvWrite": bool, "wholeFrameGeo": bool, "worldSpace": bool, @@ -115,7 +116,9 @@ _alembic_options = { "melPostJobCallback": str, "pythonPerFrameCallback": str, "pythonPostJobCallback": str, - "selection": bool + "selection": bool, + "preRoll": bool, + "preRollStartFrame": int } INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} @@ -1083,12 +1086,28 @@ def is_visible(node, def extract_alembic(file, startFrame=None, endFrame=None, + frameRange="", + eulerFilter=True, + noNormals=False, + preRoll=False, + renderableOnly=False, selection=True, uvWrite=True, - eulerFilter=True, + writeColorSets=False, + writeFaceSets=False, + wholeFrameGeo=False, + worldSpace=False, + writeVisibility=False, + writeUVSets=False, + writeCreases=False, dataFormat="ogawa", + step=1.0, + attr=[], + attrPrefix=[], + root=[], + stripNamespaces=True, verbose=False, - **kwargs): + preRollStartFrame=0): """Extract a single Alembic Cache. This extracts an Alembic cache using the `-selection` flag to minimize @@ -1106,37 +1125,87 @@ def extract_alembic(file, string formatted as: "startFrame endFrame". This argument overrides `startFrame` and `endFrame` arguments. - dataFormat (str): The data format to use for the cache, - defaults to "ogawa" - - verbose (bool): When on, outputs frame number information to the - Script Editor or output window during extraction. + eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with + an Euler filter. Euler filtering helps resolve irregularities in + rotations especially if X, Y, and Z rotations exceed 360 degrees. + Defaults to True. noNormals (bool): When on, normal data from the original polygon objects is not included in the exported Alembic cache file. + preRoll (bool): This frame range will not be sampled. + Defaults to False. + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, such as hidden objects, are not included in the Alembic file. Defaults to False. + selection (bool): Write out all all selected nodes from the + active selection list that are descendents of the roots specified + with -root. Defaults to False. + + uvWrite (bool): When on, UV data from polygon meshes and subdivision + objects are written to the Alembic file. Only the current UV map is + included. + + writeColorSets (bool): Write all color sets on MFnMeshes as + color 3 or color 4 indexed geometry parameters with face varying + scope. Defaults to False. + + writeFaceSets (bool): Write all Face sets on MFnMeshes. + Defaults to False. + + wholeFrameGeo (bool): Data for geometry will only be written + out on whole frames. Defaults to False. + + worldSpace (bool): When on, the top node in the node hierarchy is + stored as world space. By default, these nodes are stored as local + space. Defaults to False. + + writeVisibility (bool): Visibility state will be stored in + the Alembic file. Otherwise everything written out is treated as + visible. Defaults to False. + + writeUVSets (bool): Write all uv sets on MFnMeshes as vector + 2 indexed geometry parameters with face varying scope. Defaults to + False. + + writeCreases (bool): If the mesh has crease edges or crease + vertices, the mesh (OPolyMesh) would now be written out as an OSubD + and crease info will be stored in the Alembic file. Otherwise, + creases info won't be preserved in Alembic file unless a custom + Boolean attribute SubDivisionMesh has been added to mesh node and + its value is true. Defaults to False. + + dataFormat (str): The data format to use for the cache, + defaults to "ogawa" + + step (float): The time interval (expressed in frames) at + which the frame range is sampled. Additional samples around each + frame can be specified with -frs. Defaults to 1.0. + + attr (list of str, optional): A specific geometric attribute to write + out. Defaults to []. + + attrPrefix (list of str, optional): Prefix filter for determining which + geometric attributes to write out. Defaults to ["ABC_"]. + + root (list of str): Maya dag path which will be parented to + the root of the Alembic file. Defaults to [], which means the + entire scene will be written out. + stripNamespaces (bool): When on, any namespaces associated with the exported objects are removed from the Alembic file. For example, an object with the namespace taco:foo:bar appears as bar in the Alembic file. - uvWrite (bool): When on, UV data from polygon meshes and subdivision - objects are written to the Alembic file. Only the current UV map is - included. - - worldSpace (bool): When on, the top node in the node hierarchy is - stored as world space. By default, these nodes are stored as local - space. Defaults to False. - - eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with - an Euler filter. Euler filtering helps resolve irregularities in - rotations especially if X, Y, and Z rotations exceed 360 degrees. - Defaults to True. + verbose (bool): When on, outputs frame number information to the + Script Editor or output window during extraction. + preRollStartFrame (float): The frame to start scene + evaluation at. This is used to set the starting frame for time + dependent translations and can be used to evaluate run-up that + isn't actually translated. Defaults to 0. """ # Ensure alembic exporter is loaded @@ -1147,7 +1216,7 @@ def extract_alembic(file, # Pass the start and end frame on as `frameRange` so that it # never conflicts with that argument - if "frameRange" not in kwargs: + if not frameRange: # Fallback to maya timeline if no start or end frame provided. if startFrame is None: startFrame = cmds.playbackOptions(query=True, @@ -1159,23 +1228,38 @@ def extract_alembic(file, # Ensure valid types are converted to frame range assert isinstance(startFrame, _alembic_options["startFrame"]) assert isinstance(endFrame, _alembic_options["endFrame"]) - kwargs["frameRange"] = "{0} {1}".format(startFrame, endFrame) + frameRange = "{0} {1}".format(startFrame, endFrame) else: # Allow conversion from tuple for `frameRange` - frame_range = kwargs["frameRange"] - if isinstance(frame_range, (list, tuple)): - assert len(frame_range) == 2 - kwargs["frameRange"] = "{0} {1}".format(frame_range[0], - frame_range[1]) + if isinstance(frameRange, (list, tuple)): + assert len(frameRange) == 2 + frameRange = "{0} {1}".format(frameRange[0], frameRange[1]) # Assemble options options = { "selection": selection, - "uvWrite": uvWrite, + "frameRange": frameRange, "eulerFilter": eulerFilter, - "dataFormat": dataFormat + "noNormals": noNormals, + "preRoll": preRoll, + "renderableOnly": renderableOnly, + "selection": selection, + "uvWrite": uvWrite, + "writeColorSets": writeColorSets, + "writeFaceSets": writeFaceSets, + "wholeFrameGeo": wholeFrameGeo, + "worldSpace": worldSpace, + "writeVisibility": writeVisibility, + "writeUVSets": writeUVSets, + "writeCreases": writeCreases, + "dataFormat": dataFormat, + "step": step, + "attr": attr, + "attrPrefix": attrPrefix, + "stripNamespaces": stripNamespaces, + "verbose": verbose, + "preRollStartFrame": preRollStartFrame } - options.update(kwargs) # Validate options for key, value in options.copy().items(): diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 095cbcdd64..92f2556680 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -17,44 +17,65 @@ class CreateAnimation(plugin.Creator): label = "Animation" family = "animation" icon = "male" - write_color_sets = False - write_face_sets = False - include_parent_hierarchy = False - include_user_defined_attributes = False + includeUserDefinedAttributes = False + eulerFilter = True + noNormals = False + preRoll = False + renderableOnly = False + uvWrite = True + writeColorSets = False + writeFaceSets = False + wholeFrameGeo = False + worldSpace = True + writeVisibility = True + writeUVSets = True + writeCreases = False + dataFormat = "ogawa" + step = 1.0 + attr = "" + attrPrefix = "" + stripNamespaces = True + verbose = False + preRollStartFrame = 0 + farm = False + priority = 50 + includeParentHierarchy = False # Include parent groups + refresh = False # Default to suspend refresh. + visibleOnly = False # only nodes that are visible def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): self.data[key] = value - # Write vertex colors with the geometry. - self.data["writeColorSets"] = self.write_color_sets - self.data["writeFaceSets"] = self.write_face_sets - - # Include only renderable visible shapes. - # Skips locators and empty transforms - self.data["renderableOnly"] = False - - # Include only nodes that are visible at least once during the - # frame range. - self.data["visibleOnly"] = False - - # Include the groups above the out_SET content - self.data["includeParentHierarchy"] = self.include_parent_hierarchy - - # Default to exporting world-space - self.data["worldSpace"] = True - - # Default to not send to farm. - self.data["farm"] = False - self.data["priority"] = 50 - - # Default to write normals. - self.data["writeNormals"] = True - - value = self.include_user_defined_attributes - self.data["includeUserDefinedAttributes"] = value + attrs = [ + "includeUserDefinedAttributes", + "eulerFilter", + "noNormals", + "preRoll", + "renderableOnly", + "uvWrite", + "writeColorSets", + "writeFaceSets", + "wholeFrameGeo", + "worldSpace", + "writeVisibility", + "writeUVSets", + "writeCreases", + "dataFormat", + "step", + "attr", + "attrPrefix", + "stripNamespaces", + "verbose", + "preRollStartFrame", + "farm", + "priority", + "includeParentHierarchy", + "refresh", + "visibleOnly" + ] + for attr in attrs: + self.data[attr] = getattr(self, attr) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index 1b8d5e6850..c1e6056d28 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -13,9 +13,31 @@ class CreatePointCache(plugin.Creator): label = "Point Cache" family = "pointcache" icon = "gears" - write_color_sets = False - write_face_sets = False - include_user_defined_attributes = False + includeUserDefinedAttributes = False + eulerFilter = True + noNormals = False + preRoll = False + renderableOnly = False + uvWrite = True + writeColorSets = False + writeFaceSets = False + wholeFrameGeo = False + worldSpace = True + writeVisibility = True + writeUVSets = True + writeCreases = False + dataFormat = "ogawa" + step = 1.0 + attr = "" + attrPrefix = "" + stripNamespaces = True + verbose = False + preRollStartFrame = 0 + farm = False + priority = 50 + includeParentHierarchy = False # Include parent groups + refresh = False # Default to suspend refresh. + visibleOnly = False # only nodes that are visible def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) @@ -23,25 +45,35 @@ class CreatePointCache(plugin.Creator): # Add animation data self.data.update(lib.collect_animation_data()) - # Vertex colors with the geometry. - self.data["writeColorSets"] = self.write_color_sets - # Vertex colors with the geometry. - self.data["writeFaceSets"] = self.write_face_sets - self.data["renderableOnly"] = False # Only renderable visible shapes - self.data["visibleOnly"] = False # only nodes that are visible - self.data["includeParentHierarchy"] = False # Include parent groups - self.data["worldSpace"] = True # Default to exporting world-space - self.data["refresh"] = False # Default to suspend refresh. - - # Add options for custom attributes - value = self.include_user_defined_attributes - self.data["includeUserDefinedAttributes"] = value - self.data["attr"] = "" - self.data["attrPrefix"] = "" - - # Default to not send to farm. - self.data["farm"] = False - self.data["priority"] = 50 + attrs = [ + "includeUserDefinedAttributes", + "eulerFilter", + "noNormals", + "preRoll", + "renderableOnly", + "uvWrite", + "writeColorSets", + "writeFaceSets", + "wholeFrameGeo", + "worldSpace", + "writeVisibility", + "writeUVSets", + "writeCreases", + "dataFormat", + "step", + "attr", + "attrPrefix", + "stripNamespaces", + "verbose", + "preRollStartFrame", + "farm", + "priority", + "includeParentHierarchy", + "refresh", + "visibleOnly" + ] + for attr in attrs: + self.data[attr] = getattr(self, attr) def process(self): instance = super(CreatePointCache, self).process() diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index f44c13767c..1081750749 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -52,19 +52,29 @@ class ExtractAlembic(publish.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) - options = { - "step": instance.data.get("step", 1.0), - "attr": attrs, - "attrPrefix": attr_prefixes, - "writeVisibility": True, - "writeCreases": True, - "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False), - "uvWrite": True, - "selection": True, - "worldSpace": instance.data.get("worldSpace", True) - } - + options = {"selection": True} + option_keys = [ + "eulerFilter", + "noNormals", + "preRoll", + "renderableOnly", + "uvWrite", + "writeColorSets", + "writeFaceSets", + "wholeFrameGeo", + "worldSpace", + "writeVisibility", + "writeUVSets", + "writeCreases", + "dataFormat", + "step", + "stripNamespaces", + "verbose", + "preRollStartFrame" + ] + for key in option_keys: + options[key] = instance.data[key] + self.log.info(options) if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual @@ -81,12 +91,13 @@ class ExtractAlembic(publish.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_nodes_in_range(nodes, - start=start, - end=end)) + nodes = list( + iter_visible_nodes_in_range(nodes, start=start, end=end) + ) suspend = not instance.data.get("refresh", False) self.log.info(nodes) + self.log.info(options) with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 19c3da13e6..7072e2d1a4 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -556,13 +556,38 @@ }, "CreateAnimation": { "enabled": false, + "includeUserDefinedAttributes": false, + "eulerFilter": true, + "noNormals": false, + "preRoll": false, + "renderableOnly": false, + "uvWrite": true, + "WriteColorSets": false, + "writeFaceSets": false, + "wholeFrameGeo": false, + "worldSpace": true, + "writeVisibility": true, + "writeUVSets": true, + "writeCreases": false, + "dataFormat": "ogawa", + "step": 1.0, + "attr": "", + "attrPrefix": "", + "stripNamespaces": true, + "verbose": false, + "preRollStartFrame": 0, + "farm": false, + "priority": 50, + "includeParentHierarchy": false, + "refresh": false, + "visibleOnly": false, + "defaults": [ + "Main" + ], "write_color_sets": false, "write_face_sets": false, "include_parent_hierarchy": false, - "include_user_defined_attributes": false, - "defaults": [ - "Main" - ] + "include_user_defined_attributes": false }, "CreateModel": { "enabled": true, @@ -576,12 +601,37 @@ }, "CreatePointCache": { "enabled": true, - "write_color_sets": false, - "write_face_sets": false, - "include_user_defined_attributes": false, + "includeUserDefinedAttributes": false, + "eulerFilter": true, + "noNormals": false, + "preRoll": false, + "renderableOnly": false, + "uvWrite": true, + "WriteColorSets": false, + "writeFaceSets": false, + "wholeFrameGeo": false, + "worldSpace": true, + "writeVisibility": true, + "writeUVSets": true, + "writeCreases": false, + "dataFormat": "ogawa", + "step": 1.0, + "attr": "", + "attrPrefix": "", + "stripNamespaces": true, + "verbose": false, + "preRollStartFrame": 0, + "farm": false, + "priority": 50, + "includeParentHierarchy": false, + "refresh": false, + "visibleOnly": false, "defaults": [ "Main" - ] + ], + "write_color_sets": false, + "write_face_sets": false, + "include_user_defined_attributes": false }, "CreateProxyAlembic": { "enabled": true, 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 a8b76a0331..aeff809cf6 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 @@ -128,30 +128,157 @@ "label": "Enabled" }, { - "type": "boolean", - "key": "write_color_sets", - "label": "Write Color Sets" + "type": "boolean", + "key": "includeUserDefinedAttributes", + "label": "Include User Defined Attributes" }, { - "type": "boolean", - "key": "write_face_sets", - "label": "Write Face Sets" + "type": "boolean", + "key": "eulerFilter", + "label": "Euler Filter" }, { - "type": "boolean", - "key": "include_parent_hierarchy", - "label": "Include Parent Hierarchy" + "type": "boolean", + "key": "noNormals", + "label": "No Normals" }, { - "type": "boolean", - "key": "include_user_defined_attributes", - "label": "Include User Defined Attributes" + "type": "boolean", + "key": "preRoll", + "label": "Pre Roll" + }, + { + "type": "boolean", + "key": "renderableOnly", + "label": "Renderable Only" + }, + { + "type": "boolean", + "key": "uvWrite", + "label": "UV Write" + }, + { + "type": "boolean", + "key": "WriteColorSets", + "label": "Write Color Sets" + }, + { + "type": "boolean", + "key": "writeFaceSets", + "label": "Write Face Sets" + }, + { + "type": "boolean", + "key": "wholeFrameGeo", + "label": "Whole Frame Geo" + }, + { + "type": "boolean", + "key": "worldSpace", + "label": "World Space" + }, + { + "type": "boolean", + "key": "writeVisibility", + "label": "Write Visibility" + }, + { + "type": "boolean", + "key": "writeUVSets", + "label": "Write UV Sets" + }, + { + "type": "boolean", + "key": "writeCreases", + "label": "Write Creases" + }, + { + "type": "text", + "key": "dataFormat", + "label": "Data Format" + }, + { + "type": "number", + "key": "step", + "label": "Step", + "minimum": 0.0, + "decimal": 4 + }, + { + "type": "text", + "key": "attr", + "label": "Attr" + }, + { + "type": "text", + "key": "attrPrefix", + "label": "Attr Prefix" + }, + { + "type": "boolean", + "key": "stripNamespaces", + "label": "StripNamespaces" + }, + { + "type": "boolean", + "key": "verbose", + "label": "Verbose" + }, + { + "type": "number", + "key": "preRollStartFrame", + "label": "Pre Roll Start Frame" + }, + { + "type": "boolean", + "key": "farm", + "label": "Farm" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "boolean", + "key": "includeParentHierarchy", + "label": "Include Parent Hierarchy" + }, + { + "type": "boolean", + "key": "refresh", + "label": "Refresh" + }, + { + "type": "boolean", + "key": "visibleOnly", + "label": "Visible Only" }, { "type": "list", "key": "defaults", "label": "Default Subsets", "object_type": "text" + }, + { + "type": "boolean", + "key": "write_color_sets", + "label": "DEPRECATED! Write Color Sets" + }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "DEPRECATED! Write Face Sets" + }, + { + "type": "boolean", + "key": "include_parent_hierarchy", + "label": "DEPRECATED! Include Parent Hierarchy" + }, + { + "type": "boolean", + "key": "include_user_defined_attributes", + "label": "DEPRECATED! Include User Defined Attributes" } ] }, @@ -198,25 +325,152 @@ "label": "Enabled" }, { - "type": "boolean", - "key": "write_color_sets", - "label": "Write Color Sets" + "type": "boolean", + "key": "includeUserDefinedAttributes", + "label": "Include User Defined Attributes" }, { - "type": "boolean", - "key": "write_face_sets", - "label": "Write Face Sets" + "type": "boolean", + "key": "eulerFilter", + "label": "Euler Filter" }, { - "type": "boolean", - "key": "include_user_defined_attributes", - "label": "Include User Defined Attributes" + "type": "boolean", + "key": "noNormals", + "label": "No Normals" + }, + { + "type": "boolean", + "key": "preRoll", + "label": "Pre Roll" + }, + { + "type": "boolean", + "key": "renderableOnly", + "label": "Renderable Only" + }, + { + "type": "boolean", + "key": "uvWrite", + "label": "UV Write" + }, + { + "type": "boolean", + "key": "WriteColorSets", + "label": "Write Color Sets" + }, + { + "type": "boolean", + "key": "writeFaceSets", + "label": "Write Face Sets" + }, + { + "type": "boolean", + "key": "wholeFrameGeo", + "label": "Whole Frame Geo" + }, + { + "type": "boolean", + "key": "worldSpace", + "label": "World Space" + }, + { + "type": "boolean", + "key": "writeVisibility", + "label": "Write Visibility" + }, + { + "type": "boolean", + "key": "writeUVSets", + "label": "Write UV Sets" + }, + { + "type": "boolean", + "key": "writeCreases", + "label": "Write Creases" + }, + { + "type": "text", + "key": "dataFormat", + "label": "Data Format" + }, + { + "type": "number", + "key": "step", + "label": "Step", + "minimum": 0.0, + "decimal": 4 + }, + { + "type": "text", + "key": "attr", + "label": "Attr" + }, + { + "type": "text", + "key": "attrPrefix", + "label": "Attr Prefix" + }, + { + "type": "boolean", + "key": "stripNamespaces", + "label": "StripNamespaces" + }, + { + "type": "boolean", + "key": "verbose", + "label": "Verbose" + }, + { + "type": "number", + "key": "preRollStartFrame", + "label": "Pre Roll Start Frame" + }, + { + "type": "boolean", + "key": "farm", + "label": "Farm" + }, + { + "type": "number", + "key": "priority", + "label": "Priority" + }, + { + "type": "boolean", + "key": "includeParentHierarchy", + "label": "Include Parent Hierarchy" + }, + { + "type": "boolean", + "key": "refresh", + "label": "Refresh" + }, + { + "type": "boolean", + "key": "visibleOnly", + "label": "Visible Only" }, { "type": "list", "key": "defaults", "label": "Default Subsets", "object_type": "text" + }, + { + "type": "boolean", + "key": "write_color_sets", + "label": "DEPRECATED! Write Color Sets" + }, + { + "type": "boolean", + "key": "write_face_sets", + "label": "DEPRECATED! Write Face Sets" + }, + { + "type": "boolean", + "key": "include_user_defined_attributes", + "label": "DEPRECATED! Include User Defined Attributes" } ] }, From d87e64e4ea22b8ba16afb549e8b4f222ddc45c95 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 08:40:03 +0100 Subject: [PATCH 002/244] Docs --- website/docs/artist_hosts_maya.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index e36ccb77d2..bc7b719990 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -347,6 +347,24 @@ Example setup: - **Include User Defined Attribudes**: include all user defined attributes in the publish. - **Farm**: if your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle this attribute in created pointcache instance to True. - **Priority**: Farm priority. +- **Euler Filter**: Apply Euler filter while sampling rotations. +- **No Normals**: Present normal data for Alembic poly meshes will not be written. +- **Pre Roll**: This frame range will not be sampled. +- **Renderable Only**: Non-renderable hierarchy (invisible, or templated) will not be written out. +- **UV Write**: Uv data for PolyMesh and SubD shapes will be written to the Alembic file. Only the current uv map is used. +- **Write Color Sets**: Write all color sets on MFnMeshes as color 3 or color 4 indexed geometry parameters with face varying scope. +- **Write Face Sets**: Write all Face sets on MFnMeshes. +- **Whole Frame Geo**: Data for geometry will only be written out on whole frames. +- **World Space**: Any root nodes will be stored in world space. +- **Write Visibility**: Visibility state will be stored in the Alembic file. Otherwise everything written out is treated as visible. +- **Write UV Sets**: Write all uv sets on MFnMeshes as vector 2 indexed geometry parameters with face varying scope. +- **Write Creases**: If the mesh has crease edges or crease vertices, the mesh (OPolyMesh) would now be written out as an OSubD and crease info will be stored in the Alembic file. Otherwise, creases info won't be preserved in Alembic file unless a custom Boolean attribute SubDivisionMesh has been added to mesh node and its value is true. +- **Data Format**: The data format to use to write the file. Can be either "HDF" or "Ogawa". +- **Strip Namespaces**: Namespaces will be stripped off of the node before being written to Alembic. The int specifies how many namespaces will be stripped off of the node name. Be careful that the new stripped name does not collide with other sibling node names. +- **Verbose**: Prints the current frame that is being evaluated. +- **Pre Roll Start Frame**: The frame to start scene evaluation at. This is used to set the starting frame for time dependent translations and can be used to evaluate run-up that isn't actually translated. +- **Include Parent Hierarchy**: Set the root nodes if we don't want to include parents. The roots are to be considered the ones that are the actual direct members of the set. +- **Visible Only**: Does not filter out nodes that are only hidden on some frames as it counts "animated" or "connected" visibilities as if it's always visible. ### Loading Point Caches From c07729dc9a2a4ccf8374e91e9e866678c7516017 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 08:57:01 +0100 Subject: [PATCH 003/244] Hound --- openpype/hosts/maya/api/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index fb65bc1072..a34318f4e9 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3,7 +3,6 @@ import os from pprint import pformat import sys -import platform import uuid import re @@ -1102,9 +1101,9 @@ def extract_alembic(file, writeCreases=False, dataFormat="ogawa", step=1.0, - attr=[], - attrPrefix=[], - root=[], + attr=None, + attrPrefix=None, + root=None, stripNamespaces=True, verbose=False, preRollStartFrame=0): From e4a76418557b679c74f6755791afae0023d0fe04 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 10:51:10 +0100 Subject: [PATCH 004/244] Backwards compatibility attributes. --- .../maya/plugins/publish/collect_animation.py | 32 +++++++++++++------ .../plugins/publish/collect_pointcache.py | 29 +++++++++++------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 8f523f770b..74efe6af9b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -59,16 +59,28 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): instance.data["families"].append("publish.farm") # Collect user defined attributes. - if not instance.data.get("includeUserDefinedAttributes", False): - return + if instance.data.get("includeUserDefinedAttributes", False): + user_defined_attributes = set() + for node in hierarchy: + attrs = cmds.listAttr(node, userDefined=True) or list() + shapes = cmds.listRelatives(node, shapes=True) or list() + for shape in shapes: + attrs.extend( + cmds.listAttr(shape, userDefined=True) or list() + ) - user_defined_attributes = set() - for node in hierarchy: - attrs = cmds.listAttr(node, userDefined=True) or list() - shapes = cmds.listRelatives(node, shapes=True) or list() - for shape in shapes: - attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) + user_defined_attributes.update(attrs) - user_defined_attributes.update(attrs) + instance.data["userDefinedAttributes"] = list( + user_defined_attributes + ) - instance.data["userDefinedAttributes"] = list(user_defined_attributes) + # Backwards compatibility for attributes. + backwards_mapping = { + "write_color_sets": "writeColorSets", + "write_face_sets": "writeFaceSets", + "include_parent_hierarchy": "includeParentHierarchy", + "include_user_defined_attributes": "includeUserDefinedAttributes" + } + for key, value in backwards_mapping.items(): + instance.data[value] = instance.data[key] diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index d0430c5612..eba6e510e7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -45,15 +45,26 @@ class CollectPointcache(pyblish.api.InstancePlugin): # Collect user defined attributes. if not instance.data.get("includeUserDefinedAttributes", False): - return + user_defined_attributes = set() + for node in instance: + attrs = cmds.listAttr(node, userDefined=True) or list() + shapes = cmds.listRelatives(node, shapes=True) or list() + for shape in shapes: + attrs.extend( + cmds.listAttr(shape, userDefined=True) or list() + ) - user_defined_attributes = set() - for node in instance: - attrs = cmds.listAttr(node, userDefined=True) or list() - shapes = cmds.listRelatives(node, shapes=True) or list() - for shape in shapes: - attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) + user_defined_attributes.update(attrs) - user_defined_attributes.update(attrs) + instance.data["userDefinedAttributes"] = list( + user_defined_attributes + ) - instance.data["userDefinedAttributes"] = list(user_defined_attributes) + # Backwards compatibility for attributes. + backwards_mapping = { + "write_color_sets": "writeColorSets", + "write_face_sets": "writeFaceSets", + "include_user_defined_attributes": "includeUserDefinedAttributes" + } + for key, value in backwards_mapping.items(): + instance.data[value] = instance.data[key] From cda90897b697f23b10df09244ca854f7eafe9cb3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 10:51:26 +0100 Subject: [PATCH 005/244] Debug logging --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 1081750749..1968cad976 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -96,8 +96,7 @@ class ExtractAlembic(publish.Extractor): ) suspend = not instance.data.get("refresh", False) - self.log.info(nodes) - self.log.info(options) + self.log.debug(nodes) with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) From 1a5201af40b2c75ebad89a687e61aa9d1827dc08 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 10:51:37 +0100 Subject: [PATCH 006/244] Remove redundant code. --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 1968cad976..afbbc32290 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -81,10 +81,6 @@ class ExtractAlembic(publish.Extractor): # direct members of the set options["root"] = roots - if int(cmds.about(version=True)) >= 2017: - # Since Maya 2017 alembic supports multiple uv sets - write them. - options["writeUVSets"] = True - if instance.data.get("visibleOnly", False): # If we only want to include nodes that are visible in the frame # range then we need to do our own check. Alembic's `visibleOnly` From 0342870cdb66374aa55a9a8fcde7662b3385df49 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 11:12:32 +0100 Subject: [PATCH 007/244] Failsafe --- openpype/hosts/maya/plugins/publish/collect_animation.py | 3 ++- openpype/hosts/maya/plugins/publish/collect_pointcache.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 74efe6af9b..af852b031b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -83,4 +83,5 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): "include_user_defined_attributes": "includeUserDefinedAttributes" } for key, value in backwards_mapping.items(): - instance.data[value] = instance.data[key] + if key in instance.data: + instance.data[value] = instance.data[key] diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index eba6e510e7..537e6d44c5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -67,4 +67,5 @@ class CollectPointcache(pyblish.api.InstancePlugin): "include_user_defined_attributes": "includeUserDefinedAttributes" } for key, value in backwards_mapping.items(): - instance.data[value] = instance.data[key] + if key in instance.data: + instance.data[value] = instance.data[key] From fab76c619a3409043a68a6cdcdf40d92095ae35e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 11:54:53 +0100 Subject: [PATCH 008/244] Ensure includeUserDefinedAttributes gets correct value --- .../maya/plugins/publish/collect_animation.py | 22 +++++++++---------- .../plugins/publish/collect_pointcache.py | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index af852b031b..5cfdb358ce 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -58,6 +58,17 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + # Backwards compatibility for attributes. + backwards_mapping = { + "write_color_sets": "writeColorSets", + "write_face_sets": "writeFaceSets", + "include_parent_hierarchy": "includeParentHierarchy", + "include_user_defined_attributes": "includeUserDefinedAttributes" + } + for key, value in backwards_mapping.items(): + if key in instance.data: + instance.data[value] = instance.data[key] + # Collect user defined attributes. if instance.data.get("includeUserDefinedAttributes", False): user_defined_attributes = set() @@ -74,14 +85,3 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): instance.data["userDefinedAttributes"] = list( user_defined_attributes ) - - # Backwards compatibility for attributes. - backwards_mapping = { - "write_color_sets": "writeColorSets", - "write_face_sets": "writeFaceSets", - "include_parent_hierarchy": "includeParentHierarchy", - "include_user_defined_attributes": "includeUserDefinedAttributes" - } - for key, value in backwards_mapping.items(): - if key in instance.data: - instance.data[value] = instance.data[key] diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 537e6d44c5..4f3ae833fd 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -43,8 +43,18 @@ class CollectPointcache(pyblish.api.InstancePlugin): instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) + # Backwards compatibility for attributes. + backwards_mapping = { + "write_color_sets": "writeColorSets", + "write_face_sets": "writeFaceSets", + "include_user_defined_attributes": "includeUserDefinedAttributes" + } + for key, value in backwards_mapping.items(): + if key in instance.data: + instance.data[value] = instance.data[key] + # Collect user defined attributes. - if not instance.data.get("includeUserDefinedAttributes", False): + if instance.data.get("includeUserDefinedAttributes", False): user_defined_attributes = set() for node in instance: attrs = cmds.listAttr(node, userDefined=True) or list() @@ -59,13 +69,3 @@ class CollectPointcache(pyblish.api.InstancePlugin): instance.data["userDefinedAttributes"] = list( user_defined_attributes ) - - # Backwards compatibility for attributes. - backwards_mapping = { - "write_color_sets": "writeColorSets", - "write_face_sets": "writeFaceSets", - "include_user_defined_attributes": "includeUserDefinedAttributes" - } - for key, value in backwards_mapping.items(): - if key in instance.data: - instance.data[value] = instance.data[key] From 99233285451378a8aea73965659527476adce65e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 23 Jun 2023 15:09:28 +0100 Subject: [PATCH 009/244] Update openpype/hosts/maya/plugins/publish/collect_pointcache.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_pointcache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 4f3ae833fd..5946d62f9e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -51,7 +51,10 @@ class CollectPointcache(pyblish.api.InstancePlugin): } for key, value in backwards_mapping.items(): if key in instance.data: - instance.data[value] = instance.data[key] + self.log.debug( + "Using legacy attribute name '{}' since it exists.".format(key) + ) + instance.data[value] = instance.data.pop(key) # Collect user defined attributes. if instance.data.get("includeUserDefinedAttributes", False): From b601aa6160a085280834ac12b96898d9bae17264 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 23 Jun 2023 15:10:47 +0100 Subject: [PATCH 010/244] Hound --- openpype/hosts/maya/plugins/publish/collect_pointcache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 5946d62f9e..06be1be825 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -52,7 +52,9 @@ class CollectPointcache(pyblish.api.InstancePlugin): for key, value in backwards_mapping.items(): if key in instance.data: self.log.debug( - "Using legacy attribute name '{}' since it exists.".format(key) + "Using legacy attribute name '{}' since it exists.".format( + key + ) ) instance.data[value] = instance.data.pop(key) From b63f5c5245c6956b453794b7cb9771fd9d1675d2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 28 Jun 2023 09:57:02 +0100 Subject: [PATCH 011/244] Ensure list arguments are valid --- openpype/hosts/maya/api/lib.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index a34318f4e9..6a218e5cfe 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1213,6 +1213,12 @@ def extract_alembic(file, # Alembic Exporter requires forward slashes file = file.replace('\\', '/') + # Ensure list arguments are valid. + args = [attr, attrPrefix, root] + for arg in args: + if arg is None: + arg = [] + # Pass the start and end frame on as `frameRange` so that it # never conflicts with that argument if not frameRange: From e722fbe1c7ab5a2bf2c176aef679b57a6de0bd6a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 28 Jun 2023 11:05:23 +0100 Subject: [PATCH 012/244] BigRoy feedback --- openpype/hosts/maya/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6a218e5cfe..68f5d01128 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1214,10 +1214,10 @@ def extract_alembic(file, file = file.replace('\\', '/') # Ensure list arguments are valid. - args = [attr, attrPrefix, root] - for arg in args: - if arg is None: - arg = [] + local_vars = locals() + for key in ["attr", "attrPrefix", "root"]: + if local_vars[key] is None: + local_vars[key] = [] # Pass the start and end frame on as `frameRange` so that it # never conflicts with that argument From 38be2be6298e8badfceec67e206dd620c82c31b1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 3 Jul 2023 10:13:32 +0100 Subject: [PATCH 013/244] Fix list arguments. --- openpype/hosts/maya/api/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 68f5d01128..53bbee784a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1214,10 +1214,9 @@ def extract_alembic(file, file = file.replace('\\', '/') # Ensure list arguments are valid. - local_vars = locals() - for key in ["attr", "attrPrefix", "root"]: - if local_vars[key] is None: - local_vars[key] = [] + attr = attr or [] + attrPrefix = attrPrefix or [] + root = root or [] # Pass the start and end frame on as `frameRange` so that it # never conflicts with that argument From 5aa5215b824139ea20b1bc21ba9d20911531f99c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 4 Jul 2023 10:09:14 +0100 Subject: [PATCH 014/244] Working animation family --- .../maya/plugins/create/create_animation.py | 80 ++- .../plugins/publish/extract_pointcache.py | 25 +- .../defaults/project_settings/maya.json | 122 +++- .../schemas/schema_maya_create.json | 548 ++++++++++++++---- 4 files changed, 580 insertions(+), 195 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 92f2556680..81bcce690c 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -2,6 +2,8 @@ from openpype.hosts.maya.api import ( lib, plugin ) +from openpype.settings import get_project_settings +from openpype.pipeline import legacy_io class CreateAnimation(plugin.Creator): @@ -17,31 +19,7 @@ class CreateAnimation(plugin.Creator): label = "Animation" family = "animation" icon = "male" - includeUserDefinedAttributes = False - eulerFilter = True - noNormals = False - preRoll = False - renderableOnly = False - uvWrite = True - writeColorSets = False - writeFaceSets = False - wholeFrameGeo = False - worldSpace = True - writeVisibility = True - writeUVSets = True - writeCreases = False - dataFormat = "ogawa" - step = 1.0 - attr = "" - attrPrefix = "" - stripNamespaces = True - verbose = False - preRollStartFrame = 0 - farm = False - priority = 50 - includeParentHierarchy = False # Include parent groups - refresh = False # Default to suspend refresh. - visibleOnly = False # only nodes that are visible + writeNormals = True # Multiverse specific attribute. def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -51,31 +29,43 @@ class CreateAnimation(plugin.Creator): self.data[key] = value attrs = [ + "step", + "writeColorSets", + "writeFaceSets", + "renderableOnly", + "visibleOnly", + "includeParentHierarchy", + "worldSpace", + "farm", + "priority", + "writeNormals", "includeUserDefinedAttributes", + "attr", + "attrPrefix", + "dataFormat", "eulerFilter", "noNormals", "preRoll", - "renderableOnly", - "uvWrite", - "writeColorSets", - "writeFaceSets", - "wholeFrameGeo", - "worldSpace", - "writeVisibility", - "writeUVSets", - "writeCreases", - "dataFormat", - "step", - "attr", - "attrPrefix", - "stripNamespaces", - "verbose", "preRollStartFrame", - "farm", - "priority", - "includeParentHierarchy", "refresh", - "visibleOnly" + "stripNamespaces", + "uvWrite", + "verbose", + "wholeFrameGeo", + "writeCreases", + "writeUVSets", + "writeVisibility" ] for attr in attrs: - self.data[attr] = getattr(self, attr) + value = getattr(self, attr) + if isinstance(value, dict): + if not value["enabled"]: + self.log.debug( + "Skipping \"{}\" because its disabled in " + "settings".format(attr) + ) + continue + value = value["value"] + + # Setting value from settings. + self.data[attr] = value diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index afbbc32290..1edff7d68a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -54,27 +54,28 @@ class ExtractAlembic(publish.Extractor): options = {"selection": True} option_keys = [ + "dataFormat", "eulerFilter", "noNormals", "preRoll", + "preRollStartFrame", "renderableOnly", - "uvWrite", - "writeColorSets", - "writeFaceSets", - "wholeFrameGeo", - "worldSpace", - "writeVisibility", - "writeUVSets", - "writeCreases", - "dataFormat", "step", "stripNamespaces", + "uvWrite", "verbose", - "preRollStartFrame" + "wholeFrameGeo", + "worldSpace", + "writeColorSets", + "writeCreases", + "writeFaceSets", + "writeUVSets", + "writeVisibility", ] for key in option_keys: - options[key] = instance.data[key] - self.log.info(options) + if key in instance.data: + options[key] = instance.data[key] + if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 7072e2d1a4..7c7c7a100a 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -556,34 +556,106 @@ }, "CreateAnimation": { "enabled": false, - "includeUserDefinedAttributes": false, - "eulerFilter": true, - "noNormals": false, - "preRoll": false, - "renderableOnly": false, - "uvWrite": true, - "WriteColorSets": false, - "writeFaceSets": false, - "wholeFrameGeo": false, - "worldSpace": true, - "writeVisibility": true, - "writeUVSets": true, - "writeCreases": false, - "dataFormat": "ogawa", - "step": 1.0, - "attr": "", - "attrPrefix": "", - "stripNamespaces": true, - "verbose": false, - "preRollStartFrame": 0, - "farm": false, - "priority": 50, - "includeParentHierarchy": false, - "refresh": false, - "visibleOnly": false, "defaults": [ "Main" ], + "step": 1.0, + "writeColorSets": { + "enabled": true, + "value": false + }, + "writeFaceSets": { + "enabled": true, + "value": false + }, + "renderableOnly": { + "enabled": true, + "value": false + }, + "visibleOnly": { + "enabled": true, + "value": false + }, + "includeParentHierarchy": { + "enabled": true, + "value": false + }, + "worldSpace": { + "enabled": true, + "value": true + }, + "farm": { + "enabled": true, + "value": false + }, + "priority": { + "enabled": true, + "value": 50 + }, + "includeUserDefinedAttributes": { + "enabled": true, + "value": true + }, + "attr": { + "enabled": true, + "value": "" + }, + "attrPrefix": { + "enabled": true, + "value": "" + }, + "dataFormat": { + "enabled": false, + "value": "ogawa" + }, + "eulerFilter": { + "enabled": false, + "value": true + }, + "noNormals": { + "enabled": false, + "value": true + }, + "preRoll": { + "enabled": false, + "value": true + }, + "preRollStartFrame": { + "enabled": false, + "value": 0 + }, + "refresh": { + "enabled": false, + "value": false + }, + "stripNamespaces": { + "enabled": false, + "value": true + }, + "uvWrite": { + "enabled": false, + "value": true + }, + "verbose": { + "enabled": false, + "value": true + }, + "wholeFrameGeo": { + "enabled": false, + "value": true + }, + "writeCreases": { + "enabled": false, + "value": true + }, + "writeUVSets": { + "enabled": false, + "value": true + }, + "writeVisibility": { + "enabled": false, + "value": true + }, "write_color_sets": false, "write_face_sets": false, "include_parent_hierarchy": false, 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 aeff809cf6..f4cfee9b32 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 @@ -128,74 +128,10 @@ "label": "Enabled" }, { - "type": "boolean", - "key": "includeUserDefinedAttributes", - "label": "Include User Defined Attributes" - }, - { - "type": "boolean", - "key": "eulerFilter", - "label": "Euler Filter" - }, - { - "type": "boolean", - "key": "noNormals", - "label": "No Normals" - }, - { - "type": "boolean", - "key": "preRoll", - "label": "Pre Roll" - }, - { - "type": "boolean", - "key": "renderableOnly", - "label": "Renderable Only" - }, - { - "type": "boolean", - "key": "uvWrite", - "label": "UV Write" - }, - { - "type": "boolean", - "key": "WriteColorSets", - "label": "Write Color Sets" - }, - { - "type": "boolean", - "key": "writeFaceSets", - "label": "Write Face Sets" - }, - { - "type": "boolean", - "key": "wholeFrameGeo", - "label": "Whole Frame Geo" - }, - { - "type": "boolean", - "key": "worldSpace", - "label": "World Space" - }, - { - "type": "boolean", - "key": "writeVisibility", - "label": "Write Visibility" - }, - { - "type": "boolean", - "key": "writeUVSets", - "label": "Write UV Sets" - }, - { - "type": "boolean", - "key": "writeCreases", - "label": "Write Creases" - }, - { - "type": "text", - "key": "dataFormat", - "label": "Data Format" + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" }, { "type": "number", @@ -205,60 +141,446 @@ "decimal": 4 }, { - "type": "text", - "key": "attr", - "label": "Attr" + "label": "Write Color Sets", + "checkbox_key": "enabled", + "key": "writeColorSets", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] }, { - "type": "text", - "key": "attrPrefix", - "label": "Attr Prefix" + "label": "Write Face Sets", + "checkbox_key": "enabled", + "key": "writeFaceSets", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] }, { - "type": "boolean", - "key": "stripNamespaces", - "label": "StripNamespaces" + "label": "Renderable Only", + "checkbox_key": "enabled", + "key": "renderableOnly", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] }, { - "type": "boolean", - "key": "verbose", - "label": "Verbose" - }, - { - "type": "number", - "key": "preRollStartFrame", - "label": "Pre Roll Start Frame" - }, - { - "type": "boolean", - "key": "farm", - "label": "Farm" - }, - { - "type": "number", - "key": "priority", - "label": "Priority" - }, - { - "type": "boolean", - "key": "includeParentHierarchy", - "label": "Include Parent Hierarchy" - }, - { - "type": "boolean", - "key": "refresh", - "label": "Refresh" - }, - { - "type": "boolean", + "label": "Visible Only", + "checkbox_key": "enabled", "key": "visibleOnly", - "label": "Visible Only" + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] }, { - "type": "list", - "key": "defaults", - "label": "Default Subsets", - "object_type": "text" + "label": "Include Parent Hierarchy", + "checkbox_key": "enabled", + "key": "includeParentHierarchy", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "World Space", + "checkbox_key": "enabled", + "key": "worldSpace", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Farm", + "checkbox_key": "enabled", + "key": "farm", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Priority", + "checkbox_key": "enabled", + "key": "priority", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "number", + "key": "value", + "label": "Value", + "minimum": 0 + } + ] + }, + { + "label": "Include User Defined Attributes", + "checkbox_key": "enabled", + "key": "includeUserDefinedAttributes", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Attr", + "checkbox_key": "enabled", + "key": "attr", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Attr Prefix", + "checkbox_key": "enabled", + "key": "attrPrefix", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Data Format", + "checkbox_key": "enabled", + "key": "dataFormat", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "enum", + "key": "value", + "label": "Value", + "enum_items": [ + { + "ogawa": "ogawa" + }, + { + "HDF": "HDF" + } + ] + } + ] + }, + { + "label": "Euler Filter", + "checkbox_key": "enabled", + "key": "eulerFilter", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "No Normals", + "checkbox_key": "enabled", + "key": "noNormals", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Pre Roll", + "checkbox_key": "enabled", + "key": "preRoll", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Pre Roll Start Frame", + "checkbox_key": "enabled", + "key": "preRollStartFrame", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "number", + "key": "value", + "label": "Value", + "minimum": 0 + } + ] + }, + { + "label": "Refresh", + "checkbox_key": "enabled", + "key": "refresh", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Strip Namespaces", + "checkbox_key": "enabled", + "key": "stripNamespaces", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "UV Write", + "checkbox_key": "enabled", + "key": "uvWrite", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Verbose", + "checkbox_key": "enabled", + "key": "verbose", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Whole Frame Geo", + "checkbox_key": "enabled", + "key": "wholeFrameGeo", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Write Creases", + "checkbox_key": "enabled", + "key": "writeCreases", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Write UV Sets", + "checkbox_key": "enabled", + "key": "writeUVSets", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] + }, + { + "label": "Write Visibility", + "checkbox_key": "enabled", + "key": "writeVisibility", + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "value", + "label": "Value" + } + ] }, { "type": "boolean", From 2f8dafd2a0f3a09e33994818c46fd7b4ad5aa440 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 4 Jul 2023 10:10:06 +0100 Subject: [PATCH 015/244] Hound --- openpype/hosts/maya/plugins/create/create_animation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 81bcce690c..7b01f5a1b9 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -2,8 +2,6 @@ from openpype.hosts.maya.api import ( lib, plugin ) -from openpype.settings import get_project_settings -from openpype.pipeline import legacy_io class CreateAnimation(plugin.Creator): From e447e6a894af951229374c94bff94e4c9031c20d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 6 Jul 2023 17:01:28 +0100 Subject: [PATCH 016/244] Initial working animation family --- openpype/hosts/maya/api/plugin.py | 6 +- .../maya/plugins/create/create_animation.py | 93 +-- .../publish/validate_instance_attributes.py | 7 +- .../defaults/project_settings/maya.json | 147 ++--- .../schemas/schema_maya_create.json | 613 +++++++----------- 5 files changed, 332 insertions(+), 534 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 604ff101db..761d98cf12 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -1,5 +1,4 @@ import os -import re from maya import cmds @@ -97,8 +96,13 @@ class Creator(LegacyCreator): instance = cmds.sets(nodes, name=self.name) lib.imprint(instance, self.data) + self.post_imprint(instance) + return instance + def post_imprint(self, objset): + pass + class Loader(LoaderPlugin): hosts = ["maya"] diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 7b01f5a1b9..f1b15770f1 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -1,3 +1,5 @@ +from maya import cmds + from openpype.hosts.maya.api import ( lib, plugin @@ -17,7 +19,34 @@ class CreateAnimation(plugin.Creator): label = "Animation" family = "animation" icon = "male" - writeNormals = True # Multiverse specific attribute. + settings_attrs = [ + "step", + "writeColorSets", + "writeFaceSets", + "renderableOnly", + "visibleOnly", + "includeParentHierarchy", + "worldSpace", + "farm", + "priority", + "writeNormals", + "includeUserDefinedAttributes", + "attr", + "attrPrefix", + "dataFormat", + "eulerFilter", + "noNormals", + "preRoll", + "preRollStartFrame", + "refresh", + "stripNamespaces", + "uvWrite", + "verbose", + "wholeFrameGeo", + "writeCreases", + "writeUVSets", + "writeVisibility" + ] def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -26,44 +55,26 @@ class CreateAnimation(plugin.Creator): for key, value in lib.collect_animation_data().items(): self.data[key] = value - attrs = [ - "step", - "writeColorSets", - "writeFaceSets", - "renderableOnly", - "visibleOnly", - "includeParentHierarchy", - "worldSpace", - "farm", - "priority", - "writeNormals", - "includeUserDefinedAttributes", - "attr", - "attrPrefix", - "dataFormat", - "eulerFilter", - "noNormals", - "preRoll", - "preRollStartFrame", - "refresh", - "stripNamespaces", - "uvWrite", - "verbose", - "wholeFrameGeo", - "writeCreases", - "writeUVSets", - "writeVisibility" - ] - for attr in attrs: - value = getattr(self, attr) - if isinstance(value, dict): - if not value["enabled"]: - self.log.debug( - "Skipping \"{}\" because its disabled in " - "settings".format(attr) - ) - continue - value = value["value"] + # Setting value from settings. + for attr in self.settings_attrs: + if not hasattr(self, attr): + continue - # Setting value from settings. - self.data[attr] = value + self.data[attr] = getattr(self, attr) + + def post_imprint(self, objset): + for attr in self.settings_attrs: + editable = attr + "_editable" + + if not hasattr(self, editable): + continue + + if getattr(self, editable): + continue + + self.log.debug( + "Locking \"{}\" because its disabled in settings".format(attr) + ) + cmds.setAttr( + objset + "." + attr, channelBox=False, lock=True + ) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py index f870c9f8c4..41bb6ed3ba 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py @@ -57,4 +57,9 @@ class ValidateInstanceAttributes(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - imprint(instance.data["objset"], cls.get_missing_attributes(instance)) + missing_attributes = cls.get_missing_attributes(instance) + imprint(instance.data["objset"], missing_attributes) + + plugin = cls.plugins_by_family[instance.data["family"]] + if hasattr(plugin, "post_imprint"): + plugin.post_imprint(plugin, instance.data["objset"]) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 7c7c7a100a..7a4bccee7b 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -560,102 +560,57 @@ "Main" ], "step": 1.0, - "writeColorSets": { - "enabled": true, - "value": false - }, - "writeFaceSets": { - "enabled": true, - "value": false - }, - "renderableOnly": { - "enabled": true, - "value": false - }, - "visibleOnly": { - "enabled": true, - "value": false - }, - "includeParentHierarchy": { - "enabled": true, - "value": false - }, - "worldSpace": { - "enabled": true, - "value": true - }, - "farm": { - "enabled": true, - "value": false - }, - "priority": { - "enabled": true, - "value": 50 - }, - "includeUserDefinedAttributes": { - "enabled": true, - "value": true - }, - "attr": { - "enabled": true, - "value": "" - }, - "attrPrefix": { - "enabled": true, - "value": "" - }, - "dataFormat": { - "enabled": false, - "value": "ogawa" - }, - "eulerFilter": { - "enabled": false, - "value": true - }, - "noNormals": { - "enabled": false, - "value": true - }, - "preRoll": { - "enabled": false, - "value": true - }, - "preRollStartFrame": { - "enabled": false, - "value": 0 - }, - "refresh": { - "enabled": false, - "value": false - }, - "stripNamespaces": { - "enabled": false, - "value": true - }, - "uvWrite": { - "enabled": false, - "value": true - }, - "verbose": { - "enabled": false, - "value": true - }, - "wholeFrameGeo": { - "enabled": false, - "value": true - }, - "writeCreases": { - "enabled": false, - "value": true - }, - "writeUVSets": { - "enabled": false, - "value": true - }, - "writeVisibility": { - "enabled": false, - "value": true - }, + "step_editable": true, + "writeColorSets": false, + "writeColorSets_editable": true, + "writeFaceSets": false, + "writeFaceSets_editable": true, + "renderableOnly": false, + "renderableOnly_editable": true, + "visibleOnly": false, + "visibleOnly_editable": true, + "includeParentHierarchy": false, + "includeParentHierarchy_editable": true, + "worldSpace": true, + "worldSpace_editable": true, + "farm": false, + "farm_editable": true, + "priority": 50, + "priority_editable": true, + "writeNormals": true, + "writeNormals_editable": true, + "includeUserDefinedAttributes": true, + "includeUserDefinedAttributes_editable": true, + "attr": "", + "attr_editable": true, + "attrPrefix": "", + "attrPrefix_editable": true, + "dataFormat": "ogawa", + "dataFormat_editable": false, + "eulerFilter": false, + "eulerFilter_editable": false, + "noNormals": false, + "noNormals_editable": false, + "preRoll": false, + "preRoll_editable": false, + "preRollStartFrame": 0, + "preRollStartFrame_editable": false, + "refresh": false, + "refresh_editable": false, + "stripNamespaces": false, + "stripNamespaces_editable": false, + "uvWrite": true, + "uvWrite_editable": false, + "verbose": false, + "verbose_editable": false, + "wholeFrameGeo": false, + "wholeFrameGeo_editable": false, + "writeCreases": false, + "writeCreases_editable": false, + "writeUVSets": false, + "writeUVSets_editable": false, + "writeVisibility": true, + "writeVisibility_editable": false, "write_color_sets": false, "write_face_sets": false, "include_parent_hierarchy": false, 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 f4cfee9b32..2179e56209 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 @@ -136,451 +136,274 @@ { "type": "number", "key": "step", - "label": "Step", + "label": "Step default", "minimum": 0.0, "decimal": 4 }, { - "label": "Write Color Sets", - "checkbox_key": "enabled", - "key": "writeColorSets", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "step_editable", + "label": "Step editable" }, { - "label": "Write Face Sets", - "checkbox_key": "enabled", - "key": "writeFaceSets", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeColorSets", + "label": "Write Color Sets default" }, { - "label": "Renderable Only", - "checkbox_key": "enabled", - "key": "renderableOnly", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeColorSets_editable", + "label": "Write Color Sets editable" }, { - "label": "Visible Only", - "checkbox_key": "enabled", - "key": "visibleOnly", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeFaceSets", + "label": "Write Face Sets default" }, { - "label": "Include Parent Hierarchy", - "checkbox_key": "enabled", - "key": "includeParentHierarchy", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeFaceSets_editable", + "label": "Write Face Sets editable" }, { - "label": "World Space", - "checkbox_key": "enabled", - "key": "worldSpace", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "renderableOnly", + "label": "Renderable Only default" }, { - "label": "Farm", - "checkbox_key": "enabled", - "key": "farm", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "renderableOnly_editable", + "label": "Renderable Only editable" }, { - "label": "Priority", - "checkbox_key": "enabled", - "key": "priority", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "number", - "key": "value", - "label": "Value", - "minimum": 0 - } - ] + "type": "boolean", + "key": "visibleOnly", + "label": "Visible Only default" }, { - "label": "Include User Defined Attributes", - "checkbox_key": "enabled", - "key": "includeUserDefinedAttributes", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "visibleOnly_editable", + "label": "Visible Only editable" }, { - "label": "Attr", - "checkbox_key": "enabled", - "key": "attr", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "includeParentHierarchy", + "label": "Include Parent Hierarchy default" }, { - "label": "Attr Prefix", - "checkbox_key": "enabled", - "key": "attrPrefix", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "includeParentHierarchy_editable", + "label": "Include Parent Hierarchy editable" }, { - "label": "Data Format", - "checkbox_key": "enabled", - "key": "dataFormat", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "enum", - "key": "value", - "label": "Value", - "enum_items": [ - { - "ogawa": "ogawa" - }, - { - "HDF": "HDF" - } - ] - } - ] + "type": "boolean", + "key": "worldSpace", + "label": "World Space default" }, { - "label": "Euler Filter", - "checkbox_key": "enabled", - "key": "eulerFilter", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "worldSpace_editable", + "label": "World Space editable" }, { - "label": "No Normals", - "checkbox_key": "enabled", - "key": "noNormals", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "farm", + "label": "Farm default" }, { - "label": "Pre Roll", - "checkbox_key": "enabled", - "key": "preRoll", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "farm_editable", + "label": "Farm editable" }, { - "label": "Pre Roll Start Frame", - "checkbox_key": "enabled", - "key": "preRollStartFrame", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "number", - "key": "value", - "label": "Value", - "minimum": 0 - } - ] + "type": "number", + "key": "priority", + "label": "Priority default", + "minimum": 0 }, { - "label": "Refresh", - "checkbox_key": "enabled", - "key": "refresh", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "priority_editable", + "label": "Priority editable" }, { - "label": "Strip Namespaces", - "checkbox_key": "enabled", - "key": "stripNamespaces", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeNormals", + "label": "Write Normals default" }, { - "label": "UV Write", - "checkbox_key": "enabled", - "key": "uvWrite", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "writeNormals_editable", + "label": "Write Normals editable" }, { - "label": "Verbose", - "checkbox_key": "enabled", - "key": "verbose", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "includeUserDefinedAttributes", + "label": "Include User Defined Attributes default" }, { - "label": "Whole Frame Geo", - "checkbox_key": "enabled", - "key": "wholeFrameGeo", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "includeUserDefinedAttributes_editable", + "label": "Include User Defined Attributes editable" }, { - "label": "Write Creases", - "checkbox_key": "enabled", - "key": "writeCreases", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "text", + "key": "attr", + "label": "Attr default" }, { - "label": "Write UV Sets", - "checkbox_key": "enabled", - "key": "writeUVSets", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "boolean", + "key": "attr_editable", + "label": "Attr editable" }, { - "label": "Write Visibility", - "checkbox_key": "enabled", - "key": "writeVisibility", - "type": "dict", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "boolean", - "key": "value", - "label": "Value" - } - ] + "type": "text", + "key": "attrPrefix", + "label": "Attr Prefix default" + }, + { + "type": "boolean", + "key": "attrPrefix_editable", + "label": "Attr Prefix editable" + }, + { + "type": "enum", + "key": "dataFormat", + "label": "Data Format default", + "enum_items": [ + { + "ogawa": "ogawa" + }, + { + "HDF": "HDF" + } + ] + }, + { + "type": "boolean", + "key": "dataFormat_editable", + "label": "Data Format editable" + }, + { + "type": "boolean", + "key": "eulerFilter", + "label": "Euler Filter default" + }, + { + "type": "boolean", + "key": "eulerFilter_editable", + "label": "Euler Filter editable" + }, + { + "type": "boolean", + "key": "noNormals", + "label": "No Normals default" + }, + { + "type": "boolean", + "key": "noNormals_editable", + "label": "No Normals editable" + }, + { + "type": "boolean", + "key": "preRoll", + "label": "Pre Roll default" + }, + { + "type": "boolean", + "key": "preRoll_editable", + "label": "Pre Roll editable" + }, + { + "type": "number", + "key": "preRollStartFrame", + "label": "Pre Roll Start Frame default", + "minimum": 0 + }, + { + "type": "boolean", + "key": "preRollStartFrame_editable", + "label": "Pre Roll Start Frame editable" + }, + { + "type": "boolean", + "key": "refresh", + "label": "Refresh default" + }, + { + "type": "boolean", + "key": "refresh_editable", + "label": "Refresh editable" + }, + { + "type": "boolean", + "key": "stripNamespaces", + "label": "Strip Namespaces default" + }, + { + "type": "boolean", + "key": "stripNamespaces_editable", + "label": "Strip Namespaces editable" + }, + { + "type": "boolean", + "key": "uvWrite", + "label": "UV Write default" + }, + { + "type": "boolean", + "key": "uvWrite_editable", + "label": "UV Write editable" + }, + { + "type": "boolean", + "key": "verbose", + "label": "Verbose default" + }, + { + "type": "boolean", + "key": "verbose_editable", + "label": "Verbose editable" + }, + { + "type": "boolean", + "key": "wholeFrameGeo", + "label": "Whole Frame Geo default" + }, + { + "type": "boolean", + "key": "wholeFrameGeo_editable", + "label": "Whole Frame Geo editable" + }, + { + "type": "boolean", + "key": "writeCreases", + "label": "Write Creases default" + }, + { + "type": "boolean", + "key": "writeCreases_editable", + "label": "Write Creases editable" + }, + { + "type": "boolean", + "key": "writeUVSets", + "label": "Write UV Sets default" + }, + { + "type": "boolean", + "key": "writeUVSets_editable", + "label": "Write UV Sets editable" + }, + { + "type": "boolean", + "key": "writeVisibility", + "label": "Write Visibility default" + }, + { + "type": "boolean", + "key": "writeVisibility_editable", + "label": "Write Visibility editable" }, { "type": "boolean", From 9ac4017d015d18bbf3aee48687e7c35b3551af7f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 14 Jul 2023 15:10:00 +0100 Subject: [PATCH 017/244] Working pointcache --- .../maya/plugins/create/create_pointcache.py | 97 +++-- .../maya/plugins/publish/collect_animation.py | 4 + .../plugins/publish/collect_pointcache.py | 29 +- .../defaults/project_settings/maya.json | 77 ++-- .../schemas/schema_maya_create.json | 399 ++++++++++++------ 5 files changed, 419 insertions(+), 187 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index f4e8cbfc9a..8cd96cc689 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -6,8 +6,11 @@ from openpype.hosts.maya.api import ( ) from openpype.lib import ( BoolDef, - TextDef + TextDef, + NumberDef, + EnumDef ) +from openpype.settings import get_current_project_settings#not needed if applying settings is merged. class CreatePointCache(plugin.MayaCreator): @@ -22,59 +25,95 @@ class CreatePointCache(plugin.MayaCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): - defs = lib.collect_animation_defs() defs.extend([ BoolDef("writeColorSets", label="Write vertex colors", - tooltip="Write vertex colors with the geometry", - default=False), + tooltip="Write vertex colors with the geometry"), BoolDef("writeFaceSets", label="Write face sets", - tooltip="Write face sets with the geometry", - default=False), + tooltip="Write face sets with the geometry"), BoolDef("renderableOnly", label="Renderable Only", - tooltip="Only export renderable visible shapes", - default=False), + tooltip="Only export renderable visible shapes"), BoolDef("visibleOnly", label="Visible Only", tooltip="Only export dag objects visible during " - "frame range", - default=False), + "frame range"), BoolDef("includeParentHierarchy", label="Include Parent Hierarchy", tooltip="Whether to include parent hierarchy of nodes in " - "the publish instance", - default=False), + "the publish instance"), BoolDef("worldSpace", - label="World-Space Export", - default=True), - BoolDef("refresh", - label="Refresh viewport during export", - default=False), + label="World-Space Export"), + BoolDef("farm", + label="Submit to farm"), + NumberDef("priority", + label="Priority for farm"), + BoolDef("noNormals", + label="Include normals"), BoolDef("includeUserDefinedAttributes", - label="Include User Defined Attributes", - default=self.include_user_defined_attributes), + label="Include User Defined Attributes"), TextDef("attr", label="Custom Attributes", - default="", placeholder="attr1, attr2"), TextDef("attrPrefix", label="Custom Attributes Prefix", - default="", - placeholder="prefix1, prefix2") + placeholder="prefix1, prefix2"), + EnumDef("dataFormat", + label="Data Format", + items=["ogawa", "HDF"]), + BoolDef("eulerFilter", + label="Apply Euler Filter"), + BoolDef("preRoll", + label="Start from preroll start frame"), + NumberDef("preRollStartFrame", + label="Start frame for preroll"), + BoolDef("refresh", + label="Refresh viewport during export"), + BoolDef("stripNamespaces", + label="Strip namespaces on export"), + BoolDef("uvWrite", + label="Write UVs"), + BoolDef("verbose", + label="Verbose output"), + BoolDef("wholeFrameGeo", + label="Whole Frame Geo"), + BoolDef("writeCreases", + label="Write Creases"), + BoolDef("writeUVSets", + label="Write UV Sets"), + BoolDef("writeVisibility", + label="Write Visibility") ]) - # TODO: Implement these on a Deadline plug-in instead? - """ - # Default to not send to farm. - self.data["farm"] = False - self.data["priority"] = 50 - """ + # Collect editable state and default values. + settings = get_current_project_settings() + settings = settings["maya"]["create"]["CreatePointCache"] + editable_attributes = {} + for key, value in settings.items(): + if not key.endswith("_editable"): + continue - return defs + if not value: + continue + + attribute = key.replace("_editable", "") + editable_attributes[attribute] = settings[attribute] + + resulting_defs = [] + for definition in defs: + # Remove non-editable defs. + if definition.key not in editable_attributes.keys(): + continue + + # Set default values from settings. + definition.default = editable_attributes[definition.key] + + resulting_defs.append(definition) + + return resulting_defs def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 5cfdb358ce..94b732cc4d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -58,6 +58,10 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + # Alembic and Multiverse share the same attribute functionality but + # different names. + instance.data["writeNormals"] = not instance.data["noNormals"] + # Backwards compatibility for attributes. backwards_mapping = { "write_color_sets": "writeColorSets", diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 99e2e7ef8e..87eecbcf3e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -10,6 +10,11 @@ class CollectPointcache(pyblish.api.InstancePlugin): families = ["pointcache"] label = "Collect Pointcache" hosts = ["maya"] + legacy_settings = { + "write_color_sets": "writeColorSets", + "write_face_sets": "writeFaceSets", + "include_user_defined_attributes": "includeUserDefinedAttributes" + } def process(self, instance): if instance.data.get("farm"): @@ -46,13 +51,25 @@ class CollectPointcache(pyblish.api.InstancePlugin): instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) + # Apply default values not exposed to the user. + settings = instance.context.data["project_settings"]["maya"]["create"] + for key, value in settings["CreatePointCache"].items(): + if key.endswith("_editable"): + continue + + if key in instance.data: + continue + + if key in self.legacy_settings.keys(): + continue + + self.log.debug( + "Adding \"{}:{}\" from settings.".format(key, value) + ) + instance.data[key] = value + # Backwards compatibility for attributes. - backwards_mapping = { - "write_color_sets": "writeColorSets", - "write_face_sets": "writeFaceSets", - "include_user_defined_attributes": "includeUserDefinedAttributes" - } - for key, value in backwards_mapping.items(): + for key, value in self.legacy_settings.items(): if key in instance.data: self.log.debug( "Using legacy attribute name '{}' since it exists.".format( diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 7c479eba36..6cb11fb401 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -628,34 +628,61 @@ }, "CreatePointCache": { "enabled": true, - "includeUserDefinedAttributes": false, - "eulerFilter": true, - "noNormals": false, - "preRoll": false, - "renderableOnly": false, - "uvWrite": true, - "WriteColorSets": false, - "writeFaceSets": false, - "wholeFrameGeo": false, - "worldSpace": true, - "writeVisibility": true, - "writeUVSets": true, - "writeCreases": false, - "dataFormat": "ogawa", - "step": 1.0, - "attr": "", - "attrPrefix": "", - "stripNamespaces": true, - "verbose": false, - "preRollStartFrame": 0, - "farm": false, - "priority": 50, - "includeParentHierarchy": false, - "refresh": false, - "visibleOnly": false, "defaults": [ "Main" ], + "step": 1.0, + "step_editable": true, + "writeColorSets": true, + "writeColorSets_editable": true, + "writeFaceSets": false, + "writeFaceSets_editable": true, + "renderableOnly": false, + "renderableOnly_editable": true, + "visibleOnly": false, + "visibleOnly_editable": true, + "includeParentHierarchy": false, + "includeParentHierarchy_editable": true, + "worldSpace": true, + "worldSpace_editable": true, + "farm": false, + "farm_editable": true, + "priority": 50, + "priority_editable": true, + "writeNormals": true, + "writeNormals_editable": true, + "includeUserDefinedAttributes": true, + "includeUserDefinedAttributes_editable": true, + "attr": "", + "attr_editable": true, + "attrPrefix": "", + "attrPrefix_editable": true, + "dataFormat": "ogawa", + "dataFormat_editable": false, + "eulerFilter": false, + "eulerFilter_editable": false, + "noNormals": false, + "noNormals_editable": false, + "preRoll": false, + "preRoll_editable": false, + "preRollStartFrame": 0, + "preRollStartFrame_editable": false, + "refresh": false, + "refresh_editable": false, + "stripNamespaces": false, + "stripNamespaces_editable": false, + "uvWrite": true, + "uvWrite_editable": false, + "verbose": false, + "verbose_editable": false, + "wholeFrameGeo": false, + "wholeFrameGeo_editable": false, + "writeCreases": false, + "writeCreases_editable": false, + "writeUVSets": false, + "writeUVSets_editable": false, + "writeVisibility": true, + "writeVisibility_editable": false, "write_color_sets": false, "write_face_sets": false, "include_user_defined_attributes": false 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 81f2301047..8adcdcd1d7 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 @@ -469,139 +469,284 @@ "key": "enabled", "label": "Enabled" }, - { - "type": "boolean", - "key": "includeUserDefinedAttributes", - "label": "Include User Defined Attributes" - }, - { - "type": "boolean", - "key": "eulerFilter", - "label": "Euler Filter" - }, - { - "type": "boolean", - "key": "noNormals", - "label": "No Normals" - }, - { - "type": "boolean", - "key": "preRoll", - "label": "Pre Roll" - }, - { - "type": "boolean", - "key": "renderableOnly", - "label": "Renderable Only" - }, - { - "type": "boolean", - "key": "uvWrite", - "label": "UV Write" - }, - { - "type": "boolean", - "key": "WriteColorSets", - "label": "Write Color Sets" - }, - { - "type": "boolean", - "key": "writeFaceSets", - "label": "Write Face Sets" - }, - { - "type": "boolean", - "key": "wholeFrameGeo", - "label": "Whole Frame Geo" - }, - { - "type": "boolean", - "key": "worldSpace", - "label": "World Space" - }, - { - "type": "boolean", - "key": "writeVisibility", - "label": "Write Visibility" - }, - { - "type": "boolean", - "key": "writeUVSets", - "label": "Write UV Sets" - }, - { - "type": "boolean", - "key": "writeCreases", - "label": "Write Creases" - }, - { - "type": "text", - "key": "dataFormat", - "label": "Data Format" - }, - { - "type": "number", - "key": "step", - "label": "Step", - "minimum": 0.0, - "decimal": 4 - }, - { - "type": "text", - "key": "attr", - "label": "Attr" - }, - { - "type": "text", - "key": "attrPrefix", - "label": "Attr Prefix" - }, - { - "type": "boolean", - "key": "stripNamespaces", - "label": "StripNamespaces" - }, - { - "type": "boolean", - "key": "verbose", - "label": "Verbose" - }, - { - "type": "number", - "key": "preRollStartFrame", - "label": "Pre Roll Start Frame" - }, - { - "type": "boolean", - "key": "farm", - "label": "Farm" - }, - { - "type": "number", - "key": "priority", - "label": "Priority" - }, - { - "type": "boolean", - "key": "includeParentHierarchy", - "label": "Include Parent Hierarchy" - }, - { - "type": "boolean", - "key": "refresh", - "label": "Refresh" - }, - { - "type": "boolean", - "key": "visibleOnly", - "label": "Visible Only" - }, { "type": "list", "key": "defaults", "label": "Default Subsets", "object_type": "text" }, + { + "type": "number", + "key": "step", + "label": "Step default", + "minimum": 0.0, + "decimal": 4 + }, + { + "type": "boolean", + "key": "step_editable", + "label": "Step editable" + }, + { + "type": "boolean", + "key": "writeColorSets", + "label": "Write Color Sets default" + }, + { + "type": "boolean", + "key": "writeColorSets_editable", + "label": "Write Color Sets editable" + }, + { + "type": "boolean", + "key": "writeFaceSets", + "label": "Write Face Sets default" + }, + { + "type": "boolean", + "key": "writeFaceSets_editable", + "label": "Write Face Sets editable" + }, + { + "type": "boolean", + "key": "renderableOnly", + "label": "Renderable Only default" + }, + { + "type": "boolean", + "key": "renderableOnly_editable", + "label": "Renderable Only editable" + }, + { + "type": "boolean", + "key": "visibleOnly", + "label": "Visible Only default" + }, + { + "type": "boolean", + "key": "visibleOnly_editable", + "label": "Visible Only editable" + }, + { + "type": "boolean", + "key": "includeParentHierarchy", + "label": "Include Parent Hierarchy default" + }, + { + "type": "boolean", + "key": "includeParentHierarchy_editable", + "label": "Include Parent Hierarchy editable" + }, + { + "type": "boolean", + "key": "worldSpace", + "label": "World Space default" + }, + { + "type": "boolean", + "key": "worldSpace_editable", + "label": "World Space editable" + }, + { + "type": "boolean", + "key": "farm", + "label": "Farm default" + }, + { + "type": "boolean", + "key": "farm_editable", + "label": "Farm editable" + }, + { + "type": "number", + "key": "priority", + "label": "Priority default", + "minimum": 0 + }, + { + "type": "boolean", + "key": "priority_editable", + "label": "Priority editable" + }, + { + "type": "boolean", + "key": "writeNormals", + "label": "Write Normals default" + }, + { + "type": "boolean", + "key": "writeNormals_editable", + "label": "Write Normals editable" + }, + { + "type": "boolean", + "key": "includeUserDefinedAttributes", + "label": "Include User Defined Attributes default" + }, + { + "type": "boolean", + "key": "includeUserDefinedAttributes_editable", + "label": "Include User Defined Attributes editable" + }, + { + "type": "text", + "key": "attr", + "label": "Attr default" + }, + { + "type": "boolean", + "key": "attr_editable", + "label": "Attr editable" + }, + { + "type": "text", + "key": "attrPrefix", + "label": "Attr Prefix default" + }, + { + "type": "boolean", + "key": "attrPrefix_editable", + "label": "Attr Prefix editable" + }, + { + "type": "enum", + "key": "dataFormat", + "label": "Data Format default", + "enum_items": [ + { + "ogawa": "ogawa" + }, + { + "HDF": "HDF" + } + ] + }, + { + "type": "boolean", + "key": "dataFormat_editable", + "label": "Data Format editable" + }, + { + "type": "boolean", + "key": "eulerFilter", + "label": "Euler Filter default" + }, + { + "type": "boolean", + "key": "eulerFilter_editable", + "label": "Euler Filter editable" + }, + { + "type": "boolean", + "key": "noNormals", + "label": "No Normals default" + }, + { + "type": "boolean", + "key": "noNormals_editable", + "label": "No Normals editable" + }, + { + "type": "boolean", + "key": "preRoll", + "label": "Pre Roll default" + }, + { + "type": "boolean", + "key": "preRoll_editable", + "label": "Pre Roll editable" + }, + { + "type": "number", + "key": "preRollStartFrame", + "label": "Pre Roll Start Frame default", + "minimum": 0 + }, + { + "type": "boolean", + "key": "preRollStartFrame_editable", + "label": "Pre Roll Start Frame editable" + }, + { + "type": "boolean", + "key": "refresh", + "label": "Refresh default" + }, + { + "type": "boolean", + "key": "refresh_editable", + "label": "Refresh editable" + }, + { + "type": "boolean", + "key": "stripNamespaces", + "label": "Strip Namespaces default" + }, + { + "type": "boolean", + "key": "stripNamespaces_editable", + "label": "Strip Namespaces editable" + }, + { + "type": "boolean", + "key": "uvWrite", + "label": "UV Write default" + }, + { + "type": "boolean", + "key": "uvWrite_editable", + "label": "UV Write editable" + }, + { + "type": "boolean", + "key": "verbose", + "label": "Verbose default" + }, + { + "type": "boolean", + "key": "verbose_editable", + "label": "Verbose editable" + }, + { + "type": "boolean", + "key": "wholeFrameGeo", + "label": "Whole Frame Geo default" + }, + { + "type": "boolean", + "key": "wholeFrameGeo_editable", + "label": "Whole Frame Geo editable" + }, + { + "type": "boolean", + "key": "writeCreases", + "label": "Write Creases default" + }, + { + "type": "boolean", + "key": "writeCreases_editable", + "label": "Write Creases editable" + }, + { + "type": "boolean", + "key": "writeUVSets", + "label": "Write UV Sets default" + }, + { + "type": "boolean", + "key": "writeUVSets_editable", + "label": "Write UV Sets editable" + }, + { + "type": "boolean", + "key": "writeVisibility", + "label": "Write Visibility default" + }, + { + "type": "boolean", + "key": "writeVisibility_editable", + "label": "Write Visibility editable" + }, { "type": "boolean", "key": "write_color_sets", From c1e771bedc3f3bd9f176042c924c0ccc10b6b103 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 14 Jul 2023 15:14:34 +0100 Subject: [PATCH 018/244] Revert post_imprint --- .../maya/plugins/publish/validate_instance_attributes.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py index 41bb6ed3ba..f870c9f8c4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py @@ -57,9 +57,4 @@ class ValidateInstanceAttributes(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - missing_attributes = cls.get_missing_attributes(instance) - imprint(instance.data["objset"], missing_attributes) - - plugin = cls.plugins_by_family[instance.data["family"]] - if hasattr(plugin, "post_imprint"): - plugin.post_imprint(plugin, instance.data["objset"]) + imprint(instance.data["objset"], cls.get_missing_attributes(instance)) From 98870357c7769491389f2ad6727e68d30ec3a075 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 14 Jul 2023 15:40:52 +0100 Subject: [PATCH 019/244] Combine CreateAnimation and CreatePointCache --- .../maya/plugins/create/create_animation.py | 87 ------------------- ...ache.py => create_animation_pointcache.py} | 39 +++++++-- 2 files changed, 32 insertions(+), 94 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/create/create_animation.py rename openpype/hosts/maya/plugins/create/{create_pointcache.py => create_animation_pointcache.py} (82%) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py deleted file mode 100644 index cade8603ce..0000000000 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ /dev/null @@ -1,87 +0,0 @@ -from openpype.hosts.maya.api import ( - lib, - plugin -) -from openpype.lib import ( - BoolDef, - TextDef -) - - -class CreateAnimation(plugin.MayaCreator): - """Animation output for character rigs""" - - # We hide the animation creator from the UI since the creation of it - # is automated upon loading a rig. There's an inventory action to recreate - # it for loaded rigs if by chance someone deleted the animation instance. - # Note: This setting is actually applied from project settings - enabled = False - - identifier = "io.openpype.creators.maya.animation" - name = "animationDefault" - label = "Animation" - family = "animation" - icon = "male" - - write_color_sets = False - write_face_sets = False - include_parent_hierarchy = False - include_user_defined_attributes = False - - # TODO: Would be great if we could visually hide this from the creator - # by default but do allow to generate it through code. - - def get_instance_attr_defs(self): - - defs = lib.collect_animation_defs() - - defs.extend([ - BoolDef("writeColorSets", - label="Write vertex colors", - tooltip="Write vertex colors with the geometry", - default=self.write_color_sets), - BoolDef("writeFaceSets", - label="Write face sets", - tooltip="Write face sets with the geometry", - default=self.write_face_sets), - BoolDef("writeNormals", - label="Write normals", - tooltip="Write normals with the deforming geometry", - default=True), - BoolDef("renderableOnly", - label="Renderable Only", - tooltip="Only export renderable visible shapes", - default=False), - BoolDef("visibleOnly", - label="Visible Only", - tooltip="Only export dag objects visible during " - "frame range", - default=False), - BoolDef("includeParentHierarchy", - label="Include Parent Hierarchy", - tooltip="Whether to include parent hierarchy of nodes in " - "the publish instance", - default=self.include_parent_hierarchy), - BoolDef("worldSpace", - label="World-Space Export", - default=True), - BoolDef("includeUserDefinedAttributes", - label="Include User Defined Attributes", - default=self.include_user_defined_attributes), - TextDef("attr", - label="Custom Attributes", - default="", - placeholder="attr1, attr2"), - TextDef("attrPrefix", - label="Custom Attributes Prefix", - placeholder="prefix1, prefix2") - ]) - - # TODO: Implement these on a Deadline plug-in instead? - """ - # Default to not send to farm. - self.data["farm"] = False - self.data["priority"] = 50 - """ - - return defs diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py similarity index 82% rename from openpype/hosts/maya/plugins/create/create_pointcache.py rename to openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 8cd96cc689..d0d7cc9675 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -13,17 +13,29 @@ from openpype.lib import ( from openpype.settings import get_current_project_settings#not needed if applying settings is merged. -class CreatePointCache(plugin.MayaCreator): - """Alembic pointcache for animated data""" +class CreateAnimation(plugin.MayaCreator): + """Animation output for character rigs""" + + # We hide the animation creator from the UI since the creation of it + # is automated upon loading a rig. There's an inventory action to recreate + # it for loaded rigs if by chance someone deleted the animation instance. + # Note: This setting is actually applied from project settings + enabled = False + + identifier = "io.openpype.creators.maya.animation" + name = "animationDefault" + label = "Animation" + family = "animation" + icon = "male" - identifier = "io.openpype.creators.maya.pointcache" - label = "Pointcache" - family = "pointcache" - icon = "gears" write_color_sets = False write_face_sets = False + include_parent_hierarchy = False include_user_defined_attributes = False + # TODO: Would be great if we could visually hide this from the creator + # by default but do allow to generate it through code. + def get_instance_attr_defs(self): defs = lib.collect_animation_defs() @@ -90,7 +102,7 @@ class CreatePointCache(plugin.MayaCreator): # Collect editable state and default values. settings = get_current_project_settings() - settings = settings["maya"]["create"]["CreatePointCache"] + settings = settings["maya"]["create"][self.__class__.__name__] editable_attributes = {} for key, value in settings.items(): if not key.endswith("_editable"): @@ -115,6 +127,19 @@ class CreatePointCache(plugin.MayaCreator): return resulting_defs + +class CreatePointCache(CreateAnimation): + """Alembic pointcache for animated data""" + + enabled = True + identifier = "io.openpype.creators.maya.pointcache" + label = "Pointcache" + family = "pointcache" + icon = "gears" + write_color_sets = False + write_face_sets = False + include_user_defined_attributes = False + def create(self, subset_name, instance_data, pre_create_data): instance = super(CreatePointCache, self).create( From eafe2d05a7e2c28367d458f83ba137de514c8813 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 22 Aug 2023 16:10:01 +0100 Subject: [PATCH 020/244] Working merge of develop --- .../maya/plugins/create/create_animation.py | 91 ------------------- .../create/create_animation_pointcache.py | 39 +++----- .../defaults/project_settings/maya.json | 8 +- 3 files changed, 14 insertions(+), 124 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/create/create_animation.py diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py deleted file mode 100644 index 214ac18aef..0000000000 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ /dev/null @@ -1,91 +0,0 @@ -from openpype.hosts.maya.api import ( - lib, - plugin -) -from openpype.lib import ( - BoolDef, - TextDef -) - - -class CreateAnimation(plugin.MayaHiddenCreator): - """Animation output for character rigs - - We hide the animation creator from the UI since the creation of it is - automated upon loading a rig. There's an inventory action to recreate it - for loaded rigs if by chance someone deleted the animation instance. - """ - identifier = "io.openpype.creators.maya.animation" - name = "animationDefault" - label = "Animation" - family = "animation" - icon = "male" - - write_color_sets = False - write_face_sets = False - include_parent_hierarchy = False - include_user_defined_attributes = False - - def get_instance_attr_defs(self): - - defs = lib.collect_animation_defs() - - defs.extend([ - BoolDef("writeColorSets", - label="Write vertex colors", - tooltip="Write vertex colors with the geometry", - default=self.write_color_sets), - BoolDef("writeFaceSets", - label="Write face sets", - tooltip="Write face sets with the geometry", - default=self.write_face_sets), - BoolDef("writeNormals", - label="Write normals", - tooltip="Write normals with the deforming geometry", - default=True), - BoolDef("renderableOnly", - label="Renderable Only", - tooltip="Only export renderable visible shapes", - default=False), - BoolDef("visibleOnly", - label="Visible Only", - tooltip="Only export dag objects visible during " - "frame range", - default=False), - BoolDef("includeParentHierarchy", - label="Include Parent Hierarchy", - tooltip="Whether to include parent hierarchy of nodes in " - "the publish instance", - default=self.include_parent_hierarchy), - BoolDef("worldSpace", - label="World-Space Export", - default=True), - BoolDef("includeUserDefinedAttributes", - label="Include User Defined Attributes", - default=self.include_user_defined_attributes), - TextDef("attr", - label="Custom Attributes", - default="", - placeholder="attr1, attr2"), - TextDef("attrPrefix", - label="Custom Attributes Prefix", - placeholder="prefix1, prefix2") - ]) - - # TODO: Implement these on a Deadline plug-in instead? - """ - # Default to not send to farm. - self.data["farm"] = False - self.data["priority"] = 50 - """ - - return defs - - def apply_settings(self, project_settings, system_settings): - super(CreateAnimation, self).apply_settings( - project_settings, system_settings - ) - # Hardcoding creator to be enabled due to existing settings would - # disable the creator causing the creator plugin to not be - # discoverable. - self.enabled = True diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index d0d7cc9675..83f21eb80f 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -10,18 +10,15 @@ from openpype.lib import ( NumberDef, EnumDef ) -from openpype.settings import get_current_project_settings#not needed if applying settings is merged. class CreateAnimation(plugin.MayaCreator): - """Animation output for character rigs""" - - # We hide the animation creator from the UI since the creation of it - # is automated upon loading a rig. There's an inventory action to recreate - # it for loaded rigs if by chance someone deleted the animation instance. - # Note: This setting is actually applied from project settings - enabled = False + """Animation output for character rigs + We hide the animation creator from the UI since the creation of it is + automated upon loading a rig. There's an inventory action to recreate it + for loaded rigs if by chance someone deleted the animation instance. + """ identifier = "io.openpype.creators.maya.animation" name = "animationDefault" label = "Animation" @@ -33,9 +30,6 @@ class CreateAnimation(plugin.MayaCreator): include_parent_hierarchy = False include_user_defined_attributes = False - # TODO: Would be great if we could visually hide this from the creator - # by default but do allow to generate it through code. - def get_instance_attr_defs(self): defs = lib.collect_animation_defs() @@ -101,27 +95,20 @@ class CreateAnimation(plugin.MayaCreator): ]) # Collect editable state and default values. - settings = get_current_project_settings() - settings = settings["maya"]["create"][self.__class__.__name__] - editable_attributes = {} - for key, value in settings.items(): - if not key.endswith("_editable"): - continue - - if not value: - continue - - attribute = key.replace("_editable", "") - editable_attributes[attribute] = settings[attribute] - resulting_defs = [] for definition in defs: + # Include by default any attributes which has no editable state + # from settings. + if not hasattr(self, definition.key + "_editable"): + resulting_defs.append(definition) + continue + # Remove non-editable defs. - if definition.key not in editable_attributes.keys(): + if not getattr(self, definition.key + "_editable"): continue # Set default values from settings. - definition.default = editable_attributes[definition.key] + definition.default = getattr(self, definition.key) resulting_defs.append(definition) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 161fac0044..90b4574f15 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -557,10 +557,7 @@ "publish_mip_map": true }, "CreateAnimation": { - "enabled": false, - "defaults": [ - "Main" - ], + "default_variants": [], "step": 1.0, "step_editable": true, "writeColorSets": false, @@ -630,9 +627,6 @@ }, "CreatePointCache": { "enabled": true, - "write_color_sets": false, - "write_face_sets": false, - "include_user_defined_attributes": false, "default_variants": [ "Main" ], From 7d1f0098d4aa19b584fb113ed7236ea51a0ca8b8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 22 Aug 2023 16:54:58 +0100 Subject: [PATCH 021/244] Working hidden animation creator --- openpype/hosts/maya/api/plugin.py | 47 +++-- .../create/create_animation_pointcache.py | 177 +++++++++--------- 2 files changed, 124 insertions(+), 100 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 00d6602ef9..ecbbece220 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -65,6 +65,25 @@ def get_reference_node_parents(*args, **kwargs): return lib.get_reference_node_parents(*args, **kwargs) +def apply_settings(cls, project_settings, system_settings): + """Method called on initialization of plugin to apply settings.""" + + settings_name = cls.settings_name + if settings_name is None: + settings_name = cls.__class__.__name__ + + settings = project_settings["maya"]["create"] + settings = settings.get(settings_name) + if settings is None: + cls.log.debug( + "No settings found for {}".format(cls.__class__.__name__) + ) + return + + for key, value in settings.items(): + setattr(cls, key, value) + + class Creator(LegacyCreator): defaults = ['Main'] @@ -262,21 +281,7 @@ class MayaCreator(NewCreator, MayaCreatorBase): def apply_settings(self, project_settings, system_settings): """Method called on initialization of plugin to apply settings.""" - - settings_name = self.settings_name - if settings_name is None: - settings_name = self.__class__.__name__ - - settings = project_settings["maya"]["create"] - settings = settings.get(settings_name) - if settings is None: - self.log.debug( - "No settings found for {}".format(self.__class__.__name__) - ) - return - - for key, value in settings.items(): - setattr(self, key, value) + apply_settings(self, project_settings, system_settings) class MayaAutoCreator(AutoCreator, MayaCreatorBase): @@ -286,6 +291,8 @@ class MayaAutoCreator(AutoCreator, MayaCreatorBase): any arguments. """ + settings_name = None + def collect_instances(self): return self._default_collect_instances() @@ -295,6 +302,10 @@ class MayaAutoCreator(AutoCreator, MayaCreatorBase): def remove_instances(self, instances): return self._default_remove_instances(instances) + def apply_settings(self, project_settings, system_settings): + """Method called on initialization of plugin to apply settings.""" + apply_settings(self, project_settings, system_settings) + class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): """Hidden creator for Maya. @@ -303,6 +314,8 @@ class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): arguments for 'create' method. """ + settings_name = None + def create(self, *args, **kwargs): return MayaCreator.create(self, *args, **kwargs) @@ -315,6 +328,10 @@ class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): def remove_instances(self, instances): return self._default_remove_instances(instances) + def apply_settings(self, project_settings, system_settings): + """Method called on initialization of plugin to apply settings.""" + apply_settings(self, project_settings, system_settings) + def ensure_namespace(namespace): """Make sure the namespace exists. diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 83f21eb80f..d9fd0fff86 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -12,7 +12,93 @@ from openpype.lib import ( ) -class CreateAnimation(plugin.MayaCreator): +def get_instance_attr_defs(cls): + defs = lib.collect_animation_defs() + + defs.extend([ + BoolDef("writeColorSets", + label="Write vertex colors", + tooltip="Write vertex colors with the geometry"), + BoolDef("writeFaceSets", + label="Write face sets", + tooltip="Write face sets with the geometry"), + BoolDef("renderableOnly", + label="Renderable Only", + tooltip="Only export renderable visible shapes"), + BoolDef("visibleOnly", + label="Visible Only", + tooltip="Only export dag objects visible during " + "frame range"), + BoolDef("includeParentHierarchy", + label="Include Parent Hierarchy", + tooltip="Whether to include parent hierarchy of nodes in " + "the publish instance"), + BoolDef("worldSpace", + label="World-Space Export"), + BoolDef("farm", + label="Submit to farm"), + NumberDef("priority", + label="Priority for farm"), + BoolDef("noNormals", + label="Include normals"), + BoolDef("includeUserDefinedAttributes", + label="Include User Defined Attributes"), + TextDef("attr", + label="Custom Attributes", + placeholder="attr1, attr2"), + TextDef("attrPrefix", + label="Custom Attributes Prefix", + placeholder="prefix1, prefix2"), + EnumDef("dataFormat", + label="Data Format", + items=["ogawa", "HDF"]), + BoolDef("eulerFilter", + label="Apply Euler Filter"), + BoolDef("preRoll", + label="Start from preroll start frame"), + NumberDef("preRollStartFrame", + label="Start frame for preroll"), + BoolDef("refresh", + label="Refresh viewport during export"), + BoolDef("stripNamespaces", + label="Strip namespaces on export"), + BoolDef("uvWrite", + label="Write UVs"), + BoolDef("verbose", + label="Verbose output"), + BoolDef("wholeFrameGeo", + label="Whole Frame Geo"), + BoolDef("writeCreases", + label="Write Creases"), + BoolDef("writeUVSets", + label="Write UV Sets"), + BoolDef("writeVisibility", + label="Write Visibility") + ]) + + # Collect editable state and default values. + resulting_defs = [] + for definition in defs: + # Include by default any attributes which has no editable state + # from settings. + if not hasattr(cls, definition.key + "_editable"): + print("{} was not found.".format(definition.key + "_editable")) + resulting_defs.append(definition) + continue + + # Remove non-editable defs. + if not getattr(cls, definition.key + "_editable"): + continue + + # Set default values from settings. + definition.default = getattr(cls, definition.key) + + resulting_defs.append(definition) + + return resulting_defs + + +class CreateAnimation(plugin.MayaHiddenCreator): """Animation output for character rigs We hide the animation creator from the UI since the creation of it is @@ -31,94 +117,12 @@ class CreateAnimation(plugin.MayaCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): - defs = lib.collect_animation_defs() - - defs.extend([ - BoolDef("writeColorSets", - label="Write vertex colors", - tooltip="Write vertex colors with the geometry"), - BoolDef("writeFaceSets", - label="Write face sets", - tooltip="Write face sets with the geometry"), - BoolDef("renderableOnly", - label="Renderable Only", - tooltip="Only export renderable visible shapes"), - BoolDef("visibleOnly", - label="Visible Only", - tooltip="Only export dag objects visible during " - "frame range"), - BoolDef("includeParentHierarchy", - label="Include Parent Hierarchy", - tooltip="Whether to include parent hierarchy of nodes in " - "the publish instance"), - BoolDef("worldSpace", - label="World-Space Export"), - BoolDef("farm", - label="Submit to farm"), - NumberDef("priority", - label="Priority for farm"), - BoolDef("noNormals", - label="Include normals"), - BoolDef("includeUserDefinedAttributes", - label="Include User Defined Attributes"), - TextDef("attr", - label="Custom Attributes", - placeholder="attr1, attr2"), - TextDef("attrPrefix", - label="Custom Attributes Prefix", - placeholder="prefix1, prefix2"), - EnumDef("dataFormat", - label="Data Format", - items=["ogawa", "HDF"]), - BoolDef("eulerFilter", - label="Apply Euler Filter"), - BoolDef("preRoll", - label="Start from preroll start frame"), - NumberDef("preRollStartFrame", - label="Start frame for preroll"), - BoolDef("refresh", - label="Refresh viewport during export"), - BoolDef("stripNamespaces", - label="Strip namespaces on export"), - BoolDef("uvWrite", - label="Write UVs"), - BoolDef("verbose", - label="Verbose output"), - BoolDef("wholeFrameGeo", - label="Whole Frame Geo"), - BoolDef("writeCreases", - label="Write Creases"), - BoolDef("writeUVSets", - label="Write UV Sets"), - BoolDef("writeVisibility", - label="Write Visibility") - ]) - - # Collect editable state and default values. - resulting_defs = [] - for definition in defs: - # Include by default any attributes which has no editable state - # from settings. - if not hasattr(self, definition.key + "_editable"): - resulting_defs.append(definition) - continue - - # Remove non-editable defs. - if not getattr(self, definition.key + "_editable"): - continue - - # Set default values from settings. - definition.default = getattr(self, definition.key) - - resulting_defs.append(definition) - - return resulting_defs + return get_instance_attr_defs(self) -class CreatePointCache(CreateAnimation): +class CreatePointCache(plugin.MayaCreator): """Alembic pointcache for animated data""" - enabled = True identifier = "io.openpype.creators.maya.pointcache" label = "Pointcache" family = "pointcache" @@ -127,6 +131,9 @@ class CreatePointCache(CreateAnimation): write_face_sets = False include_user_defined_attributes = False + def get_instance_attr_defs(self): + return get_instance_attr_defs(self) + def create(self, subset_name, instance_data, pre_create_data): instance = super(CreatePointCache, self).create( From fcf1c1ec8a5e668014f1e16cc1073952631f1b80 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 1 Sep 2023 10:07:15 +0100 Subject: [PATCH 022/244] Fix writeNormals --- .../maya/plugins/create/create_animation_pointcache.py | 2 ++ openpype/hosts/maya/plugins/publish/collect_animation.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index d9fd0fff86..f8577b1e87 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -41,6 +41,8 @@ def get_instance_attr_defs(cls): label="Priority for farm"), BoolDef("noNormals", label="Include normals"), + BoolDef("writeNormals", + label="Write Normals"), BoolDef("includeUserDefinedAttributes", label="Include User Defined Attributes"), TextDef("attr", diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 94b732cc4d..25747c7a45 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -60,7 +60,11 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): # Alembic and Multiverse share the same attribute functionality but # different names. - instance.data["writeNormals"] = not instance.data["noNormals"] + instance.data["writeNormals"] = ( + instance.data.get("writeNormals") or + not instance.data.get("noNormals") or + False + ) # Backwards compatibility for attributes. backwards_mapping = { From ee8b2302735775be59432ee705f731bcc29d6747 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 1 Sep 2023 10:07:29 +0100 Subject: [PATCH 023/244] Remove redundant code. --- openpype/hosts/maya/plugins/publish/collect_animation.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 25747c7a45..46facffce0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -82,12 +82,6 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): user_defined_attributes = set() for node in hierarchy: attrs = cmds.listAttr(node, userDefined=True) or list() - shapes = cmds.listRelatives(node, shapes=True) or list() - for shape in shapes: - attrs.extend( - cmds.listAttr(shape, userDefined=True) or list() - ) - user_defined_attributes.update(attrs) instance.data["userDefinedAttributes"] = list( From de5c1ceb50ca59ce4e4d2254ab734c428e013aec Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 1 Sep 2023 14:19:35 +0100 Subject: [PATCH 024/244] Add tooltip to preRollStartFrame --- .../plugins/create/create_animation_pointcache.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index f8577b1e87..6a9a8caa03 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -58,8 +58,15 @@ def get_instance_attr_defs(cls): label="Apply Euler Filter"), BoolDef("preRoll", label="Start from preroll start frame"), - NumberDef("preRollStartFrame", - label="Start frame for preroll"), + NumberDef( + "preRollStartFrame", + label="Start frame for preroll", + tooltip=( + "The frame to start scene evaluation at. This is used to set" + " the starting frame for time dependent translations and can" + " be used to evaluate run-up that isn't actually translated." + ) + ), BoolDef("refresh", label="Refresh viewport during export"), BoolDef("stripNamespaces", From 7f152b9175835b71a4dc9385e7b9a88f353be92c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 18 Sep 2023 10:25:01 +0100 Subject: [PATCH 025/244] Sync with develop plugin.py --- openpype/hosts/maya/api/plugin.py | 47 ++++++++++--------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index b9591e60b2..4032618afb 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -65,25 +65,6 @@ def get_reference_node_parents(*args, **kwargs): return lib.get_reference_node_parents(*args, **kwargs) -def apply_settings(cls, project_settings, system_settings): - """Method called on initialization of plugin to apply settings.""" - - settings_name = cls.settings_name - if settings_name is None: - settings_name = cls.__class__.__name__ - - settings = project_settings["maya"]["create"] - settings = settings.get(settings_name) - if settings is None: - cls.log.debug( - "No settings found for {}".format(cls.__class__.__name__) - ) - return - - for key, value in settings.items(): - setattr(cls, key, value) - - class Creator(LegacyCreator): defaults = ['Main'] @@ -281,7 +262,21 @@ class MayaCreator(NewCreator, MayaCreatorBase): def apply_settings(self, project_settings): """Method called on initialization of plugin to apply settings.""" - apply_settings(self, project_settings, system_settings) + + settings_name = self.settings_name + if settings_name is None: + settings_name = self.__class__.__name__ + + settings = project_settings["maya"]["create"] + settings = settings.get(settings_name) + if settings is None: + self.log.debug( + "No settings found for {}".format(self.__class__.__name__) + ) + return + + for key, value in settings.items(): + setattr(self, key, value) class MayaAutoCreator(AutoCreator, MayaCreatorBase): @@ -291,8 +286,6 @@ class MayaAutoCreator(AutoCreator, MayaCreatorBase): any arguments. """ - settings_name = None - def collect_instances(self): return self._default_collect_instances() @@ -302,10 +295,6 @@ class MayaAutoCreator(AutoCreator, MayaCreatorBase): def remove_instances(self, instances): return self._default_remove_instances(instances) - def apply_settings(self, project_settings, system_settings): - """Method called on initialization of plugin to apply settings.""" - apply_settings(self, project_settings, system_settings) - class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): """Hidden creator for Maya. @@ -314,8 +303,6 @@ class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): arguments for 'create' method. """ - settings_name = None - def create(self, *args, **kwargs): return MayaCreator.create(self, *args, **kwargs) @@ -328,10 +315,6 @@ class MayaHiddenCreator(HiddenCreator, MayaCreatorBase): def remove_instances(self, instances): return self._default_remove_instances(instances) - def apply_settings(self, project_settings, system_settings): - """Method called on initialization of plugin to apply settings.""" - apply_settings(self, project_settings, system_settings) - def ensure_namespace(namespace): """Make sure the namespace exists. From bb8b4c1881056d0cf052bea89757de83dbccb432 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 13 Nov 2023 17:55:26 +0000 Subject: [PATCH 026/244] `maya` Define Alembic attributes as enums This commit converts most of the Alembic attributes into an EnumDef. A new setting (also an enum) allows the person to select which of the Alembic attributes are modifiable by the person at publish time. --- .../create/create_animation_pointcache.py | 254 ++++++--- .../defaults/project_settings/maya.json | 108 ++-- .../schemas/schema_maya_create.json | 502 ++++-------------- 3 files changed, 323 insertions(+), 541 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 658c10ae62..b91994c65d 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,5 +1,7 @@ from maya import cmds +from pprint import pprint + from openpype.hosts.maya.api import ( lib, plugin @@ -8,103 +10,173 @@ from openpype.lib import ( BoolDef, TextDef, NumberDef, - EnumDef + EnumDef, + UISeparatorDef, + UILabelDef ) - -def get_instance_attr_defs(cls): +def _get_animation_attr_defs(cls): + """Get Animation generic ddefinitions. + """ defs = lib.collect_animation_defs() - defs.extend([ - BoolDef("writeColorSets", - label="Write vertex colors", - tooltip="Write vertex colors with the geometry"), - BoolDef("writeFaceSets", - label="Write face sets", - tooltip="Write face sets with the geometry"), - BoolDef("renderableOnly", - label="Renderable Only", - tooltip="Only export renderable visible shapes"), - BoolDef("visibleOnly", - label="Visible Only", - tooltip="Only export dag objects visible during " - "frame range"), - BoolDef("includeParentHierarchy", - label="Include Parent Hierarchy", - tooltip="Whether to include parent hierarchy of nodes in " - "the publish instance"), - BoolDef("worldSpace", - label="World-Space Export"), - BoolDef("farm", - label="Submit to farm"), - NumberDef("priority", - label="Priority for farm"), - BoolDef("noNormals", - label="Include normals"), - BoolDef("writeNormals", - label="Write Normals"), - BoolDef("includeUserDefinedAttributes", - label="Include User Defined Attributes"), - TextDef("attr", - label="Custom Attributes", - placeholder="attr1, attr2"), - TextDef("attrPrefix", - label="Custom Attributes Prefix", - placeholder="prefix1, prefix2"), - EnumDef("dataFormat", - label="Data Format", - items=["ogawa", "HDF"]), - BoolDef("eulerFilter", - label="Apply Euler Filter"), - BoolDef("preRoll", - label="Start from preroll start frame"), - NumberDef( + BoolDef("farm", label="Submit to Farm"), + NumberDef("priority", label="Farm job Priority", default=50), + BoolDef("refresh", label="Refresh viewport during export"), + BoolDef("includeParentHierarchy", label="Include Parent Hierarchy"), + BoolDef("writeNormals", label="Write Normals"), + BoolDef("writeCreases", label="Write Creases") + ]) + + return defs + +def _get_animation_abc_attr_defs(cls): + """ Get definitions relating to Alembic. + """ + # List of arguments extracted from AbcExport -h + # Them being here doesn't imply we support them or that we need them at this + # point, it's a convininece list to populate the UI defaults. + alembic_attributes = [ + "preRollStartFrame", + "dontSkipUnwrittenFrames", + "verbose", + "attr", + "autoSubd", + "attrPrefix", + "dataFormat", + "eulerFilter", + "frameRange", + "frameRelativeSample", + "noNormals", + "preRoll", + "renderableOnly", + "root", + "step", + "selection", + "stripNamespaces", + "userAttr", + "userAttrPrefix", + "uvWrite", + "uvsOnly", + "writeColorSets", + "writeFaceSets", + "wholeFrameGeo", + "worldSpace", + "writeVisibility", + "writeUVSets", + "melPerFrameCallback", + "melPostJobCallback", + "pythonPerFrameCallback", + "pythonPostJobCallback" + ] + + abc_defs = [ + UISeparatorDef(), + UILabelDef("Alembic Options") + ] + + print("Processing editable Alembic attributes...") + alembic_editable_attributes = getattr(cls, "abc_editable_flags", None) + + if not alembic_editable_attributes: + return None + + print(alembic_editable_attributes) + + abc_boolean_defs = [ + "writeColorSets", + "writeFaceSets", + "renderableOnly", + "visibleOnly", + "worldSpace", + "noNormals", + "includeUserDefinedAttributes", + "eulerFilter", + "preRoll", + "stripNamespaces", + "uvWrite", + "verbose", + "wholeFrameGeo", + "writeUVSets", + "writeVisibility", + ] + + abc_boolean_defaults = [ + "uvWrite", + "worldSpace", + "writeVisibility", + ] + + enabled_boolean_attributes = [ + attrib + for attrib in abc_boolean_defs + if attrib in alembic_editable_attributes + ] + + if enabled_boolean_attributes: + abc_defs.extend([EnumDef( + "abcExportFlags", + enabled_boolean_attributes, + default=abc_boolean_defaults, + multiselection=True, + label="Alembic Export Flags" + )]) + + + abc_defs.append(TextDef( + "attr", + label="Alembic Custom Attributes", + placeholder="attr1, attr2", + disabled=True if "attr" not in alembic_editable_attributes else False + )) + + abc_defs.append(TextDef( + "attrPrefix", + label="Alembic Custom Attributes Prefix", + placeholder="prefix1, prefix2", + disabled=True if "attrPrefix" not in alembic_editable_attributes else False + )) + + abc_defs.append(EnumDef( + "dataFormat", + label="Alembic Data Format", + items=["ogawa", "HDF"], + disabled=True if "dataFormat" not in alembic_editable_attributes else False + )) + + abc_defs.append(NumberDef( "preRollStartFrame", - label="Start frame for preroll", + label="Start frame for preroll (Alembic)", tooltip=( "The frame to start scene evaluation at. This is used to set" " the starting frame for time dependent translations and can" " be used to evaluate run-up that isn't actually translated." - ) - ), - BoolDef("refresh", - label="Refresh viewport during export"), - BoolDef("stripNamespaces", - label="Strip namespaces on export"), - BoolDef("uvWrite", - label="Write UVs"), - BoolDef("verbose", - label="Verbose output"), - BoolDef("wholeFrameGeo", - label="Whole Frame Geo"), - BoolDef("writeCreases", - label="Write Creases"), - BoolDef("writeUVSets", - label="Write UV Sets"), - BoolDef("writeVisibility", - label="Write Visibility") - ]) + ), + disabled=True if "preRollStartFrame" not in alembic_editable_attributes else False + )) + #['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_add_instance_to_context', '_cached_group_label', '_default_collect_instances', '_default_remove_instances', '_default_update_instances', '_log', '_remove_instance_from_context', 'apply_settings', 'cache_subsets', 'collect_instances', 'collection_shared_data', 'create', 'create_context', 'enabled', 'family', 'get_dynamic_data', 'get_group_label', 'get_icon', 'get_instance_attr_defs', 'get_next_versions_for_instances', 'get_publish_families', 'get_subset_name', 'group_label', 'headless', 'host', 'host_name', 'icon', 'identifier', 'imprint_instance_node', 'include_parent_hierarchy', 'include_user_defined_attributes', 'instance_attr_defs', 'label', 'log', 'name', 'order', 'project_anatomy', 'project_name', 'project_settings', 'read_instance_node', 'remove_instances', 'set_instance_thumbnail_path', 'update_instances', 'write_color_sets', 'write_face_sets'] + #for # Collect editable state and default values. - resulting_defs = [] - for definition in defs: - # Include by default any attributes which has no editable state - # from settings. - if not hasattr(cls, definition.key + "_editable"): - print("{} was not found.".format(definition.key + "_editable")) - resulting_defs.append(definition) - continue + # resulting_defs = [] + # for definition in defs: + # # Include by default any attributes which has no editable state + # # from settings. + # if not hasattr(cls, definition.key + "_editable"): + # print("{} was not found.".format(definition.key + "_editable")) + # resulting_defs.append(definition) + # continue - # Remove non-editable defs. - if not getattr(cls, definition.key + "_editable"): - continue + # # Remove non-editable defs. + # if not getattr(cls, definition.key + "_editable"): + # continue - # Set default values from settings. - definition.default = getattr(cls, definition.key) + # # Set default values from settings. + # definition.default = getattr(cls, definition.key) - resulting_defs.append(definition) + # resulting_defs.append(definition) - return resulting_defs + return abc_defs class CreateAnimation(plugin.MayaHiddenCreator): @@ -126,7 +198,14 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): - return get_instance_attr_defs(self) + defs = _get_animation_attr_defs(self) + + abc_defs = _get_animation_abc_attr_defs(self) + + if abc_defs: + defs.extend(abc_defs) + + return defs def apply_settings(self, project_settings): super(CreateAnimation, self).apply_settings(project_settings) @@ -144,7 +223,14 @@ class CreatePointCache(plugin.MayaCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): - return get_instance_attr_defs(self) + defs = _get_animation_attr_defs(self) + + abc_defs = _get_animation_abc_attr_defs(self) + + if abc_defs: + defs.extend(abc_defs) + + return defs def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index d993365a8a..aaa7afdf45 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -559,57 +559,34 @@ "CreateAnimation": { "default_variants": [], "step": 1.0, - "step_editable": true, - "writeColorSets": false, - "writeColorSets_editable": true, - "writeFaceSets": false, - "writeFaceSets_editable": true, - "renderableOnly": false, - "renderableOnly_editable": true, - "visibleOnly": false, - "visibleOnly_editable": true, + "abc_flags": [ + "writeColorSets", + "visibleOnly", + "worldSpace", + "writeNormals", + "includeUserDefinedAttributes", + "attr", + "attrPrefix" + ], + "abc_editable_flags": [ + "step", + "includeParentHierarchy", + "writeNormals", + "includeUserDefinedAttributes", + "attr", + "attrPrefix" + ], "includeParentHierarchy": false, - "includeParentHierarchy_editable": true, - "worldSpace": true, - "worldSpace_editable": true, "farm": false, - "farm_editable": true, "priority": 50, - "priority_editable": true, "writeNormals": true, - "writeNormals_editable": true, - "includeUserDefinedAttributes": true, - "includeUserDefinedAttributes_editable": true, "attr": "", - "attr_editable": true, "attrPrefix": "", - "attrPrefix_editable": true, "dataFormat": "ogawa", - "dataFormat_editable": false, - "eulerFilter": false, - "eulerFilter_editable": false, - "noNormals": false, - "noNormals_editable": false, - "preRoll": false, - "preRoll_editable": false, "preRollStartFrame": 0, - "preRollStartFrame_editable": false, "refresh": false, - "refresh_editable": false, "stripNamespaces": false, - "stripNamespaces_editable": false, - "uvWrite": true, - "uvWrite_editable": false, - "verbose": false, - "verbose_editable": false, - "wholeFrameGeo": false, - "wholeFrameGeo_editable": false, "writeCreases": false, - "writeCreases_editable": false, - "writeUVSets": false, - "writeUVSets_editable": false, - "writeVisibility": true, - "writeVisibility_editable": false, "write_color_sets": false, "write_face_sets": false, "include_parent_hierarchy": false, @@ -631,57 +608,44 @@ "Main" ], "step": 1.0, - "step_editable": true, + "abc_flags": [ + "writeColorSets", + "visibleOnly", + "worldSpace", + "writeNormals", + "includeUserDefinedAttributes", + "attr", + "attrPrefix" + ], + "abc_editable_flags": [ + "step", + "writeColorSets", + "writeFaceSets", + "renderableOnly", + "visibleOnly", + "worldSpace", + "attr", + "attrPrefix" + ], "writeColorSets": true, - "writeColorSets_editable": true, "writeFaceSets": false, - "writeFaceSets_editable": true, "renderableOnly": false, - "renderableOnly_editable": true, "visibleOnly": false, - "visibleOnly_editable": true, "includeParentHierarchy": false, - "includeParentHierarchy_editable": true, "worldSpace": true, - "worldSpace_editable": true, "farm": false, - "farm_editable": true, "priority": 50, - "priority_editable": true, "writeNormals": true, - "writeNormals_editable": true, "includeUserDefinedAttributes": true, - "includeUserDefinedAttributes_editable": true, "attr": "", - "attr_editable": true, "attrPrefix": "", - "attrPrefix_editable": true, "dataFormat": "ogawa", - "dataFormat_editable": false, - "eulerFilter": false, - "eulerFilter_editable": false, "noNormals": false, - "noNormals_editable": false, - "preRoll": false, - "preRoll_editable": false, "preRollStartFrame": 0, - "preRollStartFrame_editable": false, "refresh": false, - "refresh_editable": false, "stripNamespaces": false, - "stripNamespaces_editable": false, "uvWrite": true, - "uvWrite_editable": false, - "verbose": false, - "verbose_editable": false, - "wholeFrameGeo": false, - "wholeFrameGeo_editable": false, "writeCreases": false, - "writeCreases_editable": false, - "writeUVSets": false, - "writeUVSets_editable": false, - "writeVisibility": true, - "writeVisibility_editable": false, "write_color_sets": false, "write_face_sets": false, "include_user_defined_attributes": false 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 705ddfa1ff..c5bfb18e00 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 @@ -137,143 +137,109 @@ "label": "Default Variants", "object_type": "text" }, - { - "type": "number", - "key": "step", - "label": "Step default", - "minimum": 0.0, - "decimal": 4 - }, - { - "type": "boolean", - "key": "step_editable", - "label": "Step editable" - }, - { - "type": "boolean", - "key": "writeColorSets", - "label": "Write Color Sets default" - }, - { - "type": "boolean", - "key": "writeColorSets_editable", - "label": "Write Color Sets editable" - }, - { - "type": "boolean", - "key": "writeFaceSets", - "label": "Write Face Sets default" - }, - { - "type": "boolean", - "key": "writeFaceSets_editable", - "label": "Write Face Sets editable" - }, - { - "type": "boolean", - "key": "renderableOnly", - "label": "Renderable Only default" - }, - { - "type": "boolean", - "key": "renderableOnly_editable", - "label": "Renderable Only editable" - }, - { - "type": "boolean", - "key": "visibleOnly", - "label": "Visible Only default" - }, - { - "type": "boolean", - "key": "visibleOnly_editable", - "label": "Visible Only editable" - }, { "type": "boolean", "key": "includeParentHierarchy", - "label": "Include Parent Hierarchy default" - }, - { - "type": "boolean", - "key": "includeParentHierarchy_editable", - "label": "Include Parent Hierarchy editable" - }, - { - "type": "boolean", - "key": "worldSpace", - "label": "World Space default" - }, - { - "type": "boolean", - "key": "worldSpace_editable", - "label": "World Space editable" + "label": "Include Parent Hierarchy" }, { "type": "boolean", "key": "farm", - "label": "Farm default" - }, - { - "type": "boolean", - "key": "farm_editable", - "label": "Farm editable" + "label": "Submit to the Farm" }, { "type": "number", "key": "priority", - "label": "Priority default", + "label": "Farm Job Priority", "minimum": 0 }, - { - "type": "boolean", - "key": "priority_editable", - "label": "Priority editable" - }, { "type": "boolean", "key": "writeNormals", - "label": "Write Normals default" - }, - { - "type": "boolean", - "key": "writeNormals_editable", - "label": "Write Normals editable" + "label": "Write Normals" }, { "type": "boolean", - "key": "includeUserDefinedAttributes", - "label": "Include User Defined Attributes default" + "key": "refresh", + "label": "Refresh" }, { - "type": "boolean", - "key": "includeUserDefinedAttributes_editable", - "label": "Include User Defined Attributes editable" + "type": "boolean", + "key": "stripNamespaces", + "label": "Strip Namespaces" + }, + { + "type": "boolean", + "key": "writeCreases", + "label": "Write Creases" + }, + { + "type": "enum", + "key": "abc_flags", + "multiselection": true, + "label": "Alembic Flags", + "enum_items": [ + {"writeColorSets": "writeColorSets"}, + {"writeFaceSets": "writeFaceSets"}, + {"renderableOnly": "renderableOnly"}, + {"visibleOnly": "visibleOnly"}, + {"worldSpace": "worldSpace"}, + {"noNormals": "noNormals"}, + {"eulerFilter": "eulerFilter"}, + {"preRoll": "preRoll"}, + {"uvWrite": "uvWrite"}, + {"verbose": "verbose"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + }, + { + "type": "enum", + "key": "abc_editable_flags", + "multiselection": true, + "label": "Alembic Editable Flags", + "enum_items": [ + {"step": "step"}, + {"writeColorSets": "writeColorSets"}, + {"writeFaceSets": "writeFaceSets"}, + {"renderableOnly": "renderableOnly"}, + {"visibleOnly": "visibleOnly"}, + {"worldSpace": "worldSpace"}, + {"noNormals": "noNormals"}, + {"attr": "attr"}, + {"attrPrefix": "attrPrefix"}, + {"eulerFilter": "eulerFilter"}, + {"preRoll": "preRoll"}, + {"preRollStartFrame": "preRollStartFrame"}, + {"uvWrite": "uvWrite"}, + {"verbose": "verbose"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + }, + { + "type": "number", + "key": "step", + "label": "Step", + "minimum": 0.0, + "decimal": 4 }, { "type": "text", "key": "attr", - "label": "Attr default" - }, - { - "type": "boolean", - "key": "attr_editable", - "label": "Attr editable" + "label": "Attr" }, { "type": "text", "key": "attrPrefix", - "label": "Attr Prefix default" - }, - { - "type": "boolean", - "key": "attrPrefix_editable", - "label": "Attr Prefix editable" + "label": "Attr Prefix" }, { "type": "enum", "key": "dataFormat", - "label": "Data Format default", + "label": "Data Format", "enum_items": [ { "ogawa": "ogawa" @@ -283,132 +249,12 @@ } ] }, - { - "type": "boolean", - "key": "dataFormat_editable", - "label": "Data Format editable" - }, - { - "type": "boolean", - "key": "eulerFilter", - "label": "Euler Filter default" - }, - { - "type": "boolean", - "key": "eulerFilter_editable", - "label": "Euler Filter editable" - }, - { - "type": "boolean", - "key": "noNormals", - "label": "No Normals default" - }, - { - "type": "boolean", - "key": "noNormals_editable", - "label": "No Normals editable" - }, - { - "type": "boolean", - "key": "preRoll", - "label": "Pre Roll default" - }, - { - "type": "boolean", - "key": "preRoll_editable", - "label": "Pre Roll editable" - }, { "type": "number", "key": "preRollStartFrame", - "label": "Pre Roll Start Frame default", + "label": "Pre Roll Start Frame", "minimum": 0 }, - { - "type": "boolean", - "key": "preRollStartFrame_editable", - "label": "Pre Roll Start Frame editable" - }, - { - "type": "boolean", - "key": "refresh", - "label": "Refresh default" - }, - { - "type": "boolean", - "key": "refresh_editable", - "label": "Refresh editable" - }, - { - "type": "boolean", - "key": "stripNamespaces", - "label": "Strip Namespaces default" - }, - { - "type": "boolean", - "key": "stripNamespaces_editable", - "label": "Strip Namespaces editable" - }, - { - "type": "boolean", - "key": "uvWrite", - "label": "UV Write default" - }, - { - "type": "boolean", - "key": "uvWrite_editable", - "label": "UV Write editable" - }, - { - "type": "boolean", - "key": "verbose", - "label": "Verbose default" - }, - { - "type": "boolean", - "key": "verbose_editable", - "label": "Verbose editable" - }, - { - "type": "boolean", - "key": "wholeFrameGeo", - "label": "Whole Frame Geo default" - }, - { - "type": "boolean", - "key": "wholeFrameGeo_editable", - "label": "Whole Frame Geo editable" - }, - { - "type": "boolean", - "key": "writeCreases", - "label": "Write Creases default" - }, - { - "type": "boolean", - "key": "writeCreases_editable", - "label": "Write Creases editable" - }, - { - "type": "boolean", - "key": "writeUVSets", - "label": "Write UV Sets default" - }, - { - "type": "boolean", - "key": "writeUVSets_editable", - "label": "Write UV Sets editable" - }, - { - "type": "boolean", - "key": "writeVisibility", - "label": "Write Visibility default" - }, - { - "type": "boolean", - "key": "writeVisibility_editable", - "label": "Write Visibility editable" - }, { "type": "boolean", "key": "write_color_sets", @@ -479,6 +325,52 @@ "label": "Default Variants", "object_type": "text" }, + { + "type": "enum", + "key": "abc_flags", + "multiselection": true, + "label": "Alembic Flags", + "enum_items": [ + {"writeColorSets": "writeColorSets"}, + {"writeFaceSets": "writeFaceSets"}, + {"renderableOnly": "renderableOnly"}, + {"visibleOnly": "visibleOnly"}, + {"worldSpace": "worldSpace"}, + {"noNormals": "noNormals"}, + {"eulerFilter": "eulerFilter"}, + {"preRoll": "preRoll"}, + {"uvWrite": "uvWrite"}, + {"verbose": "verbose"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + }, + { + "type": "enum", + "key": "abc_editable_flags", + "multiselection": true, + "label": "Alembic Editable Flags", + "enum_items": [ + {"step": "step"}, + {"writeColorSets": "writeColorSets"}, + {"writeFaceSets": "writeFaceSets"}, + {"renderableOnly": "renderableOnly"}, + {"visibleOnly": "visibleOnly"}, + {"worldSpace": "worldSpace"}, + {"noNormals": "noNormals"}, + {"attr": "attr"}, + {"attrPrefix": "attrPrefix"}, + {"eulerFilter": "eulerFilter"}, + {"preRoll": "preRoll"}, + {"preRollStartFrame": "preRollStartFrame"}, + {"uvWrite": "uvWrite"}, + {"verbose": "verbose"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + }, { "type": "number", "key": "step", @@ -486,132 +378,67 @@ "minimum": 0.0, "decimal": 4 }, - { - "type": "boolean", - "key": "step_editable", - "label": "Step editable" - }, { "type": "boolean", "key": "writeColorSets", "label": "Write Color Sets default" }, - { - "type": "boolean", - "key": "writeColorSets_editable", - "label": "Write Color Sets editable" - }, { "type": "boolean", "key": "writeFaceSets", "label": "Write Face Sets default" }, - { - "type": "boolean", - "key": "writeFaceSets_editable", - "label": "Write Face Sets editable" - }, { "type": "boolean", "key": "renderableOnly", "label": "Renderable Only default" }, - { - "type": "boolean", - "key": "renderableOnly_editable", - "label": "Renderable Only editable" - }, { "type": "boolean", "key": "visibleOnly", "label": "Visible Only default" }, - { - "type": "boolean", - "key": "visibleOnly_editable", - "label": "Visible Only editable" - }, { "type": "boolean", "key": "includeParentHierarchy", "label": "Include Parent Hierarchy default" }, - { - "type": "boolean", - "key": "includeParentHierarchy_editable", - "label": "Include Parent Hierarchy editable" - }, { "type": "boolean", "key": "worldSpace", "label": "World Space default" }, - { - "type": "boolean", - "key": "worldSpace_editable", - "label": "World Space editable" - }, { "type": "boolean", "key": "farm", "label": "Farm default" }, - { - "type": "boolean", - "key": "farm_editable", - "label": "Farm editable" - }, { "type": "number", "key": "priority", "label": "Priority default", "minimum": 0 }, - { - "type": "boolean", - "key": "priority_editable", - "label": "Priority editable" - }, { "type": "boolean", "key": "writeNormals", "label": "Write Normals default" }, - { - "type": "boolean", - "key": "writeNormals_editable", - "label": "Write Normals editable" - }, { "type": "boolean", "key": "includeUserDefinedAttributes", "label": "Include User Defined Attributes default" }, - { - "type": "boolean", - "key": "includeUserDefinedAttributes_editable", - "label": "Include User Defined Attributes editable" - }, { "type": "text", "key": "attr", "label": "Attr default" }, - { - "type": "boolean", - "key": "attr_editable", - "label": "Attr editable" - }, { "type": "text", "key": "attrPrefix", "label": "Attr Prefix default" }, - { - "type": "boolean", - "key": "attrPrefix_editable", - "label": "Attr Prefix editable" - }, { "type": "enum", "key": "dataFormat", @@ -625,132 +452,37 @@ } ] }, - { - "type": "boolean", - "key": "dataFormat_editable", - "label": "Data Format editable" - }, - { - "type": "boolean", - "key": "eulerFilter", - "label": "Euler Filter default" - }, - { - "type": "boolean", - "key": "eulerFilter_editable", - "label": "Euler Filter editable" - }, { "type": "boolean", "key": "noNormals", "label": "No Normals default" }, - { - "type": "boolean", - "key": "noNormals_editable", - "label": "No Normals editable" - }, - { - "type": "boolean", - "key": "preRoll", - "label": "Pre Roll default" - }, - { - "type": "boolean", - "key": "preRoll_editable", - "label": "Pre Roll editable" - }, { "type": "number", "key": "preRollStartFrame", "label": "Pre Roll Start Frame default", "minimum": 0 }, - { - "type": "boolean", - "key": "preRollStartFrame_editable", - "label": "Pre Roll Start Frame editable" - }, { "type": "boolean", "key": "refresh", "label": "Refresh default" }, - { - "type": "boolean", - "key": "refresh_editable", - "label": "Refresh editable" - }, { "type": "boolean", "key": "stripNamespaces", "label": "Strip Namespaces default" }, - { - "type": "boolean", - "key": "stripNamespaces_editable", - "label": "Strip Namespaces editable" - }, { "type": "boolean", "key": "uvWrite", "label": "UV Write default" }, - { - "type": "boolean", - "key": "uvWrite_editable", - "label": "UV Write editable" - }, - { - "type": "boolean", - "key": "verbose", - "label": "Verbose default" - }, - { - "type": "boolean", - "key": "verbose_editable", - "label": "Verbose editable" - }, - { - "type": "boolean", - "key": "wholeFrameGeo", - "label": "Whole Frame Geo default" - }, - { - "type": "boolean", - "key": "wholeFrameGeo_editable", - "label": "Whole Frame Geo editable" - }, { "type": "boolean", "key": "writeCreases", "label": "Write Creases default" }, - { - "type": "boolean", - "key": "writeCreases_editable", - "label": "Write Creases editable" - }, - { - "type": "boolean", - "key": "writeUVSets", - "label": "Write UV Sets default" - }, - { - "type": "boolean", - "key": "writeUVSets_editable", - "label": "Write UV Sets editable" - }, - { - "type": "boolean", - "key": "writeVisibility", - "label": "Write Visibility default" - }, - { - "type": "boolean", - "key": "writeVisibility_editable", - "label": "Write Visibility editable" - }, { "type": "boolean", "key": "write_color_sets", From 14fc3ab8264162758d5e430fadfb71254a070fc2 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 14 Nov 2023 16:08:11 +0000 Subject: [PATCH 027/244] `maya` Display Alembic settings for Animation Previous commit wasn't displaying the settings for the `CreateAnimation` creator since it's a "HiddenCreator", this commit ensures we display all the enabled Alembic settins in the Publisher. --- .../create/create_animation_pointcache.py | 143 +++++++++--------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index b91994c65d..335d054ab3 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -2,37 +2,36 @@ from maya import cmds from pprint import pprint -from openpype.hosts.maya.api import ( - lib, - plugin -) +from openpype.hosts.maya.api import lib, plugin from openpype.lib import ( BoolDef, TextDef, NumberDef, EnumDef, UISeparatorDef, - UILabelDef + UILabelDef, ) + def _get_animation_attr_defs(cls): - """Get Animation generic ddefinitions. - """ + """Get Animation generic ddefinitions.""" defs = lib.collect_animation_defs() - defs.extend([ - BoolDef("farm", label="Submit to Farm"), - NumberDef("priority", label="Farm job Priority", default=50), - BoolDef("refresh", label="Refresh viewport during export"), - BoolDef("includeParentHierarchy", label="Include Parent Hierarchy"), - BoolDef("writeNormals", label="Write Normals"), - BoolDef("writeCreases", label="Write Creases") - ]) + defs.extend( + [ + BoolDef("farm", label="Submit to Farm"), + NumberDef("priority", label="Farm job Priority", default=50), + BoolDef("refresh", label="Refresh viewport during export"), + BoolDef("includeParentHierarchy", label="Include Parent Hierarchy"), + BoolDef("writeNormals", label="Write Normals"), + BoolDef("writeCreases", label="Write Creases"), + ] + ) return defs + def _get_animation_abc_attr_defs(cls): - """ Get definitions relating to Alembic. - """ + """Get definitions relating to Alembic.""" # List of arguments extracted from AbcExport -h # Them being here doesn't imply we support them or that we need them at this # point, it's a convininece list to populate the UI defaults. @@ -67,21 +66,18 @@ def _get_animation_abc_attr_defs(cls): "melPerFrameCallback", "melPostJobCallback", "pythonPerFrameCallback", - "pythonPostJobCallback" + "pythonPostJobCallback", ] - abc_defs = [ - UISeparatorDef(), - UILabelDef("Alembic Options") - ] + abc_defs = [UISeparatorDef(), UILabelDef("Alembic Options")] - print("Processing editable Alembic attributes...") alembic_editable_attributes = getattr(cls, "abc_editable_flags", None) if not alembic_editable_attributes: + print("No Almbic attributes found in settings.") return None - print(alembic_editable_attributes) + print("Processing editable Alembic attributes...") abc_boolean_defs = [ "writeColorSets", @@ -108,43 +104,51 @@ def _get_animation_abc_attr_defs(cls): ] enabled_boolean_attributes = [ - attrib - for attrib in abc_boolean_defs - if attrib in alembic_editable_attributes + attrib for attrib in abc_boolean_defs if attrib in alembic_editable_attributes ] if enabled_boolean_attributes: - abc_defs.extend([EnumDef( - "abcExportFlags", - enabled_boolean_attributes, - default=abc_boolean_defaults, - multiselection=True, - label="Alembic Export Flags" - )]) + abc_defs.extend( + [ + EnumDef( + "abcExportFlags", + enabled_boolean_attributes, + default=abc_boolean_defaults, + multiselection=True, + label="Alembic Export Flags", + ) + ] + ) - - abc_defs.append(TextDef( + abc_defs.append( + TextDef( "attr", label="Alembic Custom Attributes", placeholder="attr1, attr2", - disabled=True if "attr" not in alembic_editable_attributes else False - )) + disabled=True if "attr" not in alembic_editable_attributes else False, + ) + ) - abc_defs.append(TextDef( + abc_defs.append( + TextDef( "attrPrefix", label="Alembic Custom Attributes Prefix", placeholder="prefix1, prefix2", - disabled=True if "attrPrefix" not in alembic_editable_attributes else False - )) + disabled=True if "attrPrefix" not in alembic_editable_attributes else False, + ) + ) - abc_defs.append(EnumDef( + abc_defs.append( + EnumDef( "dataFormat", label="Alembic Data Format", items=["ogawa", "HDF"], - disabled=True if "dataFormat" not in alembic_editable_attributes else False - )) + disabled=True if "dataFormat" not in alembic_editable_attributes else False, + ) + ) - abc_defs.append(NumberDef( + abc_defs.append( + NumberDef( "preRollStartFrame", label="Start frame for preroll (Alembic)", tooltip=( @@ -152,29 +156,11 @@ def _get_animation_abc_attr_defs(cls): " the starting frame for time dependent translations and can" " be used to evaluate run-up that isn't actually translated." ), - disabled=True if "preRollStartFrame" not in alembic_editable_attributes else False - )) - - #['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_add_instance_to_context', '_cached_group_label', '_default_collect_instances', '_default_remove_instances', '_default_update_instances', '_log', '_remove_instance_from_context', 'apply_settings', 'cache_subsets', 'collect_instances', 'collection_shared_data', 'create', 'create_context', 'enabled', 'family', 'get_dynamic_data', 'get_group_label', 'get_icon', 'get_instance_attr_defs', 'get_next_versions_for_instances', 'get_publish_families', 'get_subset_name', 'group_label', 'headless', 'host', 'host_name', 'icon', 'identifier', 'imprint_instance_node', 'include_parent_hierarchy', 'include_user_defined_attributes', 'instance_attr_defs', 'label', 'log', 'name', 'order', 'project_anatomy', 'project_name', 'project_settings', 'read_instance_node', 'remove_instances', 'set_instance_thumbnail_path', 'update_instances', 'write_color_sets', 'write_face_sets'] - #for - # Collect editable state and default values. - # resulting_defs = [] - # for definition in defs: - # # Include by default any attributes which has no editable state - # # from settings. - # if not hasattr(cls, definition.key + "_editable"): - # print("{} was not found.".format(definition.key + "_editable")) - # resulting_defs.append(definition) - # continue - - # # Remove non-editable defs. - # if not getattr(cls, definition.key + "_editable"): - # continue - - # # Set default values from settings. - # definition.default = getattr(cls, definition.key) - - # resulting_defs.append(definition) + disabled=True + if "preRollStartFrame" not in alembic_editable_attributes + else False, + ) + ) return abc_defs @@ -186,6 +172,7 @@ class CreateAnimation(plugin.MayaHiddenCreator): automated upon loading a rig. There's an inventory action to recreate it for loaded rigs if by chance someone deleted the animation instance. """ + identifier = "io.openpype.creators.maya.animation" name = "animationDefault" label = "Animation" @@ -198,6 +185,19 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): + super(CreateAnimation, self).get_instance_attr_defs() + # adding project settings, since MayaHiddenCreator does not + # handle this for us (yet?) + settings = ( + getattr(self, "project_settings", {}) + .get("maya", {}) + .get("create", {}) + .get("CreateAnimation") + ) + + for key, value in settings.items(): + setattr(self, key, value) + defs = _get_animation_attr_defs(self) abc_defs = _get_animation_abc_attr_defs(self) @@ -207,9 +207,6 @@ class CreateAnimation(plugin.MayaHiddenCreator): return defs - def apply_settings(self, project_settings): - super(CreateAnimation, self).apply_settings(project_settings) - class CreatePointCache(plugin.MayaCreator): """Alembic pointcache for animated data""" @@ -223,6 +220,9 @@ class CreatePointCache(plugin.MayaCreator): include_user_defined_attributes = False def get_instance_attr_defs(self): + super(CreatePointCache, self).get_instance_attr_defs() + # defs = self.get_instance_attr_defs() + print(self.instance_attr_defs) defs = _get_animation_attr_defs(self) abc_defs = _get_animation_abc_attr_defs(self) @@ -233,7 +233,6 @@ class CreatePointCache(plugin.MayaCreator): return defs def create(self, subset_name, instance_data, pre_create_data): - instance = super(CreatePointCache, self).create( subset_name, instance_data, pre_create_data ) From f4be711748dcd81439a7788a00d857b48622dbf9 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 14 Nov 2023 16:39:21 +0000 Subject: [PATCH 028/244] `maya.api.alembic` Move all the Alembic methods into it's own Following the `fbx` and `gltf` formats, a new files has been created for `abc` files, for a better organization and re-use of common parts. --- openpype/hosts/maya/api/alembic.py | 290 +++++++++++++++++ openpype/hosts/maya/api/lib.py | 292 ------------------ .../plugins/publish/extract_pointcache.py | 3 +- .../maya/plugins/publish/extract_proxy_abc.py | 2 +- .../extract_unreal_skeletalmesh_abc.py | 2 +- .../plugins/publish/extract_workfile_xgen.py | 2 +- 6 files changed, 295 insertions(+), 296 deletions(-) create mode 100644 openpype/hosts/maya/api/alembic.py diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py new file mode 100644 index 0000000000..f5684256cc --- /dev/null +++ b/openpype/hosts/maya/api/alembic.py @@ -0,0 +1,290 @@ +# The maya alembic export types +_alembic_options = { + "startFrame": float, + "endFrame": float, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "eulerFilter": bool, + "frameRelativeSample": float, + "noNormals": bool, + "renderableOnly": bool, + "step": float, + "stripNamespaces": bool, + "verbose": bool, + "uvWrite": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeVisibility": bool, + "writeColorSets": bool, + "writeFaceSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "writeUVSets": bool, # Maya 2017+ + "dataFormat": str, + "root": (list, tuple), + "attr": (list, tuple), + "attrPrefix": (list, tuple), + "userAttr": (list, tuple), + "melPerFrameCallback": str, + "melPostJobCallback": str, + "pythonPerFrameCallback": str, + "pythonPostJobCallback": str, + "selection": bool, + "preRoll": bool, + "preRollStartFrame": int +} + + +def extract_alembic(file, + startFrame=None, + endFrame=None, + frameRange="", + eulerFilter=True, + noNormals=False, + preRoll=False, + renderableOnly=False, + selection=True, + uvWrite=True, + writeColorSets=False, + writeFaceSets=False, + wholeFrameGeo=False, + worldSpace=False, + writeVisibility=False, + writeUVSets=False, + writeCreases=False, + dataFormat="ogawa", + step=1.0, + attr=None, + attrPrefix=None, + root=None, + stripNamespaces=True, + verbose=False, + preRollStartFrame=0): + """Extract a single Alembic Cache. + + This extracts an Alembic cache using the `-selection` flag to minimize + the extracted content to solely what was Collected into the instance. + + Arguments: + + startFrame (float): Start frame of output. Ignored if `frameRange` + provided. + + endFrame (float): End frame of output. Ignored if `frameRange` + provided. + + frameRange (tuple or str): Two-tuple with start and end frame or a + string formatted as: "startFrame endFrame". This argument + overrides `startFrame` and `endFrame` arguments. + + eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with + an Euler filter. Euler filtering helps resolve irregularities in + rotations especially if X, Y, and Z rotations exceed 360 degrees. + Defaults to True. + + noNormals (bool): When on, normal data from the original polygon + objects is not included in the exported Alembic cache file. + + preRoll (bool): This frame range will not be sampled. + Defaults to False. + + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, + such as hidden objects, are not included in the Alembic file. + Defaults to False. + + selection (bool): Write out all all selected nodes from the + active selection list that are descendents of the roots specified + with -root. Defaults to False. + + uvWrite (bool): When on, UV data from polygon meshes and subdivision + objects are written to the Alembic file. Only the current UV map is + included. + + writeColorSets (bool): Write all color sets on MFnMeshes as + color 3 or color 4 indexed geometry parameters with face varying + scope. Defaults to False. + + writeFaceSets (bool): Write all Face sets on MFnMeshes. + Defaults to False. + + wholeFrameGeo (bool): Data for geometry will only be written + out on whole frames. Defaults to False. + + worldSpace (bool): When on, the top node in the node hierarchy is + stored as world space. By default, these nodes are stored as local + space. Defaults to False. + + writeVisibility (bool): Visibility state will be stored in + the Alembic file. Otherwise everything written out is treated as + visible. Defaults to False. + + writeUVSets (bool): Write all uv sets on MFnMeshes as vector + 2 indexed geometry parameters with face varying scope. Defaults to + False. + + writeCreases (bool): If the mesh has crease edges or crease + vertices, the mesh (OPolyMesh) would now be written out as an OSubD + and crease info will be stored in the Alembic file. Otherwise, + creases info won't be preserved in Alembic file unless a custom + Boolean attribute SubDivisionMesh has been added to mesh node and + its value is true. Defaults to False. + + dataFormat (str): The data format to use for the cache, + defaults to "ogawa" + + step (float): The time interval (expressed in frames) at + which the frame range is sampled. Additional samples around each + frame can be specified with -frs. Defaults to 1.0. + + attr (list of str, optional): A specific geometric attribute to write + out. Defaults to []. + + attrPrefix (list of str, optional): Prefix filter for determining which + geometric attributes to write out. Defaults to ["ABC_"]. + + root (list of str): Maya dag path which will be parented to + the root of the Alembic file. Defaults to [], which means the + entire scene will be written out. + + stripNamespaces (bool): When on, any namespaces associated with the + exported objects are removed from the Alembic file. For example, an + object with the namespace taco:foo:bar appears as bar in the + Alembic file. + + verbose (bool): When on, outputs frame number information to the + Script Editor or output window during extraction. + + preRollStartFrame (float): The frame to start scene + evaluation at. This is used to set the starting frame for time + dependent translations and can be used to evaluate run-up that + isn't actually translated. Defaults to 0. + """ + + # Ensure alembic exporter is loaded + cmds.loadPlugin('AbcExport', quiet=True) + + # Alembic Exporter requires forward slashes + file = file.replace('\\', '/') + + # Ensure list arguments are valid. + attr = attr or [] + attrPrefix = attrPrefix or [] + root = root or [] + + # Pass the start and end frame on as `frameRange` so that it + # never conflicts with that argument + if not frameRange: + # Fallback to maya timeline if no start or end frame provided. + if startFrame is None: + startFrame = cmds.playbackOptions(query=True, + animationStartTime=True) + if endFrame is None: + endFrame = cmds.playbackOptions(query=True, + animationEndTime=True) + + # Ensure valid types are converted to frame range + assert isinstance(startFrame, _alembic_options["startFrame"]) + assert isinstance(endFrame, _alembic_options["endFrame"]) + frameRange = "{0} {1}".format(startFrame, endFrame) + else: + # Allow conversion from tuple for `frameRange` + if isinstance(frameRange, (list, tuple)): + assert len(frameRange) == 2 + frameRange = "{0} {1}".format(frameRange[0], frameRange[1]) + + # Assemble options + options = { + "selection": selection, + "frameRange": frameRange, + "eulerFilter": eulerFilter, + "noNormals": noNormals, + "preRoll": preRoll, + "renderableOnly": renderableOnly, + "selection": selection, + "uvWrite": uvWrite, + "writeColorSets": writeColorSets, + "writeFaceSets": writeFaceSets, + "wholeFrameGeo": wholeFrameGeo, + "worldSpace": worldSpace, + "writeVisibility": writeVisibility, + "writeUVSets": writeUVSets, + "writeCreases": writeCreases, + "dataFormat": dataFormat, + "step": step, + "attr": attr, + "attrPrefix": attrPrefix, + "stripNamespaces": stripNamespaces, + "verbose": verbose, + "preRollStartFrame": preRollStartFrame + } + + # Validate options + for key, value in options.copy().items(): + + # Discard unknown options + if key not in _alembic_options: + log.warning("extract_alembic() does not support option '%s'. " + "Flag will be ignored..", key) + options.pop(key) + continue + + # Validate value type + valid_types = _alembic_options[key] + if not isinstance(value, valid_types): + raise TypeError("Alembic option unsupported type: " + "{0} (expected {1})".format(value, valid_types)) + + # Ignore empty values, like an empty string, since they mess up how + # job arguments are built + if isinstance(value, (list, tuple)): + value = [x for x in value if x.strip()] + + # Ignore option completely if no values remaining + if not value: + options.pop(key) + continue + + options[key] = value + + # The `writeCreases` argument was changed to `autoSubd` in Maya 2018+ + maya_version = int(cmds.about(version=True)) + if maya_version >= 2018: + options['autoSubd'] = options.pop('writeCreases', False) + + # Format the job string from options + job_args = list() + for key, value in options.items(): + if isinstance(value, (list, tuple)): + for entry in value: + job_args.append("-{} {}".format(key, entry)) + elif isinstance(value, bool): + # Add only when state is set to True + if value: + job_args.append("-{0}".format(key)) + else: + job_args.append("-{0} {1}".format(key, value)) + + job_str = " ".join(job_args) + job_str += ' -file "%s"' % file + + # Ensure output directory exists + parent_dir = os.path.dirname(file) + if not os.path.exists(parent_dir): + os.makedirs(parent_dir) + + if verbose: + log.debug("Preparing Alembic export with options: %s", + json.dumps(options, indent=4)) + log.debug("Extracting Alembic with job arguments: %s", job_str) + + # Perform extraction + print("Alembic Job Arguments : {}".format(job_str)) + + # Disable the parallel evaluation temporarily to ensure no buggy + # exports are made. (PLN-31) + # TODO: Make sure this actually fixes the issues + with evaluation("off"): + cmds.AbcExport(j=job_str, verbose=verbose) + + if verbose: + log.debug("Extracted Alembic to: %s", file) + + return file diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 8b74319563..4058cc446d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -81,40 +81,6 @@ DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] -# The maya alembic export types -_alembic_options = { - "startFrame": float, - "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame - "eulerFilter": bool, - "frameRelativeSample": float, - "noNormals": bool, - "renderableOnly": bool, - "step": float, - "stripNamespaces": bool, - "verbose": bool, - "uvWrite": bool, - "wholeFrameGeo": bool, - "worldSpace": bool, - "writeVisibility": bool, - "writeColorSets": bool, - "writeFaceSets": bool, - "writeCreases": bool, # Maya 2015 Ext1+ - "writeUVSets": bool, # Maya 2017+ - "dataFormat": str, - "root": (list, tuple), - "attr": (list, tuple), - "attrPrefix": (list, tuple), - "userAttr": (list, tuple), - "melPerFrameCallback": str, - "melPostJobCallback": str, - "pythonPerFrameCallback": str, - "pythonPostJobCallback": str, - "selection": bool, - "preRoll": bool, - "preRollStartFrame": int -} - INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94} @@ -1152,264 +1118,6 @@ def is_visible(node, return True - -def extract_alembic(file, - startFrame=None, - endFrame=None, - frameRange="", - eulerFilter=True, - noNormals=False, - preRoll=False, - renderableOnly=False, - selection=True, - uvWrite=True, - writeColorSets=False, - writeFaceSets=False, - wholeFrameGeo=False, - worldSpace=False, - writeVisibility=False, - writeUVSets=False, - writeCreases=False, - dataFormat="ogawa", - step=1.0, - attr=None, - attrPrefix=None, - root=None, - stripNamespaces=True, - verbose=False, - preRollStartFrame=0): - """Extract a single Alembic Cache. - - This extracts an Alembic cache using the `-selection` flag to minimize - the extracted content to solely what was Collected into the instance. - - Arguments: - - startFrame (float): Start frame of output. Ignored if `frameRange` - provided. - - endFrame (float): End frame of output. Ignored if `frameRange` - provided. - - frameRange (tuple or str): Two-tuple with start and end frame or a - string formatted as: "startFrame endFrame". This argument - overrides `startFrame` and `endFrame` arguments. - - eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with - an Euler filter. Euler filtering helps resolve irregularities in - rotations especially if X, Y, and Z rotations exceed 360 degrees. - Defaults to True. - - noNormals (bool): When on, normal data from the original polygon - objects is not included in the exported Alembic cache file. - - preRoll (bool): This frame range will not be sampled. - Defaults to False. - - renderableOnly (bool): When on, any non-renderable nodes or hierarchy, - such as hidden objects, are not included in the Alembic file. - Defaults to False. - - selection (bool): Write out all all selected nodes from the - active selection list that are descendents of the roots specified - with -root. Defaults to False. - - uvWrite (bool): When on, UV data from polygon meshes and subdivision - objects are written to the Alembic file. Only the current UV map is - included. - - writeColorSets (bool): Write all color sets on MFnMeshes as - color 3 or color 4 indexed geometry parameters with face varying - scope. Defaults to False. - - writeFaceSets (bool): Write all Face sets on MFnMeshes. - Defaults to False. - - wholeFrameGeo (bool): Data for geometry will only be written - out on whole frames. Defaults to False. - - worldSpace (bool): When on, the top node in the node hierarchy is - stored as world space. By default, these nodes are stored as local - space. Defaults to False. - - writeVisibility (bool): Visibility state will be stored in - the Alembic file. Otherwise everything written out is treated as - visible. Defaults to False. - - writeUVSets (bool): Write all uv sets on MFnMeshes as vector - 2 indexed geometry parameters with face varying scope. Defaults to - False. - - writeCreases (bool): If the mesh has crease edges or crease - vertices, the mesh (OPolyMesh) would now be written out as an OSubD - and crease info will be stored in the Alembic file. Otherwise, - creases info won't be preserved in Alembic file unless a custom - Boolean attribute SubDivisionMesh has been added to mesh node and - its value is true. Defaults to False. - - dataFormat (str): The data format to use for the cache, - defaults to "ogawa" - - step (float): The time interval (expressed in frames) at - which the frame range is sampled. Additional samples around each - frame can be specified with -frs. Defaults to 1.0. - - attr (list of str, optional): A specific geometric attribute to write - out. Defaults to []. - - attrPrefix (list of str, optional): Prefix filter for determining which - geometric attributes to write out. Defaults to ["ABC_"]. - - root (list of str): Maya dag path which will be parented to - the root of the Alembic file. Defaults to [], which means the - entire scene will be written out. - - stripNamespaces (bool): When on, any namespaces associated with the - exported objects are removed from the Alembic file. For example, an - object with the namespace taco:foo:bar appears as bar in the - Alembic file. - - verbose (bool): When on, outputs frame number information to the - Script Editor or output window during extraction. - - preRollStartFrame (float): The frame to start scene - evaluation at. This is used to set the starting frame for time - dependent translations and can be used to evaluate run-up that - isn't actually translated. Defaults to 0. - """ - - # Ensure alembic exporter is loaded - cmds.loadPlugin('AbcExport', quiet=True) - - # Alembic Exporter requires forward slashes - file = file.replace('\\', '/') - - # Ensure list arguments are valid. - attr = attr or [] - attrPrefix = attrPrefix or [] - root = root or [] - - # Pass the start and end frame on as `frameRange` so that it - # never conflicts with that argument - if not frameRange: - # Fallback to maya timeline if no start or end frame provided. - if startFrame is None: - startFrame = cmds.playbackOptions(query=True, - animationStartTime=True) - if endFrame is None: - endFrame = cmds.playbackOptions(query=True, - animationEndTime=True) - - # Ensure valid types are converted to frame range - assert isinstance(startFrame, _alembic_options["startFrame"]) - assert isinstance(endFrame, _alembic_options["endFrame"]) - frameRange = "{0} {1}".format(startFrame, endFrame) - else: - # Allow conversion from tuple for `frameRange` - if isinstance(frameRange, (list, tuple)): - assert len(frameRange) == 2 - frameRange = "{0} {1}".format(frameRange[0], frameRange[1]) - - # Assemble options - options = { - "selection": selection, - "frameRange": frameRange, - "eulerFilter": eulerFilter, - "noNormals": noNormals, - "preRoll": preRoll, - "renderableOnly": renderableOnly, - "selection": selection, - "uvWrite": uvWrite, - "writeColorSets": writeColorSets, - "writeFaceSets": writeFaceSets, - "wholeFrameGeo": wholeFrameGeo, - "worldSpace": worldSpace, - "writeVisibility": writeVisibility, - "writeUVSets": writeUVSets, - "writeCreases": writeCreases, - "dataFormat": dataFormat, - "step": step, - "attr": attr, - "attrPrefix": attrPrefix, - "stripNamespaces": stripNamespaces, - "verbose": verbose, - "preRollStartFrame": preRollStartFrame - } - - # Validate options - for key, value in options.copy().items(): - - # Discard unknown options - if key not in _alembic_options: - log.warning("extract_alembic() does not support option '%s'. " - "Flag will be ignored..", key) - options.pop(key) - continue - - # Validate value type - valid_types = _alembic_options[key] - if not isinstance(value, valid_types): - raise TypeError("Alembic option unsupported type: " - "{0} (expected {1})".format(value, valid_types)) - - # Ignore empty values, like an empty string, since they mess up how - # job arguments are built - if isinstance(value, (list, tuple)): - value = [x for x in value if x.strip()] - - # Ignore option completely if no values remaining - if not value: - options.pop(key) - continue - - options[key] = value - - # The `writeCreases` argument was changed to `autoSubd` in Maya 2018+ - maya_version = int(cmds.about(version=True)) - if maya_version >= 2018: - options['autoSubd'] = options.pop('writeCreases', False) - - # Format the job string from options - job_args = list() - for key, value in options.items(): - if isinstance(value, (list, tuple)): - for entry in value: - job_args.append("-{} {}".format(key, entry)) - elif isinstance(value, bool): - # Add only when state is set to True - if value: - job_args.append("-{0}".format(key)) - else: - job_args.append("-{0} {1}".format(key, value)) - - job_str = " ".join(job_args) - job_str += ' -file "%s"' % file - - # Ensure output directory exists - parent_dir = os.path.dirname(file) - if not os.path.exists(parent_dir): - os.makedirs(parent_dir) - - if verbose: - log.debug("Preparing Alembic export with options: %s", - json.dumps(options, indent=4)) - log.debug("Extracting Alembic with job arguments: %s", job_str) - - # Perform extraction - print("Alembic Job Arguments : {}".format(job_str)) - - # Disable the parallel evaluation temporarily to ensure no buggy - # exports are made. (PLN-31) - # TODO: Make sure this actually fixes the issues - with evaluation("off"): - cmds.AbcExport(j=job_str, verbose=verbose) - - if verbose: - log.debug("Extracted Alembic to: %s", file) - - return file - - # region ID def get_id_required_nodes(referenced_nodes=False, nodes=None): """Filter out any node which are locked (reference) or readOnly diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 0cc802fa7a..8ee723098c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -3,8 +3,8 @@ import os from maya import cmds from openpype.pipeline import publish +from openpype.hosts.maya.api.alembic import extract_alembic from openpype.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection, iter_visible_nodes_in_range @@ -45,6 +45,7 @@ class ExtractAlembic(publish.Extractor): attr_prefixes = instance.data.get("attrPrefix", "").split(";") attr_prefixes = [value for value in attr_prefixes if value.strip()] + self.log.debug("Extracting pointcache..") dirname = self.staging_dir(instance) diff --git a/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py b/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py index d9bec87cfd..b54c91f05a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py +++ b/openpype/hosts/maya/plugins/publish/extract_proxy_abc.py @@ -3,8 +3,8 @@ import os from maya import cmds from openpype.pipeline import publish +from openpype.hosts.maya.api.alembic import extract_alembic from openpype.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection, iter_visible_nodes_in_range diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py index 9c2f55a1ef..af82c24683 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -6,8 +6,8 @@ from contextlib import contextmanager from maya import cmds # noqa from openpype.pipeline import publish +from openpype.hosts.maya.api.alembic import extract_alembic from openpype.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection ) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index 4bd01c2df2..bfcf65c652 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -5,7 +5,7 @@ import copy from maya import cmds import pyblish.api -from openpype.hosts.maya.api.lib import extract_alembic +from openpype.hosts.maya.api.alembic import extract_alembic from openpype.pipeline import publish from openpype.lib import StringTemplate From 38a3ea45e1cc52039f7c5cd555daf6eb98038476 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 14 Nov 2023 18:50:50 +0000 Subject: [PATCH 029/244] `maya.api.alembic` Alphabetically order arguments --- openpype/hosts/maya/api/alembic.py | 113 +++++++++++++++-------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py index f5684256cc..75dad62ae1 100644 --- a/openpype/hosts/maya/api/alembic.py +++ b/openpype/hosts/maya/api/alembic.py @@ -1,63 +1,69 @@ # The maya alembic export types -_alembic_options = { - "startFrame": float, - "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame - "eulerFilter": bool, - "frameRelativeSample": float, - "noNormals": bool, - "renderableOnly": bool, - "step": float, - "stripNamespaces": bool, - "verbose": bool, - "uvWrite": bool, - "wholeFrameGeo": bool, - "worldSpace": bool, - "writeVisibility": bool, - "writeColorSets": bool, - "writeFaceSets": bool, - "writeCreases": bool, # Maya 2015 Ext1+ - "writeUVSets": bool, # Maya 2017+ - "dataFormat": str, - "root": (list, tuple), +ALEMBIC_ARGS = { "attr": (list, tuple), "attrPrefix": (list, tuple), - "userAttr": (list, tuple), + "autoSubd": bool, + "dataFormat": str, + "dontSkipUnwrittenFrames": bool, + "endFrame": float, + "eulerFilter": bool, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "frameRelativeSample": float, "melPerFrameCallback": str, "melPostJobCallback": str, + "noNormals": bool, + "preRoll": bool, + "preRollStartFrame": int, "pythonPerFrameCallback": str, "pythonPostJobCallback": str, + "renderableOnly": bool, + "root": (list, tuple), "selection": bool, - "preRoll": bool, - "preRollStartFrame": int + "startFrame": float, + "step": float, + "stripNamespaces": bool, + "userAttr": (list, tuple), + "userAttrPrefix": (list, tuple), + "uvWrite": bool, + "uvsOnly": bool, + "verbose": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeColorSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "writeFaceSets": bool, + "writeUVSets": bool, # Maya 2017+ + "writeVisibility": bool, } -def extract_alembic(file, - startFrame=None, - endFrame=None, - frameRange="", - eulerFilter=True, - noNormals=False, - preRoll=False, - renderableOnly=False, - selection=True, - uvWrite=True, - writeColorSets=False, - writeFaceSets=False, - wholeFrameGeo=False, - worldSpace=False, - writeVisibility=False, - writeUVSets=False, - writeCreases=False, - dataFormat="ogawa", - step=1.0, - attr=None, - attrPrefix=None, - root=None, - stripNamespaces=True, - verbose=False, - preRollStartFrame=0): +def extract_alembic( + file, + attr=None, + attrPrefix=None, + dataFormat="ogawa", + endFrame=None, + eulerFilter=True, + frameRange="", + noNormals=False, + preRoll=False, + preRollStartFrame=0, + renderableOnly=False, + root=None, + selection=True, + startFrame=None, + step=1.0, + stripNamespaces=True, + uvWrite=True, + verbose=False, + wholeFrameGeo=False, + worldSpace=False, + writeColorSets=False, + writeCreases=False, + writeFaceSets=False, + writeUVSets=False, + writeVisibility=False +): """Extract a single Alembic Cache. This extracts an Alembic cache using the `-selection` flag to minimize @@ -181,8 +187,8 @@ def extract_alembic(file, animationEndTime=True) # Ensure valid types are converted to frame range - assert isinstance(startFrame, _alembic_options["startFrame"]) - assert isinstance(endFrame, _alembic_options["endFrame"]) + assert isinstance(startFrame, ALEMBIC_ARGS["startFrame"]) + assert isinstance(endFrame, ALEMBIC_ARGS["endFrame"]) frameRange = "{0} {1}".format(startFrame, endFrame) else: # Allow conversion from tuple for `frameRange` @@ -198,7 +204,6 @@ def extract_alembic(file, "noNormals": noNormals, "preRoll": preRoll, "renderableOnly": renderableOnly, - "selection": selection, "uvWrite": uvWrite, "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, @@ -220,14 +225,14 @@ def extract_alembic(file, for key, value in options.copy().items(): # Discard unknown options - if key not in _alembic_options: + if key not in ALEMBIC_ARGS: log.warning("extract_alembic() does not support option '%s'. " "Flag will be ignored..", key) options.pop(key) continue # Validate value type - valid_types = _alembic_options[key] + valid_types = ALEMBIC_ARGS[key] if not isinstance(value, valid_types): raise TypeError("Alembic option unsupported type: " "{0} (expected {1})".format(value, valid_types)) From 6363064f927356b0ae21b803577bff5bd3977136 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 14 Nov 2023 18:52:51 +0000 Subject: [PATCH 030/244] `maya` Rename Alemebic settings, update `pointcache` creator The previous naming was a bit confusing, and now all the possible arguments are made available to the overrides setting, and all the booleans ones added to the corresponding key. --- .../create/create_animation_pointcache.py | 129 ++++++------------ .../defaults/project_settings/maya.json | 18 +-- .../schemas/schema_maya_create.json | 112 ++++++++++----- 3 files changed, 125 insertions(+), 134 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 335d054ab3..a497ace496 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,8 +1,7 @@ from maya import cmds -from pprint import pprint - from openpype.hosts.maya.api import lib, plugin +from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS from openpype.lib import ( BoolDef, TextDef, @@ -35,129 +34,83 @@ def _get_animation_abc_attr_defs(cls): # List of arguments extracted from AbcExport -h # Them being here doesn't imply we support them or that we need them at this # point, it's a convininece list to populate the UI defaults. - alembic_attributes = [ - "preRollStartFrame", - "dontSkipUnwrittenFrames", - "verbose", - "attr", - "autoSubd", - "attrPrefix", - "dataFormat", - "eulerFilter", - "frameRange", - "frameRelativeSample", - "noNormals", - "preRoll", - "renderableOnly", - "root", - "step", - "selection", - "stripNamespaces", - "userAttr", - "userAttrPrefix", - "uvWrite", - "uvsOnly", - "writeColorSets", - "writeFaceSets", - "wholeFrameGeo", - "worldSpace", - "writeVisibility", - "writeUVSets", - "melPerFrameCallback", - "melPostJobCallback", - "pythonPerFrameCallback", - "pythonPostJobCallback", - ] abc_defs = [UISeparatorDef(), UILabelDef("Alembic Options")] - alembic_editable_attributes = getattr(cls, "abc_editable_flags", None) + # The Arguments that can be modified by the Publisher + abc_args_overrides = getattr(cls, "abc_args_overrides", None) - if not alembic_editable_attributes: - print("No Almbic attributes found in settings.") - return None + # What we have set in the Settings as default. + abc_boolean_args = getattr(cls, "abc_boolean_args", []) - print("Processing editable Alembic attributes...") + # Default Flags set in Settings; unless they are editable. + abc_settings_boolean_arguments = [ + arg + for arg in abc_boolean_args + if arg not in abc_args_overrides + ] + # We display them to the user + abc_defs.append(EnumDef( + "abcDefaultExportBooleanArguments", + abc_settings_boolean_arguments, + default=abc_settings_boolean_arguments, + multiselection=True, + label="Settings set Arguments", + disabled=True + )) - abc_boolean_defs = [ - "writeColorSets", - "writeFaceSets", - "renderableOnly", - "visibleOnly", - "worldSpace", - "noNormals", - "includeUserDefinedAttributes", - "eulerFilter", - "preRoll", - "stripNamespaces", - "uvWrite", - "verbose", - "wholeFrameGeo", - "writeUVSets", - "writeVisibility", + abc_boolean_overrides = [ + arg + for arg in abc_args_overrides + if arg in abc_boolean_args ] - abc_boolean_defaults = [ - "uvWrite", - "worldSpace", - "writeVisibility", - ] - - enabled_boolean_attributes = [ - attrib for attrib in abc_boolean_defs if attrib in alembic_editable_attributes - ] - - if enabled_boolean_attributes: - abc_defs.extend( - [ - EnumDef( - "abcExportFlags", - enabled_boolean_attributes, - default=abc_boolean_defaults, - multiselection=True, - label="Alembic Export Flags", - ) - ] - ) + if abc_boolean_overrides: + abc_defs.append(EnumDef( + "abcExportBooleanArguments", + abc_boolean_overrides, + multiselection=True, + label="Arguments Overrides" + )) abc_defs.append( TextDef( "attr", - label="Alembic Custom Attributes", - placeholder="attr1, attr2", - disabled=True if "attr" not in alembic_editable_attributes else False, + label="Custom Attributes", + placeholder="attr1, attr2, ...", + disabled=True if "attr" not in abc_args_overrides else False, ) ) abc_defs.append( TextDef( "attrPrefix", - label="Alembic Custom Attributes Prefix", - placeholder="prefix1, prefix2", - disabled=True if "attrPrefix" not in alembic_editable_attributes else False, + label="Custom Attributes Prefix", + placeholder="prefix1, prefix2, ...", + disabled=True if "attrPrefix" not in abc_args_overrides else False, ) ) abc_defs.append( EnumDef( "dataFormat", - label="Alembic Data Format", + label="Data Format", items=["ogawa", "HDF"], - disabled=True if "dataFormat" not in alembic_editable_attributes else False, + disabled=True if "dataFormat" not in abc_args_overrides else False, ) ) abc_defs.append( NumberDef( "preRollStartFrame", - label="Start frame for preroll (Alembic)", + label="Start frame for preroll", tooltip=( "The frame to start scene evaluation at. This is used to set" " the starting frame for time dependent translations and can" " be used to evaluate run-up that isn't actually translated." ), disabled=True - if "preRollStartFrame" not in alembic_editable_attributes + if "preRollStartFrame" not in abc_args_overrides else False, ) ) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index aaa7afdf45..4f6cf68daa 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -559,16 +559,13 @@ "CreateAnimation": { "default_variants": [], "step": 1.0, - "abc_flags": [ + "abc_boolean_args": [ "writeColorSets", "visibleOnly", "worldSpace", - "writeNormals", - "includeUserDefinedAttributes", - "attr", - "attrPrefix" + "writeNormals" ], - "abc_editable_flags": [ + "abc_args_overrides": [ "step", "includeParentHierarchy", "writeNormals", @@ -608,16 +605,13 @@ "Main" ], "step": 1.0, - "abc_flags": [ + "abc_boolean_args": [ "writeColorSets", "visibleOnly", "worldSpace", - "writeNormals", - "includeUserDefinedAttributes", - "attr", - "attrPrefix" + "writeNormals" ], - "abc_editable_flags": [ + "abc_args_overrides": [ "step", "writeColorSets", "writeFaceSets", 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 c5bfb18e00..3317fe7d71 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 @@ -175,46 +175,68 @@ }, { "type": "enum", - "key": "abc_flags", + "key": "abc_boolean_args", "multiselection": true, - "label": "Alembic Flags", + "label": "Alembic Boolean Args", "enum_items": [ - {"writeColorSets": "writeColorSets"}, - {"writeFaceSets": "writeFaceSets"}, - {"renderableOnly": "renderableOnly"}, - {"visibleOnly": "visibleOnly"}, - {"worldSpace": "worldSpace"}, - {"noNormals": "noNormals"}, + {"autoSubd": "autoSubd"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, {"eulerFilter": "eulerFilter"}, + {"noNormals": "noNormals"}, {"preRoll": "preRoll"}, + {"renderableOnly": "renderableOnly"}, + {"selection": "selection"}, + {"stripNamespaces": "stripNamespaces"}, {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, {"verbose": "verbose"}, {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeCreases": "writeCreases"}, + {"writeFaceSets": "writeFaceSets"}, {"writeUVSets": "writeUVSets"}, {"writeVisibility": "writeVisibility"} ] }, { "type": "enum", - "key": "abc_editable_flags", + "key": "abc_args_overrides", "multiselection": true, - "label": "Alembic Editable Flags", + "label": "Alembic Arguments Overrides: Choose which arguments the user can modify.", "enum_items": [ - {"step": "step"}, - {"writeColorSets": "writeColorSets"}, - {"writeFaceSets": "writeFaceSets"}, - {"renderableOnly": "renderableOnly"}, - {"visibleOnly": "visibleOnly"}, - {"worldSpace": "worldSpace"}, - {"noNormals": "noNormals"}, {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, + {"autoSubd": "autoSubd"}, + {"dataFormat": "dataFormat"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, + {"endFrame": "endFrame"}, {"eulerFilter": "eulerFilter"}, + {"frameRange": "frameRange"}, + {"frameRelativeSample": "frameRelativeSample"}, + {"melPerFrameCallback": "melPerFrameCallback"}, + {"melPostJobCallback": "melPostJobCallback"}, + {"noNormals": "noNormals"}, {"preRoll": "preRoll"}, {"preRollStartFrame": "preRollStartFrame"}, + {"pythonPerFrameCallback": "pythonPerFrameCallback"}, + {"pythonPostJobCallback": "pythonPostJobCallback"}, + {"renderableOnly": "renderableOnly"}, + {"root": "root"}, + {"selection": "selection"}, + {"startFrame": "startFrame"}, + {"step": "step"}, + {"stripNamespaces": "stripNamespaces"}, + {"userAttr": "userAttr"}, + {"userAttrPrefix": "userAttrPrefix"}, {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, {"verbose": "verbose"}, {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeCreases": "writeCreases"}, + {"writeFaceSets": "writeFaceSets"}, {"writeUVSets": "writeUVSets"}, {"writeVisibility": "writeVisibility"} ] @@ -327,46 +349,68 @@ }, { "type": "enum", - "key": "abc_flags", + "key": "abc_boolean_args", "multiselection": true, - "label": "Alembic Flags", + "label": "Alembic Arguments (passed to AbcExport)", "enum_items": [ - {"writeColorSets": "writeColorSets"}, - {"writeFaceSets": "writeFaceSets"}, - {"renderableOnly": "renderableOnly"}, - {"visibleOnly": "visibleOnly"}, - {"worldSpace": "worldSpace"}, - {"noNormals": "noNormals"}, + {"autoSubd": "autoSubd"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, {"eulerFilter": "eulerFilter"}, + {"noNormals": "noNormals"}, {"preRoll": "preRoll"}, + {"renderableOnly": "renderableOnly"}, + {"selection": "selection"}, + {"stripNamespaces": "stripNamespaces"}, {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, {"verbose": "verbose"}, {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeCreases": "writeCreases"}, + {"writeFaceSets": "writeFaceSets"}, {"writeUVSets": "writeUVSets"}, {"writeVisibility": "writeVisibility"} ] }, { "type": "enum", - "key": "abc_editable_flags", + "key": "abc_args_overrides", "multiselection": true, - "label": "Alembic Editable Flags", + "label": "Alembic Arguments Overrides: Choose which arguments the user can modify.", "enum_items": [ - {"step": "step"}, - {"writeColorSets": "writeColorSets"}, - {"writeFaceSets": "writeFaceSets"}, - {"renderableOnly": "renderableOnly"}, - {"visibleOnly": "visibleOnly"}, - {"worldSpace": "worldSpace"}, - {"noNormals": "noNormals"}, {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, + {"autoSubd": "autoSubd"}, + {"dataFormat": "dataFormat"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, + {"endFrame": "endFrame"}, {"eulerFilter": "eulerFilter"}, + {"frameRange": "frameRange"}, + {"frameRelativeSample": "frameRelativeSample"}, + {"melPerFrameCallback": "melPerFrameCallback"}, + {"melPostJobCallback": "melPostJobCallback"}, + {"noNormals": "noNormals"}, {"preRoll": "preRoll"}, {"preRollStartFrame": "preRollStartFrame"}, + {"pythonPerFrameCallback": "pythonPerFrameCallback"}, + {"pythonPostJobCallback": "pythonPostJobCallback"}, + {"renderableOnly": "renderableOnly"}, + {"root": "root"}, + {"selection": "selection"}, + {"startFrame": "startFrame"}, + {"step": "step"}, + {"stripNamespaces": "stripNamespaces"}, + {"userAttr": "userAttr"}, + {"userAttrPrefix": "userAttrPrefix"}, {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, {"verbose": "verbose"}, {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeCreases": "writeCreases"}, + {"writeFaceSets": "writeFaceSets"}, {"writeUVSets": "writeUVSets"}, {"writeVisibility": "writeVisibility"} ] From 3a14c8ebc813039ed32bb5acdcd8a7641ccc8cbf Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 14 Nov 2023 18:54:49 +0000 Subject: [PATCH 031/244] `maya.publish.pointcache` Use the `api.alembic` instead of `lib` --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 8ee723098c..8780a1e8b3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -3,7 +3,7 @@ import os from maya import cmds from openpype.pipeline import publish -from openpype.hosts.maya.api.alembic import extract_alembic +from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS, extract_alembic from openpype.hosts.maya.api.lib import ( suspended_refresh, maintained_selection, From 33784f765c73502ff8be34671252bdbf7f143467 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 15 Nov 2023 14:21:10 +0000 Subject: [PATCH 032/244] `maya.api.alembic` Fix missing `cmds` import --- openpype/hosts/maya/api/alembic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py index 75dad62ae1..0fde1cdfb5 100644 --- a/openpype/hosts/maya/api/alembic.py +++ b/openpype/hosts/maya/api/alembic.py @@ -1,3 +1,5 @@ +from maya import cmds # noqa + # The maya alembic export types ALEMBIC_ARGS = { "attr": (list, tuple), From fca2442a8b055848bdfb056b8f306db6775683fa Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 15 Nov 2023 14:22:33 +0000 Subject: [PATCH 033/244] `maya.plugin.pointcache` Rework settings names Improve settings names, remove unnecessary ones and fix the `create` plugin to use the `maya.api.alembic` to find out boolean arguments. --- .../create/create_animation_pointcache.py | 71 +++++++++++++------ .../defaults/project_settings/maya.json | 25 +++---- .../schemas/schema_maya_create.json | 40 ----------- 3 files changed, 57 insertions(+), 79 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index a497ace496..01bb95f222 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -30,54 +30,75 @@ def _get_animation_attr_defs(cls): def _get_animation_abc_attr_defs(cls): - """Get definitions relating to Alembic.""" - # List of arguments extracted from AbcExport -h - # Them being here doesn't imply we support them or that we need them at this - # point, it's a convininece list to populate the UI defaults. + """Get definitions relating to Alembic. + Most of the Alembic Arguments are booleans, those are stored in a + `abc_boolean_args` attribute, the other ones are their own attriubte. + + An admin can define in settings the default arguments, which are then not + modifiable by the person publishing, unless they are added to the Alembic + Overrides setting, which is mapped to `abc_args_overrides`. + + We use a combination of the two above to only show a muiltiselection dropdown + for booleans, and disabling the non-boolean arguments on the interface. + + There's also a new separator so it's clearer what belongs to common Animation + publishes versus what is Almebic specific. + """ + abc_defs = None abc_defs = [UISeparatorDef(), UILabelDef("Alembic Options")] # The Arguments that can be modified by the Publisher abc_args_overrides = getattr(cls, "abc_args_overrides", None) - # What we have set in the Settings as default. - abc_boolean_args = getattr(cls, "abc_boolean_args", []) + # All the Boolean Arguments that Alembic Export accepts + abc_boolean_args = [ + arg + for arg, arg_type in ALEMBIC_ARGS.items() + if arg_type is bool + ] - # Default Flags set in Settings; unless they are editable. + # What we have set in the Settings as defaults. + abc_settings_boolean_args = getattr(cls, "abc_boolean_args", []) + + # Default Flags set in Settings; minus the overrideable ones. abc_settings_boolean_arguments = [ arg - for arg in abc_boolean_args + for arg in abc_settings_boolean_args if arg not in abc_args_overrides ] - # We display them to the user + + # We display them to the user, but disable it abc_defs.append(EnumDef( "abcDefaultExportBooleanArguments", abc_settings_boolean_arguments, default=abc_settings_boolean_arguments, multiselection=True, - label="Settings set Arguments", - disabled=True + label="Settings Defined Arguments", + disabled=False )) + # Only display Boolan flags that the Admin defined as overrideable abc_boolean_overrides = [ arg - for arg in abc_args_overrides - if arg in abc_boolean_args + for arg in abc_boolean_args + if arg in abc_args_overrides ] - if abc_boolean_overrides: - abc_defs.append(EnumDef( - "abcExportBooleanArguments", - abc_boolean_overrides, - multiselection=True, - label="Arguments Overrides" - )) + abc_defs.append(EnumDef( + "abcExportBooleanArguments", + abc_boolean_overrides if abc_boolean_overrides else [""], + multiselection=True, + label="Arguments Overrides", + disabled=True if not abc_boolean_overrides else False + )) abc_defs.append( TextDef( "attr", label="Custom Attributes", - placeholder="attr1, attr2, ...", + default=getattr(cls, "attr", None), + placeholder="attr1; attr2; ...", disabled=True if "attr" not in abc_args_overrides else False, ) ) @@ -86,7 +107,8 @@ def _get_animation_abc_attr_defs(cls): TextDef( "attrPrefix", label="Custom Attributes Prefix", - placeholder="prefix1, prefix2, ...", + default=getattr(cls, "attrPrefix", None), + placeholder="prefix1; prefix2; ...", disabled=True if "attrPrefix" not in abc_args_overrides else False, ) ) @@ -95,6 +117,7 @@ def _get_animation_abc_attr_defs(cls): EnumDef( "dataFormat", label="Data Format", + default=getattr(cls, "dataFormat", None), items=["ogawa", "HDF"], disabled=True if "dataFormat" not in abc_args_overrides else False, ) @@ -104,6 +127,7 @@ def _get_animation_abc_attr_defs(cls): NumberDef( "preRollStartFrame", label="Start frame for preroll", + default=getattr(cls, "preRollStartFrame", None), tooltip=( "The frame to start scene evaluation at. This is used to set" " the starting frame for time dependent translations and can" @@ -137,6 +161,9 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_parent_hierarchy = False include_user_defined_attributes = False + def collect_instances(self): + pass + def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() # adding project settings, since MayaHiddenCreator does not diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 4f6cf68daa..54721757e7 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -606,40 +606,31 @@ ], "step": 1.0, "abc_boolean_args": [ - "writeColorSets", - "visibleOnly", - "worldSpace", - "writeNormals" + "selection", + "uvWrite", + "writeCreases", + "writeVisibility" ], "abc_args_overrides": [ + "attr", + "attrPrefix", "step", "writeColorSets", "writeFaceSets", "renderableOnly", - "visibleOnly", - "worldSpace", - "attr", - "attrPrefix" + "worldSpace" ], - "writeColorSets": true, - "writeFaceSets": false, "renderableOnly": false, "visibleOnly": false, "includeParentHierarchy": false, - "worldSpace": true, "farm": false, "priority": 50, "writeNormals": true, - "includeUserDefinedAttributes": true, - "attr": "", + "attr": "cbId", "attrPrefix": "", "dataFormat": "ogawa", - "noNormals": false, "preRollStartFrame": 0, "refresh": false, - "stripNamespaces": false, - "uvWrite": true, - "writeCreases": false, "write_color_sets": false, "write_face_sets": false, "include_user_defined_attributes": false 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 3317fe7d71..9712569190 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 @@ -422,16 +422,6 @@ "minimum": 0.0, "decimal": 4 }, - { - "type": "boolean", - "key": "writeColorSets", - "label": "Write Color Sets default" - }, - { - "type": "boolean", - "key": "writeFaceSets", - "label": "Write Face Sets default" - }, { "type": "boolean", "key": "renderableOnly", @@ -447,11 +437,6 @@ "key": "includeParentHierarchy", "label": "Include Parent Hierarchy default" }, - { - "type": "boolean", - "key": "worldSpace", - "label": "World Space default" - }, { "type": "boolean", "key": "farm", @@ -468,11 +453,6 @@ "key": "writeNormals", "label": "Write Normals default" }, - { - "type": "boolean", - "key": "includeUserDefinedAttributes", - "label": "Include User Defined Attributes default" - }, { "type": "text", "key": "attr", @@ -496,11 +476,6 @@ } ] }, - { - "type": "boolean", - "key": "noNormals", - "label": "No Normals default" - }, { "type": "number", "key": "preRollStartFrame", @@ -512,21 +487,6 @@ "key": "refresh", "label": "Refresh default" }, - { - "type": "boolean", - "key": "stripNamespaces", - "label": "Strip Namespaces default" - }, - { - "type": "boolean", - "key": "uvWrite", - "label": "UV Write default" - }, - { - "type": "boolean", - "key": "writeCreases", - "label": "Write Creases default" - }, { "type": "boolean", "key": "write_color_sets", From 608c7ebcdcf55dad737c26586e12224e631d0512 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 15 Nov 2023 14:24:26 +0000 Subject: [PATCH 034/244] `maya.extract_pointcache` Adapt to new settings Adapted the extractor to get the settings from the new schema, and simplified the way we build the `alembic_export` arguments dict. --- .../plugins/publish/extract_pointcache.py | 86 +++++++++++-------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 8780a1e8b3..96b1438511 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -37,14 +37,23 @@ class ExtractAlembic(publish.Extractor): start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - attrs = instance.data.get("attr", "").split(";") - attrs = [value for value in attrs if value.strip()] - attrs += instance.data.get("userDefinedAttributes", []) - attrs += ["cbId"] + # Collect Alembic Arguments + creator_attributes = instance.data.get("creator_attributes") + abc_flags = creator_attributes.get( + "abcDefaultExportBooleanArguments" + ) + creator_attributes.get( + "abcExportBooleanArguments" + ) - attr_prefixes = instance.data.get("attrPrefix", "").split(";") - attr_prefixes = [value for value in attr_prefixes if value.strip()] + abc_attrs = [ + attr.strip() + for attr in creator_attributes.get("attr", "").split(";") + ] + abc_attr_prefixes = [ + attr_prefix.strip() + for attr_prefix in instance.data.get("attrPrefix", "").split(";") + ] self.log.debug("Extracting pointcache..") dirname = self.staging_dir(instance) @@ -53,28 +62,46 @@ class ExtractAlembic(publish.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) - options = { - "step": instance.data.get("step", 1.0), - "attr": attrs, - "attrPrefix": attr_prefixes, - "writeVisibility": True, - "writeCreases": True, - "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False), - "uvWrite": True, - "selection": True, - "worldSpace": instance.data.get("worldSpace", True) - } - + abc_root = None if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - options["root"] = roots + abc_root = roots + + abc_writeUVSets = False if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. - options["writeUVSets"] = True + if "writeUVSets" in abc_flags: + abc_writeUVSets = True + + extract_abc_args = { + "file": path, + "attr": abc_attrs, + "attrPrefix": abc_attr_prefixes, + "dataFormat": creator_attributes.get("dataFormat", "ogawa"), + "endFrame": end, + "eulerFilter": True if "eulerFilter" in abc_flags else False, + "noNormals": True if "noNormals" in abc_flags else False, + "preRoll": True if "preRoll" in abc_flags else False, + "preRollStartFrame": creator_attributes.get("preRollStartFrame", 0), + "renderableOnly": True if "renderableOnly" in abc_flags else False, + "root": abc_root, + "selection": True, # Should this stay like so? + "startFrame": start, + "step": creator_attributes.get("step", 1.0), + "stripNamespaces": True, + "uvWrite": True if "uvWrite" in abc_flags else False, + "verbose": True if "verbose" in abc_flags else False, + "wholeFrameGeo": True if "wholeFrameGeo" in abc_flags else False, + "worldSpace": True if "worldSpace" in abc_flags else False, + "writeColorSets": True if "writeColorSets" in abc_flags else False, + "writeCreases": True if "writeCreases" in abc_flags else False, + "writeFaceSets": True if "writeFaceSets" in abc_flags else False, + "writeUVSets": abc_writeUVSets, + "writeVisibility": True if "writeVisibility" in abc_flags else False, + } if instance.data.get("visibleOnly", False): # If we only want to include nodes that are visible in the frame @@ -90,12 +117,7 @@ class ExtractAlembic(publish.Extractor): with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) - extract_alembic( - file=path, - startFrame=start, - endFrame=end, - **options - ) + extract_alembic(**extract_abc_args) if "representations" not in instance.data: instance.data["representations"] = [] @@ -119,21 +141,17 @@ class ExtractAlembic(publish.Extractor): return path = path.replace(".abc", "_proxy.abc") + extract_abc_args["file"] = path if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - options["root"] = instance.data["proxyRoots"] + extract_abc_args["root"] = instance.data["proxyRoots"] with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(instance.data["proxy"]) - extract_alembic( - file=path, - startFrame=start, - endFrame=end, - **options - ) + extract_alembic(**extract_abc_args) representation = { "name": "proxy", From 70e48831501485b371e35bddd24a4dd1a7065a6e Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 15 Nov 2023 18:18:00 +0000 Subject: [PATCH 035/244] `maya.api.alembic` Fix missing imports --- openpype/hosts/maya/api/alembic.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py index 0fde1cdfb5..f4bdc18bfa 100644 --- a/openpype/hosts/maya/api/alembic.py +++ b/openpype/hosts/maya/api/alembic.py @@ -1,5 +1,12 @@ +import os +import logging + from maya import cmds # noqa +from openpype.hosts.maya.api.lib import evaluation + +log = logging.getLogger(__name__) + # The maya alembic export types ALEMBIC_ARGS = { "attr": (list, tuple), From c379e7f4c4def35fca74a0975df96958f67106a9 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 15 Nov 2023 18:20:33 +0000 Subject: [PATCH 036/244] `maya.create_animation_pointcache` Fix publisher widgets population Fix widgets not being properly drawn for each of the Creators. Also removed unnecessary settings, that are now handled by the EnumDef. --- .../create/create_animation_pointcache.py | 1 - .../defaults/project_settings/maya.json | 9 +--- .../schemas/schema_maya_create.json | 43 ++----------------- 3 files changed, 5 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 01bb95f222..90c89090c6 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -22,7 +22,6 @@ def _get_animation_attr_defs(cls): BoolDef("refresh", label="Refresh viewport during export"), BoolDef("includeParentHierarchy", label="Include Parent Hierarchy"), BoolDef("writeNormals", label="Write Normals"), - BoolDef("writeCreases", label="Write Creases"), ] ) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 54721757e7..fc38070573 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -576,18 +576,12 @@ "includeParentHierarchy": false, "farm": false, "priority": 50, - "writeNormals": true, "attr": "", "attrPrefix": "", "dataFormat": "ogawa", "preRollStartFrame": 0, "refresh": false, - "stripNamespaces": false, - "writeCreases": false, - "write_color_sets": false, - "write_face_sets": false, - "include_parent_hierarchy": false, - "include_user_defined_attributes": false + "stripNamespaces": false }, "CreateModel": { "enabled": true, @@ -625,7 +619,6 @@ "includeParentHierarchy": false, "farm": false, "priority": 50, - "writeNormals": true, "attr": "cbId", "attrPrefix": "", "dataFormat": "ogawa", 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 9712569190..f30120db8c 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 @@ -153,11 +153,6 @@ "label": "Farm Job Priority", "minimum": 0 }, - { - "type": "boolean", - "key": "writeNormals", - "label": "Write Normals" - }, { "type": "boolean", "key": "refresh", @@ -168,11 +163,6 @@ "key": "stripNamespaces", "label": "Strip Namespaces" }, - { - "type": "boolean", - "key": "writeCreases", - "label": "Write Creases" - }, { "type": "enum", "key": "abc_boolean_args", @@ -203,7 +193,7 @@ "type": "enum", "key": "abc_args_overrides", "multiselection": true, - "label": "Alembic Arguments Overrides: Choose which arguments the user can modify.", + "label": "Alembic Arguments Overrides:\nChoose which arguments\nthe user can modify.", "enum_items": [ {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, @@ -251,7 +241,7 @@ { "type": "text", "key": "attr", - "label": "Attr" + "label": "Attributes" }, { "type": "text", @@ -276,26 +266,6 @@ "key": "preRollStartFrame", "label": "Pre Roll Start Frame", "minimum": 0 - }, - { - "type": "boolean", - "key": "write_color_sets", - "label": "DEPRECATED! Write Color Sets" - }, - { - "type": "boolean", - "key": "write_face_sets", - "label": "DEPRECATED! Write Face Sets" - }, - { - "type": "boolean", - "key": "include_parent_hierarchy", - "label": "DEPRECATED! Include Parent Hierarchy" - }, - { - "type": "boolean", - "key": "include_user_defined_attributes", - "label": "DEPRECATED! Include User Defined Attributes" } ] }, @@ -351,7 +321,7 @@ "type": "enum", "key": "abc_boolean_args", "multiselection": true, - "label": "Alembic Arguments (passed to AbcExport)", + "label": "Alembic Boolean Args", "enum_items": [ {"autoSubd": "autoSubd"}, {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, @@ -377,7 +347,7 @@ "type": "enum", "key": "abc_args_overrides", "multiselection": true, - "label": "Alembic Arguments Overrides: Choose which arguments the user can modify.", + "label": "Alembic Arguments Overrides:\nChoose which arguments\nthe user can modify.", "enum_items": [ {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, @@ -448,11 +418,6 @@ "label": "Priority default", "minimum": 0 }, - { - "type": "boolean", - "key": "writeNormals", - "label": "Write Normals default" - }, { "type": "text", "key": "attr", From e700f2878d543ea2564a578e8260374ebf951790 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 20 Nov 2023 17:34:55 +0000 Subject: [PATCH 037/244] `maya.publish.pointcache` Log out the Alembic argumetns used. --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 96b1438511..7eb69da151 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -55,7 +55,7 @@ class ExtractAlembic(publish.Extractor): for attr_prefix in instance.data.get("attrPrefix", "").split(";") ] - self.log.debug("Extracting pointcache..") + self.log.debug("Extracting pointcache...") dirname = self.staging_dir(instance) parent_dir = self.staging_dir(instance) @@ -117,6 +117,7 @@ class ExtractAlembic(publish.Extractor): with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) + self.log.debug("Running `extract_alembic` with the arguments: {}".format(extract_abc_args)) extract_alembic(**extract_abc_args) if "representations" not in instance.data: From 5935ef5947fe6b39c7b2d2c2fbb9746abe1dc959 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 20 Nov 2023 17:35:56 +0000 Subject: [PATCH 038/244] `maya.create_animation_pointcache` Ensure we set defaults When attributes change from overridable to not overrideable, we need to discard any value that was set previously by the user and ensure we one feed the defaults to the extractor. --- .../create/create_animation_pointcache.py | 204 ++++++++++++------ 1 file changed, 134 insertions(+), 70 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 90c89090c6..70f2142a73 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,7 +1,6 @@ from maya import cmds from openpype.hosts.maya.api import lib, plugin -from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS from openpype.lib import ( BoolDef, TextDef, @@ -10,17 +9,25 @@ from openpype.lib import ( UISeparatorDef, UILabelDef, ) +from openpype.pipeline import CreatedInstance def _get_animation_attr_defs(cls): - """Get Animation generic ddefinitions.""" + """Get Animation generic definitions. + + The line is blurry between what's "Animation" generic and "Alembic" is + blurry, but the rule of thumb is that whatever "AlembicExport -h" accepts + is "Alembic" and the other ones are "Animation". + """ defs = lib.collect_animation_defs() defs.extend( [ BoolDef("farm", label="Submit to Farm"), NumberDef("priority", label="Farm job Priority", default=50), BoolDef("refresh", label="Refresh viewport during export"), - BoolDef("includeParentHierarchy", label="Include Parent Hierarchy"), + BoolDef( + "includeParentHierarchy", label="Include Parent Hierarchy" + ), BoolDef("writeNormals", label="Write Normals"), ] ) @@ -28,69 +35,89 @@ def _get_animation_attr_defs(cls): return defs -def _get_animation_abc_attr_defs(cls): - """Get definitions relating to Alembic. +def _get_alembic_boolean_arguments(cls): + """Get two lists with the Alembic flags. - Most of the Alembic Arguments are booleans, those are stored in a - `abc_boolean_args` attribute, the other ones are their own attriubte. - - An admin can define in settings the default arguments, which are then not - modifiable by the person publishing, unless they are added to the Alembic - Overrides setting, which is mapped to `abc_args_overrides`. - - We use a combination of the two above to only show a muiltiselection dropdown - for booleans, and disabling the non-boolean arguments on the interface. - - There's also a new separator so it's clearer what belongs to common Animation - publishes versus what is Almebic specific. + Alembic flags are treted as booleans, so here we get all the possible + options, and work out a list with all the ones that can be toggled and the + list of defaults (un-toggleable.) """ - abc_defs = None - abc_defs = [UISeparatorDef(), UILabelDef("Alembic Options")] # The Arguments that can be modified by the Publisher - abc_args_overrides = getattr(cls, "abc_args_overrides", None) - - # All the Boolean Arguments that Alembic Export accepts - abc_boolean_args = [ - arg - for arg, arg_type in ALEMBIC_ARGS.items() - if arg_type is bool - ] + abc_args_overrides = getattr(cls, "abc_args_overrides", []) # What we have set in the Settings as defaults. abc_settings_boolean_args = getattr(cls, "abc_boolean_args", []) - # Default Flags set in Settings; minus the overrideable ones. - abc_settings_boolean_arguments = [ + abc_defaults = [ arg for arg in abc_settings_boolean_args if arg not in abc_args_overrides ] - # We display them to the user, but disable it - abc_defs.append(EnumDef( - "abcDefaultExportBooleanArguments", - abc_settings_boolean_arguments, - default=abc_settings_boolean_arguments, - multiselection=True, - label="Settings Defined Arguments", - disabled=False - )) - - # Only display Boolan flags that the Admin defined as overrideable - abc_boolean_overrides = [ - arg - for arg in abc_boolean_args - if arg in abc_args_overrides + abc_overrideable = [ + arg for arg in abc_settings_boolean_args if arg in abc_args_overrides ] - abc_defs.append(EnumDef( - "abcExportBooleanArguments", - abc_boolean_overrides if abc_boolean_overrides else [""], - multiselection=True, - label="Arguments Overrides", - disabled=True if not abc_boolean_overrides else False - )) + return abc_defaults, abc_overrideable + + +def _get_animation_abc_attr_defs(cls): + """Get definitions relating to Alembic. + + An admin can define in settings the default arguments, which are then not + modifiable by the person publishing, unless they are added to the Alembic + Overrides setting, which is mapped to `abc_args_overrides`. + + Most of the Alembic Arguments are flags, treated as booleans, and there are + two possible lists: the defaults (from settings) and the the toggleable by + the user, these two define an EnumDef respectively. + + We use a combination of the two above to only show a muiltiselection + dropdown for booleans, and disabling the non-boolean arguments on the + interface. + + There's also a new separator so it's clearer what belongs to common + Animation publishes versus what is Almebic specific, the line is blurry, + but the rule of thumb is that whatever "AlembicExport -h" accepts is + "Alembic" and the other ones are "Animation". + """ + abc_defs = None + abc_defs = [ + UISeparatorDef("sep_alembic_options"), + UILabelDef("Alembic Options"), + ] + + # The Arguments that can be modified by the Publisher + abc_args_overrides = getattr(cls, "abc_args_overrides", None) + + ( + abc_boolean_defaults, + abc_boolean_overrides, + ) = _get_alembic_boolean_arguments(cls) + + # We display them to the user, but disable it + abc_defs.append( + EnumDef( + "abcDefaultExportBooleanArguments", + abc_boolean_defaults, + default=abc_boolean_defaults, + multiselection=True, + label="Settings Defined Arguments", + disabled=True, + ) + ) + + # Only display Boolan flags that the Admin defined as overrideable + abc_defs.append( + EnumDef( + "abcExportBooleanArguments", + abc_boolean_overrides if abc_boolean_overrides else [""], + multiselection=True, + label="Arguments Overrides", + disabled=True if not abc_boolean_overrides else False, + ) + ) abc_defs.append( TextDef( @@ -141,6 +168,46 @@ def _get_animation_abc_attr_defs(cls): return abc_defs +def _ensure_defaults(cls, instance_data): + """Ensure we get default values when an attribute is not overrideable. + + In instances where an attribute used to be modifiable, and then was locked + again, we want to make sure that we pass the default (what's on the + settings) instead of any value that might have been stored in the scene + when the attribute was modifiable. + """ + abc_args_overrides = getattr(cls, "abc_args_overrides", []) + creator_attr = instance_data["creator_attributes"] + attr_default = getattr(cls, "attr", "") + + if "attr" not in abc_args_overrides: + creator_attr["attr"] = attr_default + + if "attrPrefix" not in abc_args_overrides: + creator_attr["attrPrefix"] = getattr(cls, "attrPrefix", "") + + if "dataFormat" not in abc_args_overrides: + creator_attr["dataFormat"] = getattr(cls, "dataFormat", "") + + if "preRollStartFrame" not in abc_args_overrides: + creator_attr["preRollStartFrame"] = getattr( + cls, "preRollStartFrame", "" + ) + + ( + abc_boolean_defaults, + abc_boolean_overrides, + ) = _get_alembic_boolean_arguments(cls) + + creator_attr["abcDefaultExportBooleanArguments"] = abc_boolean_defaults + + for idx, arg in enumerate(creator_attr["abcExportBooleanArguments"]): + if arg not in abc_boolean_overrides: + del creator_attr["abcExportBooleanArguments"][idx] + + return instance_data + + class CreateAnimation(plugin.MayaHiddenCreator): """Animation output for character rigs @@ -161,26 +228,18 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_user_defined_attributes = False def collect_instances(self): - pass + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): + node_data = self.read_instance_node(node) + _ensure_defaults(self, node_data) + + created_instance = CreatedInstance.from_existing(node_data, self) + self._add_instance_to_context(created_instance) def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() - # adding project settings, since MayaHiddenCreator does not - # handle this for us (yet?) - settings = ( - getattr(self, "project_settings", {}) - .get("maya", {}) - .get("create", {}) - .get("CreateAnimation") - ) - - for key, value in settings.items(): - setattr(self, key, value) - defs = _get_animation_attr_defs(self) - abc_defs = _get_animation_abc_attr_defs(self) - if abc_defs: defs.extend(abc_defs) @@ -198,14 +257,19 @@ class CreatePointCache(plugin.MayaCreator): write_face_sets = False include_user_defined_attributes = False + def collect_instances(self): + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): + node_data = self.read_instance_node(node) + _ensure_defaults(self, node_data) + + created_instance = CreatedInstance.from_existing(node_data, self) + self._add_instance_to_context(created_instance) + def get_instance_attr_defs(self): super(CreatePointCache, self).get_instance_attr_defs() - # defs = self.get_instance_attr_defs() - print(self.instance_attr_defs) defs = _get_animation_attr_defs(self) - abc_defs = _get_animation_abc_attr_defs(self) - if abc_defs: defs.extend(abc_defs) From 326c2106cafe5ff9a0617711c5bc6f303b6d83dc Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 21 Nov 2023 17:11:02 +0000 Subject: [PATCH 039/244] `schemas.maya.create` Remove `stripNameSpaces` setting --- .../schemas/projects_schema/schemas/schema_maya_create.json | 5 ----- 1 file changed, 5 deletions(-) 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 f30120db8c..b101a4c0c5 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 @@ -158,11 +158,6 @@ "key": "refresh", "label": "Refresh" }, - { - "type": "boolean", - "key": "stripNamespaces", - "label": "Strip Namespaces" - }, { "type": "enum", "key": "abc_boolean_args", From ca7998e2a107d8316cd7a7a9bbbaee4fa9655ca5 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 21 Nov 2023 17:12:10 +0000 Subject: [PATCH 040/244] `maya.settings` Add AYON settings for Animation and PointCache publishes --- server_addon/maya/server/settings/creators.py | 175 +++++++++++++++++- 1 file changed, 166 insertions(+), 9 deletions(-) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 34a54832af..7719ee469c 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -1,3 +1,5 @@ +from typing import Literal + from pydantic import Field from ayon_server.settings import BaseSettingsModel @@ -56,30 +58,141 @@ class BasicExportMeshModel(BaseSettingsModel): ) +def alembic_booleans_enum(): + return [ + "autoSubd", + "dontSkipUnwritten", + "eulerFilter", + "noNormals", + "preRoll", + "renderableOnly", + "selection", + "stripNamespaces", + "uvWrite", + "uvsOnly", + "verbose", + "wholeFrameGeo", + "worldSpace", + "writeColorSets", + "writeCreases", + "writeFaceSets", + "writeUVSets", + "writeVisibility", + ] + +def alembic_arguments_enum(): + return [ + "attr", + "attrPrefix", + "autoSubd", + "dataFormat", + "dontSkipUnwrittenFrames", + "endFrame", + "eulerFilter", + "frameRange", + "frameRelativeSample", + "melPerFrameCallback", + "melPostJobCallback", + "noNormals", + "preRoll", + "preRollStartFrame", + "pythonPerFrameCallback", + "pythonPostJobCallback", + "renderableOnly", + "root", + "selection", + "startFrame", + "step", + "stripNamespaces", + "userAttr", + "userAttrPrefix", + "uvWrite", + "uvsOnly", + "verbose", + "wholeFrameGeo", + "worldSpace", + "writeColorSets", + "writeCreases", + "writeFaceSets", + "writeUVSets", + "writeVisibility", + ] + +AlembicDataFormat = Literal["ogawa", "hdf5"] + +def alembic_data_formats(): + return [ + "ogawa", + "hdf5" + ] + class CreateAnimationModel(BaseSettingsModel): write_color_sets: bool = Field(title="Write Color Sets") write_face_sets: bool = Field(title="Write Face Sets") include_parent_hierarchy: bool = Field( title="Include Parent Hierarchy") - include_user_defined_attributes: bool = Field( - title="Include User Defined Attributes") default_variants: list[str] = Field( default_factory=list, title="Default Products" ) + priority: int = Field( + title="Farm Job Priority") + pre_roll_start_frame: int = Field(title="Pre Roll Start Frame") + refresh: bool = Field( + title="Refresh") + step: int = Field(title="Step") + farm: bool = Field( + title="Submit to the Farm") + attr: str = Field(title="Attributes") + attr_prefix: str = Field(title="Attributes Prefix") + data_format: AlembicDataFormat = Field( + enum_resolver=alembic_data_formats, + title="Data Format", + ) + abc_boolean_args: list[str] = Field( + default_factory=list, + enum_resolver=alembic_booleans_enum, + title="Alembic Boolean Args", + ) + abc_args_overrides: list[str] = Field( + default_factory=list, + enum_resolver=alembic_arguments_enum, + title="Alembic Arguments Overrides", + ) class CreatePointCacheModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") write_color_sets: bool = Field(title="Write Color Sets") write_face_sets: bool = Field(title="Write Face Sets") - include_user_defined_attributes: bool = Field( - title="Include User Defined Attributes" - ) default_variants: list[str] = Field( default_factory=list, title="Default Products" ) + priority: int = Field( + title="Farm Job Priority") + pre_roll_start_frame: int = Field(title="Pre Roll Start Frame") + refresh: bool = Field( + title="Refresh") + step: int = Field(title="Step") + farm: bool = Field( + title="Submit to the Farm") + attr: str = Field(title="Attributes") + attr_prefix: str = Field(title="Attributes Prefix") + data_format: AlembicDataFormat = Field( + enum_resolver=alembic_data_formats, + title="Data Format", + ) + abc_boolean_args: list[str] = Field( + default_factory=list, + enum_resolver=alembic_booleans_enum, + title="Alembic Boolean Args", + ) + abc_args_overrides: list[str] = Field( + default_factory=list, + enum_resolver=alembic_arguments_enum, + title="Alembic Arguments Overrides", + ) class CreateProxyAlembicModel(BaseSettingsModel): @@ -276,10 +389,31 @@ DEFAULT_CREATORS_SETTINGS = { "write_color_sets": False, "write_face_sets": False, "include_parent_hierarchy": False, - "include_user_defined_attributes": False, "default_variants": [ "Main" - ] + ], + "step": 1.0, + "abc_boolean_args": [ + "writeColorSets", + "visibleOnly", + "worldSpace", + "writeNormals" + ], + "abc_args_overrides": [ + "step", + "includeParentHierarchy", + "writeNormals", + "includeUserDefinedAttributes", + "attr", + "attrPrefix" + ], + "farm": False, + "priority": 50, + "attr": "", + "attr_prefix": "", + "data_format": "ogawa", + "pre_roll_start_frame": 0, + "refresh": False, }, "CreateModel": { "enabled": True, @@ -295,10 +429,33 @@ DEFAULT_CREATORS_SETTINGS = { "enabled": True, "write_color_sets": False, "write_face_sets": False, - "include_user_defined_attributes": False, "default_variants": [ "Main" - ] + ], + "step": 1.0, + "abc_boolean_args": [ + "selection", + "uvWrite", + "writeCreases", + "writeVisibility" + ], + "abc_args_overrides": [ + "attr", + "attrPrefix", + "step", + "writeColorSets", + "writeFaceSets", + "renderableOnly", + "worldSpace" + ], + "include_parent_hierarchy": False, + "farm": False, + "priority": 50, + "attr": "cbId", + "attr_prefix": "", + "data_format": "ogawa", + "pre_roll_start_frame": 0, + "refresh": False }, "CreateProxyAlembic": { "enabled": True, From 0e905f373f34f8767e73575ef2368d1dad035550 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 12 Dec 2023 10:00:30 +0000 Subject: [PATCH 041/244] `maya` Fix faulty logic in pointcache creator --- openpype/hosts/maya/api/alembic.py | 3 ++- .../create/create_animation_pointcache.py | 24 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py index f4bdc18bfa..63f826299f 100644 --- a/openpype/hosts/maya/api/alembic.py +++ b/openpype/hosts/maya/api/alembic.py @@ -1,5 +1,6 @@ -import os +import json import logging +import os from maya import cmds # noqa diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 70f2142a73..4c8f81c783 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -44,20 +44,20 @@ def _get_alembic_boolean_arguments(cls): """ # The Arguments that can be modified by the Publisher - abc_args_overrides = getattr(cls, "abc_args_overrides", []) + abc_args_overrides = set(getattr(cls, "abc_args_overrides", [])) # What we have set in the Settings as defaults. - abc_settings_boolean_args = getattr(cls, "abc_boolean_args", []) + abc_settings_boolean_args = set(getattr(cls, "abc_boolean_args", [])) - abc_defaults = [ + abc_defaults = { arg for arg in abc_settings_boolean_args if arg not in abc_args_overrides - ] + } - abc_overrideable = [ + abc_overrideable = { arg for arg in abc_settings_boolean_args if arg in abc_args_overrides - ] + } return abc_defaults, abc_overrideable @@ -96,7 +96,6 @@ def _get_animation_abc_attr_defs(cls): abc_boolean_overrides, ) = _get_alembic_boolean_arguments(cls) - # We display them to the user, but disable it abc_defs.append( EnumDef( "abcDefaultExportBooleanArguments", @@ -105,6 +104,7 @@ def _get_animation_abc_attr_defs(cls): multiselection=True, label="Settings Defined Arguments", disabled=True, + hidden=True ) ) @@ -201,9 +201,10 @@ def _ensure_defaults(cls, instance_data): creator_attr["abcDefaultExportBooleanArguments"] = abc_boolean_defaults - for idx, arg in enumerate(creator_attr["abcExportBooleanArguments"]): - if arg not in abc_boolean_overrides: - del creator_attr["abcExportBooleanArguments"][idx] + creator_attr["abcExportBooleanArguments"] = [ + arg for arg in creator_attr["abcExportBooleanArguments"] + if arg not in abc_boolean_overrides + ] return instance_data @@ -284,6 +285,3 @@ class CreatePointCache(plugin.MayaCreator): # For Arnold standin proxy proxy_set = cmds.sets(name=instance_node + "_proxy_SET", empty=True) cmds.sets(proxy_set, forceElement=instance_node) - - def apply_settings(self, project_settings): - super(CreatePointCache, self).apply_settings(project_settings) From f2f83454b56c755567526c64618c43808cf5660b Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 4 Jan 2024 17:07:51 +0000 Subject: [PATCH 042/244] `maya.create_animation_pointcache` Ensure there are `maya_cached_subsets` in `collection_shared_data` `maya_cached_subsets` might not exist at this time. It is run in `collect_instances()` in `MayaHiddenCreator` but not here. --- .../plugins/create/create_animation_pointcache.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 4c8f81c783..3c782de88b 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -229,7 +229,12 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_user_defined_attributes = False def collect_instances(self): - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + try: + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + except KeyError: + self.cache_subsets(self.collection_shared_data) + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) _ensure_defaults(self, node_data) @@ -259,7 +264,12 @@ class CreatePointCache(plugin.MayaCreator): include_user_defined_attributes = False def collect_instances(self): - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + try: + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + except KeyError: + self.cache_subsets(self.collection_shared_data) + cached_subsets = self.collection_shared_data["maya_cached_subsets"] + for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) _ensure_defaults(self, node_data) From e0e2cb06dd8d85ec73482285a75606bcf26b99ea Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:20:19 +0100 Subject: [PATCH 043/244] Slate support, duration fix, fps fix --- openpype/hosts/resolve/api/lib.py | 4 ++++ openpype/hosts/resolve/api/plugin.py | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 3866477c77..f7150e945b 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -279,6 +279,10 @@ def create_timeline_item( # timing variables if all([timeline_in, source_start, source_end]): fps = timeline.GetSetting("timelineFrameRate") + # Strangely, Resolve seem to output '23' instead of 23.976 + if fps == '23': + fps = 23.976 + duration = source_end - source_start timecode_in = frames_to_timecode(timeline_in, fps) timecode_out = frames_to_timecode(timeline_in + duration, fps) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index a00933405f..a6123ed5d0 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -408,7 +408,14 @@ class ClipLoader: _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) - source_duration = int(_clip_property("Frames")) + + # Trim clip start if slate is present + if "slate" in self.data["versionData"]["families"]: + print("LOAD: slate found ") + source_in += 1 + print("LOAD media pool item source in slate : {}".format(source_in)) + # Calculate source duration excluding slate + source_duration = source_out - source_in + 1 if not self.with_handles: # Load file without the handles of the source media @@ -435,13 +442,12 @@ class ClipLoader: handle_start = version_data.get("handleStart", 0) handle_end = version_data.get("handleEnd", 0) frame_start_handle = frame_start - handle_start - frame_end_handle = frame_start + handle_end + frame_end_handle = frame_end + handle_end database_frame_duration = int( frame_end_handle - frame_start_handle + 1 ) - if source_duration >= database_frame_duration: - source_in += handle_start - source_out -= handle_end + source_in += handle_start + source_out -= handle_end # get timeline in timeline_start = self.active_timeline.GetStartFrame() From 5e309d01e0bc0a5ce02679f3df2c7b74bb23fae5 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:26:45 +0100 Subject: [PATCH 044/244] hound --- openpype/hosts/resolve/api/plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index a6123ed5d0..77e2991d3f 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -411,11 +411,7 @@ class ClipLoader: # Trim clip start if slate is present if "slate" in self.data["versionData"]["families"]: - print("LOAD: slate found ") source_in += 1 - print("LOAD media pool item source in slate : {}".format(source_in)) - # Calculate source duration excluding slate - source_duration = source_out - source_in + 1 if not self.with_handles: # Load file without the handles of the source media From f6b2b00d10c16c0e42bd5fe736d65c8008783518 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:46:32 +0100 Subject: [PATCH 045/244] Swapping version fix offset, copy IDT --- openpype/hosts/resolve/api/lib.py | 5 +++++ openpype/hosts/resolve/api/plugin.py | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 3866477c77..96caacf8d1 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -713,6 +713,11 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): bool: True if successfully replaced """ + # copy ACES input transform from timeline clip to new media item + mediapool_item_from_timeline = from_clip.GetMediaPoolItem() + _idt = mediapool_item_from_timeline.GetClipProperty('IDT') + to_clip.SetClipProperty('IDT', _idt) + _clip_prop = to_clip.GetClipProperty to_clip_name = _clip_prop("File Name") # add clip item as take to timeline diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index a00933405f..2346739e20 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -477,14 +477,16 @@ class ClipLoader: ) _clip_property = media_pool_item.GetClipProperty - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) + # Read trimming from timeline item + timeline_item_in = timeline_item.GetLeftOffset() + timeline_item_len = timeline_item.GetDuration() + timeline_item_out = timeline_item_in + timeline_item_len lib.swap_clips( timeline_item, media_pool_item, - source_in, - source_out + timeline_item_in, + timeline_item_out ) print("Loading clips: `{}`".format(self.data["clip_name"])) From dbe0b9c5b36e49987fdf9794c84964ba7b042cf8 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 16:59:06 +0100 Subject: [PATCH 046/244] revert to conditional handles trim --- openpype/hosts/resolve/api/plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 77e2991d3f..d254e5213c 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -412,6 +412,7 @@ class ClipLoader: # Trim clip start if slate is present if "slate" in self.data["versionData"]["families"]: source_in += 1 + source_duration = source_out - source_in + 1 if not self.with_handles: # Load file without the handles of the source media @@ -442,8 +443,9 @@ class ClipLoader: database_frame_duration = int( frame_end_handle - frame_start_handle + 1 ) - source_in += handle_start - source_out -= handle_end + if source_duration >= database_frame_duration: + source_in += handle_start + source_out -= handle_end # get timeline in timeline_start = self.active_timeline.GetStartFrame() From 749b3b41dd1b32c78a2a9176891b7ae38c95d95d Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 15 Jan 2024 10:10:13 +0000 Subject: [PATCH 047/244] `maya.create_animation_pointcache` Fix `_ensure_defaults` logic When traversing the `bool`ean argumetns, we not check if there are any before we actually traverse it, and we also cast the list to as set for performance improvements. --- .../plugins/create/create_animation_pointcache.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 3c782de88b..40d854c5b3 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -201,10 +201,14 @@ def _ensure_defaults(cls, instance_data): creator_attr["abcDefaultExportBooleanArguments"] = abc_boolean_defaults - creator_attr["abcExportBooleanArguments"] = [ - arg for arg in creator_attr["abcExportBooleanArguments"] - if arg not in abc_boolean_overrides - ] + if creator_attr.get("abcExportBooleanArguments", []): + abc_boolean_overrides = set(abc_boolean_overrides) + abc_boolean_args = creator_attr["abcExportBooleanArguments"].copy() + + creator_attr["abcExportBooleanArguments"] = [ + arg for arg in abc_boolean_args + if arg not in abc_boolean_overrides + ] return instance_data From afc7967e351d3ccea410b0d1fb768c220a846baa Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 15 Jan 2024 18:05:07 +0000 Subject: [PATCH 048/244] `maya.creat_pointcache` Fix boolean arguments not being correctly parsed and presented The logic that was filtering out non-booleans for display was faulty, we not rely on the `maya.lib.alembic` module to sort them out. --- .../create/create_animation_pointcache.py | 36 +++++++++++-------- .../defaults/project_settings/maya.json | 3 +- .../schemas/schema_maya_create.json | 5 +++ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 40d854c5b3..dc807a031d 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,6 +1,8 @@ from maya import cmds from openpype.hosts.maya.api import lib, plugin +from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS + from openpype.lib import ( BoolDef, TextDef, @@ -27,8 +29,7 @@ def _get_animation_attr_defs(cls): BoolDef("refresh", label="Refresh viewport during export"), BoolDef( "includeParentHierarchy", label="Include Parent Hierarchy" - ), - BoolDef("writeNormals", label="Write Normals"), + ) ] ) @@ -36,30 +37,36 @@ def _get_animation_attr_defs(cls): def _get_alembic_boolean_arguments(cls): - """Get two lists with the Alembic flags. + """Get two sets with the Alembic flags. Alembic flags are treted as booleans, so here we get all the possible options, and work out a list with all the ones that can be toggled and the list of defaults (un-toggleable.) """ + all_alembic_booleans = { + arg + for arg, arg_type in ALEMBIC_ARGS.items() + if arg_type is bool + } # The Arguments that can be modified by the Publisher - abc_args_overrides = set(getattr(cls, "abc_args_overrides", [])) + abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) # What we have set in the Settings as defaults. - abc_settings_boolean_args = set(getattr(cls, "abc_boolean_args", [])) + abc_settings_boolean_args = set(getattr(cls, "abc_boolean_args", set())) - abc_defaults = { + abc_boolean_args = { arg for arg in abc_settings_boolean_args if arg not in abc_args_overrides } - abc_overrideable = { - arg for arg in abc_settings_boolean_args if arg in abc_args_overrides + abc_args_overrides = { + arg + for arg in abc_args_overrides + if arg in all_alembic_booleans } - - return abc_defaults, abc_overrideable + return abc_boolean_args, abc_args_overrides def _get_animation_abc_attr_defs(cls): @@ -99,8 +106,8 @@ def _get_animation_abc_attr_defs(cls): abc_defs.append( EnumDef( "abcDefaultExportBooleanArguments", - abc_boolean_defaults, - default=abc_boolean_defaults, + list(abc_boolean_defaults), + default=list(abc_boolean_defaults), multiselection=True, label="Settings Defined Arguments", disabled=True, @@ -112,7 +119,7 @@ def _get_animation_abc_attr_defs(cls): abc_defs.append( EnumDef( "abcExportBooleanArguments", - abc_boolean_overrides if abc_boolean_overrides else [""], + list(abc_boolean_overrides) if abc_boolean_overrides else [""], multiselection=True, label="Arguments Overrides", disabled=True if not abc_boolean_overrides else False, @@ -199,10 +206,9 @@ def _ensure_defaults(cls, instance_data): abc_boolean_overrides, ) = _get_alembic_boolean_arguments(cls) - creator_attr["abcDefaultExportBooleanArguments"] = abc_boolean_defaults + creator_attr["abcDefaultExportBooleanArguments"] = list(abc_boolean_defaults) if creator_attr.get("abcExportBooleanArguments", []): - abc_boolean_overrides = set(abc_boolean_overrides) abc_boolean_args = creator_attr["abcExportBooleanArguments"].copy() creator_attr["abcExportBooleanArguments"] = [ diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index fc38070573..3c7edcc769 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -561,7 +561,6 @@ "step": 1.0, "abc_boolean_args": [ "writeColorSets", - "visibleOnly", "worldSpace", "writeNormals" ], @@ -581,7 +580,7 @@ "dataFormat": "ogawa", "preRollStartFrame": 0, "refresh": false, - "stripNamespaces": false + "visibleOnly": false }, "CreateModel": { "enabled": true, 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 b101a4c0c5..5ae4397a87 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 @@ -233,6 +233,11 @@ "minimum": 0.0, "decimal": 4 }, + { + "type": "boolean", + "key": "visibleOnly", + "label": "Visible Only default" + }, { "type": "text", "key": "attr", From 97e8ccd5c5209f11e9c822fd573e758abd094d39 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 15:29:48 +0000 Subject: [PATCH 049/244] `server_addons.maya` Fix creator settings using legacy `Field` --- server_addon/maya/server/settings/creators.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 886a4b797b..0707a20458 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -173,26 +173,26 @@ class CreatePointCacheModel(BaseSettingsModel): default_factory=list, title="Default Products" ) - priority: int = Field( + priority: int = SettingsField( title="Farm Job Priority") - pre_roll_start_frame: int = Field(title="Pre Roll Start Frame") - refresh: bool = Field( + pre_roll_start_frame: int = SettingsField(title="Pre Roll Start Frame") + refresh: bool = SettingsField( title="Refresh") - step: int = Field(title="Step") - farm: bool = Field( + step: int = SettingsField(title="Step") + farm: bool = SettingsField( title="Submit to the Farm") - attr: str = Field(title="Attributes") - attr_prefix: str = Field(title="Attributes Prefix") - data_format: AlembicDataFormat = Field( + attr: str = SettingsField(title="Attributes") + attr_prefix: str = SettingsField(title="Attributes Prefix") + data_format: AlembicDataFormat = SettingsField( enum_resolver=alembic_data_formats, title="Data Format", ) - abc_boolean_args: list[str] = Field( + abc_boolean_args: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_booleans_enum, title="Alembic Boolean Args", ) - abc_args_overrides: list[str] = Field( + abc_args_overrides: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_arguments_enum, title="Alembic Arguments Overrides", From 8c999fbea9061ae2bc02d8c6bdd26e7b90d94c60 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 15:35:46 +0000 Subject: [PATCH 050/244] `server_addons.maya` Fix creator settings using legacy `Field` --- server_addon/maya/server/settings/creators.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 0707a20458..7e26980b92 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -136,26 +136,26 @@ class CreateAnimationModel(BaseSettingsModel): default_factory=list, title="Default Products" ) - priority: int = Field( + priority: int = SettingsField( title="Farm Job Priority") - pre_roll_start_frame: int = Field(title="Pre Roll Start Frame") - refresh: bool = Field( + pre_roll_start_frame: int = SettingsField(title="Pre Roll Start Frame") + refresh: bool = SettingsField( title="Refresh") - step: int = Field(title="Step") - farm: bool = Field( + step: int = SettingsField(title="Step") + farm: bool = SettingsField( title="Submit to the Farm") - attr: str = Field(title="Attributes") - attr_prefix: str = Field(title="Attributes Prefix") - data_format: AlembicDataFormat = Field( + attr: str = SettingsField(title="Attributes") + attr_prefix: str = SettingsField(title="Attributes Prefix") + data_format: AlembicDataFormat = SettingsField( enum_resolver=alembic_data_formats, title="Data Format", ) - abc_boolean_args: list[str] = Field( + abc_boolean_args: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_booleans_enum, title="Alembic Boolean Args", ) - abc_args_overrides: list[str] = Field( + abc_args_overrides: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_arguments_enum, title="Alembic Arguments Overrides", From 8e284aaa4dc7e0e272c00432f8a8f5b21ee37e23 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 16:25:53 +0000 Subject: [PATCH 051/244] `settings.maya` Rename Alembic settings This commit attempts to better name and label the Alembic settings, used in the creation and extraction of Alembic pointcaches. --- .../defaults/project_settings/maya.json | 8 +++---- .../schemas/schema_maya_create.json | 16 ++++++------- server_addon/maya/server/settings/creators.py | 24 +++++++++---------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 28e8cfefa4..214a4f737a 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -559,12 +559,12 @@ "CreateAnimation": { "default_variants": [], "step": 1.0, - "abc_boolean_args": [ + "abc_export_flags": [ "writeColorSets", "worldSpace", "writeNormals" ], - "abc_args_overrides": [ + "abc_export_overrides": [ "step", "includeParentHierarchy", "writeNormals", @@ -598,13 +598,13 @@ "Main" ], "step": 1.0, - "abc_boolean_args": [ + "abc_export_flags": [ "selection", "uvWrite", "writeCreases", "writeVisibility" ], - "abc_args_overrides": [ + "abc_export_overrides": [ "attr", "attrPrefix", "step", 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 5ae4397a87..6f9ead7fec 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 @@ -160,9 +160,9 @@ }, { "type": "enum", - "key": "abc_boolean_args", + "key": "abc_export_flags", "multiselection": true, - "label": "Alembic Boolean Args", + "label": "Export Flags (.abc)", "enum_items": [ {"autoSubd": "autoSubd"}, {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, @@ -186,9 +186,9 @@ }, { "type": "enum", - "key": "abc_args_overrides", + "key": "abc_export_overrides", "multiselection": true, - "label": "Alembic Arguments Overrides:\nChoose which arguments\nthe user can modify.", + "label": "Export Overrides (.abc)", "enum_items": [ {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, @@ -319,9 +319,9 @@ }, { "type": "enum", - "key": "abc_boolean_args", + "key": "abc_export_flags", "multiselection": true, - "label": "Alembic Boolean Args", + "label": "Export Flags (.abc)", "enum_items": [ {"autoSubd": "autoSubd"}, {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, @@ -345,9 +345,9 @@ }, { "type": "enum", - "key": "abc_args_overrides", + "key": "abc_export_overrides", "multiselection": true, - "label": "Alembic Arguments Overrides:\nChoose which arguments\nthe user can modify.", + "label": "Export Overrides (.abc)", "enum_items": [ {"attr": "attr"}, {"attrPrefix": "attrPrefix"}, diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 7e26980b92..2fc5a66818 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -150,15 +150,15 @@ class CreateAnimationModel(BaseSettingsModel): enum_resolver=alembic_data_formats, title="Data Format", ) - abc_boolean_args: list[str] = SettingsField( + abc_export_flags: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_booleans_enum, - title="Alembic Boolean Args", + title="Export Flags (.abc)", ) - abc_args_overrides: list[str] = SettingsField( + abc_export_overrides: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_arguments_enum, - title="Alembic Arguments Overrides", + title="Export Overrides (.abc)", ) @@ -187,15 +187,15 @@ class CreatePointCacheModel(BaseSettingsModel): enum_resolver=alembic_data_formats, title="Data Format", ) - abc_boolean_args: list[str] = SettingsField( + abc_export_flags: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_booleans_enum, - title="Alembic Boolean Args", + title="Export Flags (.abc)", ) - abc_args_overrides: list[str] = SettingsField( + abc_export_overrides: list[str] = SettingsField( default_factory=list, enum_resolver=alembic_arguments_enum, - title="Alembic Arguments Overrides", + title="Export Overrides (.abc)", ) @@ -399,13 +399,13 @@ DEFAULT_CREATORS_SETTINGS = { "Main" ], "step": 1.0, - "abc_boolean_args": [ + "abc_export_flags": [ "writeColorSets", "visibleOnly", "worldSpace", "writeNormals" ], - "abc_args_overrides": [ + "abc_export_overrides": [ "step", "includeParentHierarchy", "writeNormals", @@ -439,13 +439,13 @@ DEFAULT_CREATORS_SETTINGS = { "Main" ], "step": 1.0, - "abc_boolean_args": [ + "abc_export_flags": [ "selection", "uvWrite", "writeCreases", "writeVisibility" ], - "abc_args_overrides": [ + "abc_export_overrides": [ "attr", "attrPrefix", "step", From 265239770e5d34e481f944cd8a9588dd0a23184d Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 16:27:29 +0000 Subject: [PATCH 052/244] `maya.plugins.pointcache` Fix togglable flags defaults and adapt to new settings names We weren't setting defaults when some admin defined flag was added to the toggleable flags, this commit will now pre-select those. It also adapts to the (hopefully) better named settings for the Alembic export. --- .../create/create_animation_pointcache.py | 90 +++++++++++-------- .../plugins/publish/extract_pointcache.py | 41 +++++---- 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index dc807a031d..e57e198ac4 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -17,8 +17,8 @@ from openpype.pipeline import CreatedInstance def _get_animation_attr_defs(cls): """Get Animation generic definitions. - The line is blurry between what's "Animation" generic and "Alembic" is - blurry, but the rule of thumb is that whatever "AlembicExport -h" accepts + The line is blurry between what's "Animation" generic and "Alembic", + but the rule of thumb is that whatever "AlembicExport -h" accepts is "Alembic" and the other ones are "Animation". """ defs = lib.collect_animation_defs() @@ -36,37 +36,41 @@ def _get_animation_attr_defs(cls): return defs -def _get_alembic_boolean_arguments(cls): - """Get two sets with the Alembic flags. +def _get_abc_export_flags(cls): + """Get two sets with the Alembic Export flags. - Alembic flags are treted as booleans, so here we get all the possible - options, and work out a list with all the ones that can be toggled and the - list of defaults (un-toggleable.) + Alembic flags are treated as booleans, so here we get all the possible + options, and work out a list with all the ones that can be toggled by + the user, and the ones defined in the settings. """ - all_alembic_booleans = { + + # The Arguments that can be modified by the Publisher + abc_export_overrides = set(getattr(cls, "abc_export_overrides", set())) + + # What we have set in the Settings as defaults. + default_abc_export_flags = set(getattr(cls, "abc_export_flags", set())) + + # Set of un-toggleable flags, specified by the settings + abc_export_flags = { + arg + for arg in default_abc_export_flags + if arg not in abc_export_overrides + } + + # Set of all the available Alembic Export Flags + abc_boolean_flags = { arg for arg, arg_type in ALEMBIC_ARGS.items() if arg_type is bool } - # The Arguments that can be modified by the Publisher - abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) - - # What we have set in the Settings as defaults. - abc_settings_boolean_args = set(getattr(cls, "abc_boolean_args", set())) - - abc_boolean_args = { + # Set of togglable flags + abc_export_toggleable_flags = { arg - for arg in abc_settings_boolean_args - if arg not in abc_args_overrides + for arg in abc_export_overrides + if arg in abc_boolean_flags } - - abc_args_overrides = { - arg - for arg in abc_args_overrides - if arg in all_alembic_booleans - } - return abc_boolean_args, abc_args_overrides + return abc_export_flags, abc_export_toggleable_flags def _get_animation_abc_attr_defs(cls): @@ -98,16 +102,19 @@ def _get_animation_abc_attr_defs(cls): # The Arguments that can be modified by the Publisher abc_args_overrides = getattr(cls, "abc_args_overrides", None) + # What we have set in the Settings as defaults. + default_abc_export_flags = set(getattr(cls, "abc_export_flags", set())) + ( - abc_boolean_defaults, - abc_boolean_overrides, - ) = _get_alembic_boolean_arguments(cls) + abc_export_flags, + abc_export_toggleable_flags, + ) = _get_abc_export_flags(cls) abc_defs.append( EnumDef( - "abcDefaultExportBooleanArguments", - list(abc_boolean_defaults), - default=list(abc_boolean_defaults), + "abcExportFlags", + list(abc_export_flags), + default=list(abc_export_flags), multiselection=True, label="Settings Defined Arguments", disabled=True, @@ -116,13 +123,18 @@ def _get_animation_abc_attr_defs(cls): ) # Only display Boolan flags that the Admin defined as overrideable + abc_export_toggleable_defaults = [ + for arg in abc_export_toggleable_flags + if arg in default_abc_export_flags + ] abc_defs.append( EnumDef( - "abcExportBooleanArguments", - list(abc_boolean_overrides) if abc_boolean_overrides else [""], + "abcExportTogglableFlags", + list(abc_export_toggleable_flags) if abc_export_toggleable_flags else [""], + default=abc_export_toggleable_defaults, multiselection=True, - label="Arguments Overrides", - disabled=True if not abc_boolean_overrides else False, + label="Export Flags", + disabled=True if not abc_export_toggleable_flags else False, ) ) @@ -204,14 +216,14 @@ def _ensure_defaults(cls, instance_data): ( abc_boolean_defaults, abc_boolean_overrides, - ) = _get_alembic_boolean_arguments(cls) + ) = _get_abc_export_flags(cls) - creator_attr["abcDefaultExportBooleanArguments"] = list(abc_boolean_defaults) + creator_attr["abcExportFlags"] = list(abc_boolean_defaults) - if creator_attr.get("abcExportBooleanArguments", []): - abc_boolean_args = creator_attr["abcExportBooleanArguments"].copy() + if creator_attr.get("abcExportTogglableFlags", []): + abc_boolean_args = creator_attr["abcExportTogglableFlags"].copy() - creator_attr["abcExportBooleanArguments"] = [ + creator_attr["abcExportTogglableFlags"] = [ arg for arg in abc_boolean_args if arg not in abc_boolean_overrides ] diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7eb69da151..9a6f192e03 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -3,11 +3,11 @@ import os from maya import cmds from openpype.pipeline import publish -from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS, extract_alembic +from openpype.hosts.maya.api.alembic import extract_alembic from openpype.hosts.maya.api.lib import ( suspended_refresh, maintained_selection, - iter_visible_nodes_in_range + iter_visible_nodes_in_range, ) @@ -40,14 +40,11 @@ class ExtractAlembic(publish.Extractor): # Collect Alembic Arguments creator_attributes = instance.data.get("creator_attributes") abc_flags = creator_attributes.get( - "abcDefaultExportBooleanArguments" - ) + creator_attributes.get( - "abcExportBooleanArguments" - ) + "abcExportTogglableFlags" + ) + creator_attributes.get("abcExportTogglableFlags") abc_attrs = [ - attr.strip() - for attr in creator_attributes.get("attr", "").split(";") + attr.strip() for attr in creator_attributes.get("attr", "").split(";") ] abc_attr_prefixes = [ @@ -88,7 +85,7 @@ class ExtractAlembic(publish.Extractor): "preRollStartFrame": creator_attributes.get("preRollStartFrame", 0), "renderableOnly": True if "renderableOnly" in abc_flags else False, "root": abc_root, - "selection": True, # Should this stay like so? + "selection": True, # Should this stay like so? "startFrame": start, "step": creator_attributes.get("step", 1.0), "stripNamespaces": True, @@ -109,15 +106,17 @@ class ExtractAlembic(publish.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_nodes_in_range(nodes, - start=start, - end=end)) + nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) suspend = not instance.data.get("refresh", False) with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(nodes, noExpand=True) - self.log.debug("Running `extract_alembic` with the arguments: {}".format(extract_abc_args)) + self.log.debug( + "Running `extract_alembic` with the arguments: {}".format( + extract_abc_args + ) + ) extract_alembic(**extract_abc_args) if "representations" not in instance.data: @@ -127,7 +126,7 @@ class ExtractAlembic(publish.Extractor): "name": "abc", "ext": "abc", "files": filename, - "stagingDir": dirname + "stagingDir": dirname, } instance.data["representations"].append(representation) @@ -159,7 +158,7 @@ class ExtractAlembic(publish.Extractor): "ext": "abc", "files": os.path.basename(path), "stagingDir": dirname, - "outputName": "proxy" + "outputName": "proxy", } instance.data["representations"].append(representation) @@ -172,18 +171,18 @@ class ExtractAnimation(ExtractAlembic): families = ["animation"] def get_members_and_roots(self, instance): - # Collect the out set nodes out_sets = [node for node in instance if node.endswith("out_SET")] if len(out_sets) != 1: - raise RuntimeError("Couldn't find exactly one out_SET: " - "{0}".format(out_sets)) + raise RuntimeError( + "Couldn't find exactly one out_SET: " "{0}".format(out_sets) + ) out_set = out_sets[0] roots = cmds.sets(out_set, query=True) # Include all descendants - nodes = roots + cmds.listRelatives(roots, - allDescendents=True, - fullPath=True) or [] + nodes = ( + roots + cmds.listRelatives(roots, allDescendents=True, fullPath=True) or [] + ) return nodes, roots From 76a6e2ec1a897cc846ac5dbd75ee114d8619cf44 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 16:37:20 +0000 Subject: [PATCH 053/244] `maya.plugins` Fix create pointcache and apply format --- .../create/create_animation_pointcache.py | 20 +++++++++---------- .../plugins/publish/extract_pointcache.py | 19 +++++++++++++----- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index e57e198ac4..00002768cd 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -29,7 +29,7 @@ def _get_animation_attr_defs(cls): BoolDef("refresh", label="Refresh viewport during export"), BoolDef( "includeParentHierarchy", label="Include Parent Hierarchy" - ) + ), ] ) @@ -59,16 +59,12 @@ def _get_abc_export_flags(cls): # Set of all the available Alembic Export Flags abc_boolean_flags = { - arg - for arg, arg_type in ALEMBIC_ARGS.items() - if arg_type is bool + arg for arg, arg_type in ALEMBIC_ARGS.items() if arg_type is bool } # Set of togglable flags abc_export_toggleable_flags = { - arg - for arg in abc_export_overrides - if arg in abc_boolean_flags + arg for arg in abc_export_overrides if arg in abc_boolean_flags } return abc_export_flags, abc_export_toggleable_flags @@ -118,19 +114,22 @@ def _get_animation_abc_attr_defs(cls): multiselection=True, label="Settings Defined Arguments", disabled=True, - hidden=True + hidden=True, ) ) # Only display Boolan flags that the Admin defined as overrideable abc_export_toggleable_defaults = [ + arg for arg in abc_export_toggleable_flags if arg in default_abc_export_flags ] abc_defs.append( EnumDef( "abcExportTogglableFlags", - list(abc_export_toggleable_flags) if abc_export_toggleable_flags else [""], + list(abc_export_toggleable_flags) + if abc_export_toggleable_flags + else [""], default=abc_export_toggleable_defaults, multiselection=True, label="Export Flags", @@ -224,8 +223,7 @@ def _ensure_defaults(cls, instance_data): abc_boolean_args = creator_attr["abcExportTogglableFlags"].copy() creator_attr["abcExportTogglableFlags"] = [ - arg for arg in abc_boolean_args - if arg not in abc_boolean_overrides + arg for arg in abc_boolean_args if arg not in abc_boolean_overrides ] return instance_data diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 9a6f192e03..4bfd5e2fe0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -44,7 +44,8 @@ class ExtractAlembic(publish.Extractor): ) + creator_attributes.get("abcExportTogglableFlags") abc_attrs = [ - attr.strip() for attr in creator_attributes.get("attr", "").split(";") + attr.strip() + for attr in creator_attributes.get("attr", "").split(";") ] abc_attr_prefixes = [ @@ -82,7 +83,9 @@ class ExtractAlembic(publish.Extractor): "eulerFilter": True if "eulerFilter" in abc_flags else False, "noNormals": True if "noNormals" in abc_flags else False, "preRoll": True if "preRoll" in abc_flags else False, - "preRollStartFrame": creator_attributes.get("preRollStartFrame", 0), + "preRollStartFrame": creator_attributes.get( + "preRollStartFrame", 0 + ), "renderableOnly": True if "renderableOnly" in abc_flags else False, "root": abc_root, "selection": True, # Should this stay like so? @@ -97,7 +100,9 @@ class ExtractAlembic(publish.Extractor): "writeCreases": True if "writeCreases" in abc_flags else False, "writeFaceSets": True if "writeFaceSets" in abc_flags else False, "writeUVSets": abc_writeUVSets, - "writeVisibility": True if "writeVisibility" in abc_flags else False, + "writeVisibility": True + if "writeVisibility" in abc_flags + else False, } if instance.data.get("visibleOnly", False): @@ -106,7 +111,9 @@ class ExtractAlembic(publish.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) + nodes = list( + iter_visible_nodes_in_range(nodes, start=start, end=end) + ) suspend = not instance.data.get("refresh", False) with suspended_refresh(suspend=suspend): @@ -182,7 +189,9 @@ class ExtractAnimation(ExtractAlembic): # Include all descendants nodes = ( - roots + cmds.listRelatives(roots, allDescendents=True, fullPath=True) or [] + roots + + cmds.listRelatives(roots, allDescendents=True, fullPath=True) + or [] ) return nodes, roots From 534bc8295f8b894db9c31c5b1fa8c7ff8eb5e503 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 1 Feb 2024 10:52:45 +0000 Subject: [PATCH 054/244] `server_addons.maya.settings` fix missing `Literal` type in creators --- server_addon/maya/server/settings/creators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 2fc5a66818..872984a06b 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -1,3 +1,5 @@ +from typing import Literal + from ayon_server.settings import ( BaseSettingsModel, SettingsField, From 43bbf5af8e941cf5f17f88b96db31989a3eaf787 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 1 Feb 2024 12:36:28 +0000 Subject: [PATCH 055/244] `server_addons.maya.settings` Fix missing `creator` defaults --- server_addon/maya/server/settings/creators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 872984a06b..492b2c180d 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -396,6 +396,7 @@ DEFAULT_CREATORS_SETTINGS = { "CreateAnimation": { "write_color_sets": False, "write_face_sets": False, + "include_user_defined_attributes": False, "include_parent_hierarchy": False, "default_variants": [ "Main" @@ -437,6 +438,7 @@ DEFAULT_CREATORS_SETTINGS = { "enabled": True, "write_color_sets": False, "write_face_sets": False, + "include_user_defined_attributes": False, "default_variants": [ "Main" ], From d64eb3ccc2eba2a622631bac8041df66722648a1 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:26:38 +0000 Subject: [PATCH 056/244] refactor: replaced legacy function has_unsaved_changes seems to be legacy as indicated by the base class. It was already unused/implemented. Replaced with working version workfiles_has_unsaved_changes --- openpype/hosts/max/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..ce4afd2e8b 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,9 +60,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def has_unsaved_changes(self): - # TODO: how to get it from 3dsmax? - return True + def workfiles_has_unsaved_changes(self): + return rt.getSaveRequired() def get_workfile_extensions(self): return [".max"] From 895ca1b57221e395d0f205e1ed8c45832c55e2a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:28:08 +0000 Subject: [PATCH 057/244] feat: implemented new work_file_has_unsaved_changes function on host class mirrored Houdini implementation --- .../hosts/max/plugins/publish/save_scene.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index a40788ab41..54f9f8d8eb 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -1,21 +1,24 @@ import pyblish.api -import os +from openpype.pipeline import registered_host class SaveCurrentScene(pyblish.api.ContextPlugin): - """Save current scene - - """ + """Save current scene""" label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["max"] families = ["maxrender", "workfile"] - + def process(self, context): - from pymxs import runtime as rt - folder = rt.maxFilePath - file = rt.maxFileName - current = os.path.join(folder, file) - assert context.data["currentFile"] == current - rt.saveMaxFile(current) + host = registered_host() + current_file = host.get_current_workfile() + + assert context.data["currentFile"] == current_file + + if host.workfile_has_unsaved_changes(): + self.log.info(f"Saving current file: {current_file}") + host.save_workfile(current_file) + else: + self.log.debug("No unsaved changes, skipping file save..") + From 14baeb65c8820c90e15f90295bd32e238e13c60c Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:29:59 +0000 Subject: [PATCH 058/244] feat: added line to restore menu to 3dsmax default --- openpype/hosts/max/startup/startup.ms | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index b80ead4b74..3a4e76b3cf 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -7,6 +7,9 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath + + # opens the create menu on startup to ensure users are presented with a useful default view. + max create mode python.ExecuteFile startup -) \ No newline at end of file +) From 4999550167e3668b7be617c2842cddaee100a9a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:50:04 +0000 Subject: [PATCH 059/244] fix: hound --- openpype/hosts/max/plugins/publish/save_scene.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index 54f9f8d8eb..fa571be835 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -9,16 +9,15 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.49 hosts = ["max"] families = ["maxrender", "workfile"] - + def process(self, context): host = registered_host() current_file = host.get_current_workfile() assert context.data["currentFile"] == current_file - + if host.workfile_has_unsaved_changes(): self.log.info(f"Saving current file: {current_file}") host.save_workfile(current_file) else: self.log.debug("No unsaved changes, skipping file save..") - From 87b3d7fcec70be06e77f56cf4756d7b22adffdcb Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 1 Feb 2024 16:15:04 +0000 Subject: [PATCH 060/244] `hosts.maya.create` Ensure we get a `set` in `abc_args_overrides` in `create_pointcache` --- .../hosts/maya/plugins/create/create_animation_pointcache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 00002768cd..353a4402ee 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -96,7 +96,7 @@ def _get_animation_abc_attr_defs(cls): ] # The Arguments that can be modified by the Publisher - abc_args_overrides = getattr(cls, "abc_args_overrides", None) + abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) # What we have set in the Settings as defaults. default_abc_export_flags = set(getattr(cls, "abc_export_flags", set())) @@ -194,7 +194,7 @@ def _ensure_defaults(cls, instance_data): settings) instead of any value that might have been stored in the scene when the attribute was modifiable. """ - abc_args_overrides = getattr(cls, "abc_args_overrides", []) + abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) creator_attr = instance_data["creator_attributes"] attr_default = getattr(cls, "attr", "") From be70b52285c1d0c1d4542c20b0d91de7b889c174 Mon Sep 17 00:00:00 2001 From: Sponge96 Date: Fri, 2 Feb 2024 09:26:30 +0000 Subject: [PATCH 061/244] fix: comment using wrong syntax Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/startup/startup.ms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index 3a4e76b3cf..5e79901cdd 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -8,7 +8,7 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath - # opens the create menu on startup to ensure users are presented with a useful default view. + /*opens the create menu on startup to ensure users are presented with a useful default view.*/ max create mode python.ExecuteFile startup From f83d0f974958ffef089e4fb362cf6f13514fe104 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Feb 2024 14:25:12 +0800 Subject: [PATCH 062/244] AYON menu would be registered when the workspace has been changed --- openpype/hosts/max/api/pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..18e287266a 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -59,6 +59,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) + rt.callbacks.addScript(rt.Name('postWorkspaceChange'), + self._deferred_menu_creation) def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? From d502a26bfe55b8062dc383a00b61c8f21b490c99 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:47:37 +0100 Subject: [PATCH 063/244] Update CONTRIBUTING.md --- CONTRIBUTING.md | 55 +++++++------------------------------------------ 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 644a74c1f7..2898c13acd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,53 +1,12 @@ -## How to contribute to Pype +## How to contribute to OpenPype -We are always happy for any contributions for OpenPype improvements. Before making a PR and starting working on an issue, please read these simple guidelines. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. -#### **Did you find a bug?** +## Getting my PR merged during this period -1. Check in the issues and our [bug triage[(https://github.com/pypeclub/pype/projects/2) to make sure it wasn't reported already. -2. Ask on our [discord](http://pype.community/chat) Often, what appears as a bug, might be the intended behaviour for someone else. -3. Create a new issue. -4. Use the issue template for you PR please. +- Each OpenPype PR MUST have a corresponding AYON PR in github. Without AYON compatibility features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from OpenPype to AYON should be really easy. +- Please keep the corresponding OpenPype and AYON PR names the same so they can be easily identified. +Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -#### **Did you write a patch that fixes a bug?** - -- Open a new GitHub pull request with the patch. -- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. - -#### **Do you intend to add a new feature or change an existing one?** - -- Open a new thread in the [github discussions](https://github.com/pypeclub/pype/discussions/new) -- Do not open issue until the suggestion is discussed. We will convert accepted suggestions into backlog and point them to the relevant discussion thread to keep the context. -- If you are already working on a new feature and you'd like it eventually merged to the main codebase, please consider making a DRAFT PR as soon as possible. This makes it a lot easier to give feedback, discuss the code and functionalit, plus it prevents multiple people tackling the same problem independently. - -#### **Do you have questions about the source code?** - -Open a new question on [github discussions](https://github.com/pypeclub/pype/discussions/new) - -## Branching Strategy - -As we move to 3.x as the primary supported version of pype and only keep 2.15 on bug bugfixes and client sponsored feature requests, we need to be very careful with merging strategy. - -We also use this opportunity to switch the branch naming. 3.0 production branch will no longer be called MASTER, but will be renamed to MAIN. Develop will stay as it is. - -A few important notes about 2.x and 3.x development: - -- 3.x features are not backported to 2.x unless specifically requested -- 3.x bugs and hotfixes can be ported to 2.x if they are relevant or severe -- 2.x features and bugs MUST be ported to 3.x at the same time - -## Pull Requests - -- Each 2.x PR MUST have a corresponding 3.x PR in github. Without 3.x PR, 2.x features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from 2.x to 3.x should be really easy. -- Please keep the corresponding 2 and 3 PR names the same so they can be easily identified from the PR list page. -- Each 2.x PR should be labeled with `2.x-dev` label. - -Inside each PR, put a link to the corresponding PR for the other version - -Of course if you want to contribute, feel free to make a PR to only 2.x/develop or develop, based on what you are using. While reviewing the PRs, we might convert the code to corresponding PR for the other release ourselves. - -We might also change the target of you PR to and intermediate branch, rather than `develop` if we feel it requires some extra work on our end. That way, we preserve all your commits so you don't loose out on the contribution credits. - - -If a PR is targeted at 2.x release it must be labelled with 2x-dev label in Github. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 39f3f777e5f05a40f7b31c7036c1ac79beae71d1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:31 +0100 Subject: [PATCH 064/244] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2898c13acd..5f3fb90dfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## How to contribute to OpenPype -OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON at the same time. ## Getting my PR merged during this period From f2add8f7f158e1d8237dd21816e08a018118832b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:36 +0100 Subject: [PATCH 065/244] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f3fb90dfa..27294b19be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,4 +9,4 @@ OpenPype has reached the end of its life and is now in a limited maintenance mod Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository your AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 91917f20c21324e5be8fc69534d4a3dad23f50fe Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 19:02:45 +0100 Subject: [PATCH 066/244] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a79b9f2582..5b8d3692dc 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,13 @@ OpenPype ## Important Notice! -OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +OpenPype as a standalone product has reach end of it's life and this repository is now being phased out in favour of [ayon-core](https://github.com/ynput/ayon-core). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +As such, we no longer accept Pull Requests that are not ported to AYON at the same time! + +``` +Please refer to https://github.com/ynput/OpenPype/blob/develop/CONTRIBUTING.md for more information about the current PR process. +``` Introduction ------------ From f17588b51d59f71eaffdbaf317773794a70608ff Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 15:44:08 +0000 Subject: [PATCH 067/244] Fix getting non-existent settings --- openpype/hosts/nuke/api/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index c8301b81fd..fe89a79096 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1348,7 +1348,9 @@ def _remove_old_knobs(node): def exposed_write_knobs(settings, plugin_name, instance_node): - exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"] + exposed_knobs = settings["nuke"]["create"][plugin_name].get( + "exposed_knobs", [] + ) if exposed_knobs: instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs')) write_node = nuke.allNodes(group=instance_node, filter="Write")[0] From f49781aae15060be76838016be437e74cb1441bd Mon Sep 17 00:00:00 2001 From: JackP Date: Fri, 9 Feb 2024 16:00:57 +0000 Subject: [PATCH 068/244] fix: typo in class function --- openpype/hosts/max/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index ce4afd2e8b..1b74b8131c 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,7 +60,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def workfiles_has_unsaved_changes(self): + def workfile_has_unsaved_changes(self): return rt.getSaveRequired() def get_workfile_extensions(self): From 960de700dd3ba6fb9c75fbff7abda39d60ab7c56 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 9 Feb 2024 17:33:30 +0100 Subject: [PATCH 069/244] OP-8165 - fix AE local render doesnt push thumbnail to Ftrack (#6212) Without thumbnail review is not clickable from main Versions list --- openpype/plugins/publish/extract_thumbnail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..291345abb1 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -35,6 +35,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "traypublisher", "substancepainter", "nuke", + "aftereffects" ] enabled = False From 20e12d613e4c700d981f381044818c1ce6b746e5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 16:33:48 +0000 Subject: [PATCH 070/244] Fix exposed knobs validator --- openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py index fe5644f0c9..f592fc4a44 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py +++ b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py @@ -65,7 +65,7 @@ class ValidateExposedKnobs( group_node = instance.data["transientData"]["node"] nuke_settings = instance.context.data["project_settings"]["nuke"] create_settings = nuke_settings["create"][plugin] - exposed_knobs = create_settings["exposed_knobs"] + exposed_knobs = create_settings.get("exposed_knobs", []) unexposed_knobs = [] for knob in exposed_knobs: if knob not in group_node.knobs(): From 0e58b3efc665e7e4e5581c4238164cb3a95bac7b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 10 Feb 2024 03:24:54 +0000 Subject: [PATCH 071/244] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index d105b0169e..39fb10bb6e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.2" +__version__ = "3.18.7-nightly.3" From ecaf8a6f6b1ebd256ad7dc45d934eaa4ee10ef40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Feb 2024 03:25:29 +0000 Subject: [PATCH 072/244] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f751a54116..7cf51713e4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 - 3.18.6 @@ -134,7 +135,6 @@ body: - 3.15.10-nightly.2 - 3.15.10-nightly.1 - 3.15.9 - - 3.15.9-nightly.2 validations: required: true - type: dropdown From cf4bb19ad6dbf7221bc514d32422b609d3ccb489 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:30:35 +1300 Subject: [PATCH 073/244] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 30d95128a2..24762a4232 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -7,6 +7,7 @@ from openpype.hosts.maya.api import lib from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -38,7 +39,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError("Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 7f040bd32240198a988b027fa144dbf2bcbef1a9 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:36:06 +1300 Subject: [PATCH 074/244] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 24762a4232..2bb5036f8b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -39,7 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise PublishValidationError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError( + "Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 858a90335fc792b20a34179b281f1859af7d94b6 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 14 Feb 2024 03:25:48 +0000 Subject: [PATCH 075/244] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 39fb10bb6e..9e1bd39b3a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.3" +__version__ = "3.18.7-nightly.4" From b15643e2cd8d64f06a7cfa238aed705eaea4de2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Feb 2024 03:26:23 +0000 Subject: [PATCH 076/244] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7cf51713e4..bc0e00f740 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.10 - 3.15.10-nightly.2 - 3.15.10-nightly.1 - - 3.15.9 validations: required: true - type: dropdown From bcd216946859bfdb38b2d88df4541c9fe6319da7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:11:37 +0000 Subject: [PATCH 077/244] Cast aov_list to set to improve performance --- openpype/hosts/blender/api/render_lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 17b9d926ec..bb35dd44ea 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -101,7 +101,7 @@ def set_render_format(ext, multilayer): def set_render_passes(settings, renderer): - aov_list = settings["blender"]["RenderSettings"]["aov_list"] + aov_list = set(settings["blender"]["RenderSettings"]["aov_list"]) custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] # Common passes for both renderers @@ -175,7 +175,7 @@ def set_render_passes(settings, renderer): aov.type = (cp["value"] if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) - return aov_list, custom_passes + return list(aov_list), custom_passes def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): From 6002da8235c32ac74eeb5e7c40f5153b8326f59d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:18:52 +0000 Subject: [PATCH 078/244] Added an option to disable composite output --- openpype/hosts/blender/api/render_lib.py | 29 ++++++++++++++----- .../defaults/project_settings/blender.json | 1 + .../schema_project_blender.json | 5 ++++ .../server/settings/render_settings.py | 4 +++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index bb35dd44ea..c09cfce502 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -56,6 +56,14 @@ def get_renderer(settings): ["renderer"]) +def get_compositing(settings): + """Get compositing from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["compositing"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -186,7 +194,9 @@ def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): return slot, filepath -def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): +def set_node_tree( + output_path, render_product, name, aov_sep, ext, multilayer, compositing +): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -252,11 +262,12 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): name, aov_sep, slots, pass_name, multi_exr, output_path) tree.links.new(render_layer_node.outputs["Image"], slot) - # Create a new socket for the composite output - pass_name = "composite" - comp_socket, filepath = _create_aov_slot( - name, aov_sep, slots, pass_name, multi_exr, output_path) - aov_file_products.append(("Composite", filepath)) + if compositing: + # Create a new socket for the composite output + pass_name = "composite" + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) + aov_file_products.append(("Composite", filepath)) # For each active render pass, we add a new socket to the output node # and link it @@ -280,7 +291,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): tree.links.remove(link) # If there's a composite node, we connect its input with the new output - if composite_node: + if compositing and composite_node: for link in tree.links: if link.to_node == composite_node: tree.links.new(link.from_socket, comp_socket) @@ -323,6 +334,7 @@ def prepare_rendering(asset_group): ext = get_image_format(settings) multilayer = get_multilayer(settings) renderer = get_renderer(settings) + compositing = get_compositing(settings) set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer @@ -332,7 +344,8 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, render_product, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, + ext, multilayer, compositing) # Clear the render filepath, so that the output is handled only by the # output node in the compositor. diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 48f3ef8ef0..03a5400ced 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -23,6 +23,7 @@ "image_format": "exr", "multilayer_exr": true, "renderer": "CYCLES", + "compositing": true, "aov_list": ["combined"], "custom_passes": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index bbed881ab0..13e460b74c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -114,6 +114,11 @@ {"BLENDER_EEVEE": "Eevee"} ] }, + { + "key": "compositing", + "type": "boolean", + "label": "Enable Compositing" + }, { "key": "aov_list", "label": "AOVs to create", diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index a1f6d3114a..f992ea6fcc 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -127,6 +127,9 @@ class RenderSettingsModel(BaseSettingsModel): title="Renderer", enum_resolver=renderers_enum ) + compositing: bool = SettingsField( + title="Enable Compositing" + ) aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=aov_list_enum, @@ -149,6 +152,7 @@ DEFAULT_RENDER_SETTINGS = { "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", + "compositing": True, "aov_list": ["combined"], "custom_passes": [] } From ea00109d86dd5f40422ee0eabe046ed65d50586a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 12:35:05 +0000 Subject: [PATCH 079/244] Expose families transfer setting. --- openpype/settings/defaults/project_settings/deadline.json | 1 + .../schemas/projects_schema/schema_project_deadline.json | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index b02cfa8207..0c4b282d10 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -129,6 +129,7 @@ "deadline_priority": 50, "publishing_script": "", "skip_integration_repre_list": [], + "families_transfer": ["render3d", "render2d", "ftrack", "slate"], "aov_filter": { "maya": [ ".*([Bb]eauty).*" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 42dea33ef9..bb8e0b5cd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -693,6 +693,14 @@ "type": "text" } }, + { + "type": "list", + "key": "families_transfer", + "label": "List of family names to transfer\nto generated instances (AOVs for example).", + "object_type": { + "type": "text" + } + }, { "type": "dict-modifiable", "docstring": "Regular expression to filter for which subset review should be created in publish job.", From 3520f5cc90a03d882460513e63283b0b43d54f32 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 15:25:34 +0000 Subject: [PATCH 080/244] Add AVALON_DB to Deadline submissions --- .../deadline/plugins/publish/submit_aftereffects_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_blender_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_fusion_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_harmony_deadline.py | 1 + .../deadline/plugins/publish/submit_houdini_cache_deadline.py | 1 + .../deadline/plugins/publish/submit_houdini_render_deadline.py | 1 + openpype/modules/deadline/plugins/publish/submit_max_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 + .../plugins/publish/submit_maya_remote_publish_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_publish_cache_job.py | 1 + openpype/modules/deadline/plugins/publish/submit_publish_job.py | 1 + 12 files changed, 12 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 009375e87e..d40c371de0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -82,6 +82,7 @@ class AfterEffectsSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 8f9e9a7425..58e69d0aea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -104,6 +104,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 9a718aa089..dcb79588a7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -223,6 +223,7 @@ class FusionSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 17e672334c..73bc10465d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -275,6 +275,7 @@ class HarmonySubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index ada69575a8..bef93b3947 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -110,6 +110,7 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index bf7fb45a8b..6ed9e66ce0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -205,6 +205,7 @@ class HoudiniSubmitDeadline( "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index f06bd4dbe6..e31de0a101 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -108,6 +108,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 5591db151a..4cd417b83b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -201,6 +201,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 41a2a64ab5..a9fb10de8b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -108,6 +108,7 @@ class MayaSubmitRemotePublishDeadline( if key in os.environ}, **legacy_io.Session) # TODO replace legacy_io with context.data + environment["AVALON_DB"] = os.environ.get("AVALON_DB") environment["AVALON_PROJECT"] = project_name environment["AVALON_ASSET"] = instance.context.data["asset"] environment["AVALON_TASK"] = instance.context.data["task"] diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 746b009255..9c2d212806 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -376,6 +376,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, keys = [ "PYTHONPATH", "PATH", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index 1bb45b77cc..434a823cfe 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -131,6 +131,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { + "AVALON_DB": os.environ["AVALON_DB"], "AVALON_PROJECT": instance.context.data["projectName"], "AVALON_ASSET": instance.context.data["asset"], "AVALON_TASK": instance.context.data["task"], diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 82971daee5..f622ec9a00 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -187,6 +187,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { + "AVALON_DB": os.environ["AVALON_DB"], "AVALON_PROJECT": instance.context.data["projectName"], "AVALON_ASSET": instance.context.data["asset"], "AVALON_TASK": instance.context.data["task"], From 89d69f7c94db6558b8da06014ef0a9861130b585 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 16:02:50 +0000 Subject: [PATCH 081/244] Remove redundant instance_skeleton_data code. --- .../plugins/publish/submit_publish_job.py | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 82971daee5..4e9df976cd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -321,7 +321,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. @@ -338,151 +337,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Skipping local instance.") return - data = instance.data.copy() - context = instance.context - self.context = context - self.anatomy = instance.context.data["anatomy"] - - asset = data.get("asset") or context.data["asset"] - subset = data.get("subset") - - start = instance.data.get("frameStart") - if start is None: - start = context.data["frameStart"] - - end = instance.data.get("frameEnd") - if end is None: - end = context.data["frameEnd"] - - handle_start = instance.data.get("handleStart") - if handle_start is None: - handle_start = context.data["handleStart"] - - handle_end = instance.data.get("handleEnd") - if handle_end is None: - handle_end = context.data["handleEnd"] - - fps = instance.data.get("fps") - if fps is None: - fps = context.data["fps"] - - if data.get("extendFrames", False): - start, end = self._extend_frames( - asset, - subset, - start, - end, - data["overrideExistingFrame"]) - - try: - source = data["source"] - except KeyError: - source = context.data["currentFile"] - - success, rootless_path = ( - self.anatomy.find_root_template_from_path(source) - ) - if success: - source = rootless_path - - else: - # `rootless_path` is not set to `source` if none of roots match - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues." - ).format(source)) - - family = "render" - if ("prerender" in instance.data["families"] or - "prerender.farm" in instance.data["families"]): - family = "prerender" - families = [family] - - # pass review to families if marked as review - do_not_add_review = False - if data.get("review"): - families.append("review") - elif data.get("review") is False: - self.log.debug("Instance has review explicitly disabled.") - do_not_add_review = True - - instance_skeleton_data = { - "family": family, - "subset": subset, - "families": families, - "asset": asset, - "frameStart": start, - "frameEnd": end, - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStartHandle": start - handle_start, - "frameEndHandle": end + handle_end, - "comment": instance.data["comment"], - "fps": fps, - "source": source, - "extendFrames": data.get("extendFrames"), - "overrideExistingFrame": data.get("overrideExistingFrame"), - "pixelAspect": data.get("pixelAspect", 1), - "resolutionWidth": data.get("resolutionWidth", 1920), - "resolutionHeight": data.get("resolutionHeight", 1080), - "multipartExr": data.get("multipartExr", False), - "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True), - # map inputVersions `ObjectId` -> `str` so json supports it - "inputVersions": list(map(str, data.get("inputVersions", []))), - "colorspace": instance.data.get("colorspace"), - "stagingDir_persistent": instance.data.get( - "stagingDir_persistent", False - ) - } - - # skip locking version if we are creating v01 - instance_version = instance.data.get("version") # take this if exists - if instance_version != 1: - instance_skeleton_data["version"] = instance_version - - # transfer specific families from original instance to new render - for item in self.families_transfer: - if item in instance.data.get("families", []): - instance_skeleton_data["families"] += [item] - - # transfer specific properties from original instance based on - # mapping dictionary `instance_transfer` - for key, values in self.instance_transfer.items(): - if key in instance.data.get("families", []): - for v in values: - instance_skeleton_data[v] = instance.data.get(v) - - # look into instance data if representations are not having any - # which are having tag `publish_on_farm` and include them - for repre in instance.data.get("representations", []): - staging_dir = repre.get("stagingDir") - if staging_dir: - success, rootless_staging_dir = ( - self.anatomy.find_root_template_from_path( - staging_dir - ) - ) - if success: - repre["stagingDir"] = rootless_staging_dir - else: - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(staging_dir)) - repre["stagingDir"] = staging_dir - - if "publish_on_farm" in repre.get("tags"): - # create representations attribute of not there - if "representations" not in instance_skeleton_data.keys(): - instance_skeleton_data["representations"] = [] - - instance_skeleton_data["representations"].append(repre) - - instances = None - assert data.get("expectedFiles"), ("Submission from old Pype version" - " - missing expectedFiles") - anatomy = instance.context.data["anatomy"] instance_skeleton_data = create_skeleton_instance( From 9d612e1f0f9c5deb442931ba2cf633c112088518 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 17 Feb 2024 03:25:05 +0000 Subject: [PATCH 082/244] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 9e1bd39b3a..64d8075c4a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.4" +__version__ = "3.18.7-nightly.5" From 6657b847b8ffd91e77b37e9639cef0b839dc720c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Feb 2024 03:25:42 +0000 Subject: [PATCH 083/244] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bc0e00f740..4d48212d4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.1 - 3.15.10 - 3.15.10-nightly.2 - - 3.15.10-nightly.1 validations: required: true - type: dropdown From 6aa534dbc4af1a8f59381617332c7db16c8dd40c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Feb 2024 16:44:17 +0100 Subject: [PATCH 084/244] fix value lowering in postlaunch hook --- openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index 5c780a51c4..1876ff20eb 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -132,7 +132,7 @@ class PostFtrackHook(PostLaunchHook): if key in already_tested: continue - value = value.lower() + value = [i.lower() for i in value] if actual_status in value or "__any__" in value: if key != "__ignore__": next_status_name = key From e1f5bdb5a9a2c7225583ba7b2a164f548d76d8d4 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 20 Feb 2024 11:27:12 +0000 Subject: [PATCH 085/244] Removed redundant option in aov list --- .../entities/schemas/projects_schema/schema_project_blender.json | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 13e460b74c..2ffdc6070d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -126,7 +126,6 @@ "multiselection": true, "defaults": "empty", "enum_items": [ - {"empty": "< empty >"}, {"combined": "Combined"}, {"z": "Z"}, {"mist": "Mist"}, From b0499edb1a53f17ffa4785909cefd444b3702d3a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 20 Feb 2024 13:05:15 +0000 Subject: [PATCH 086/244] [Automated] Release --- CHANGELOG.md | 401 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 403 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009150ae7d..4ec3448570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,407 @@ # Changelog +## [3.18.7](https://github.com/ynput/OpenPype/tree/3.18.7) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.6...3.18.7) + +### **🆕 New features** + + +
+Chore: Wrapper for click proposal #5928 + +This is a proposal how to resolve issues with `click` python module. Issue https://github.com/ynput/OpenPype/issues/5921 reported that in Houdini 20+ is our click clashing with click in houdini, where is expected higher version. We can't update our version to support older pythons (NOTE older Python 3). + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Add repair action to hidden joints validator #6214 + +Joints Hidden is missing repair action, this adds it back + + +___ + +
+ + +
+Blender: output node and EXR #6086 + +Output node now works correctly for Multilayer EXR and keeps existing links. The output now is handled entirely by the compositor node tree. + + +___ + +
+ + +
+AYON Switch tool: Keep version after switch #6104 + +Keep version if only representation did change. The AYON variant of https://github.com/ynput/OpenPype/pull/4629 + + +___ + +
+ + +
+Loader AYON: Reset loader window on open #6170 + +Make sure loader tool is reset on each show. + + +___ + +
+ + +
+Publisher: Show message with error on action failure #6179 + +This PR adds support for the publisher to show error message from running actions.Errors from actions will otherwise be hidden from user in various console outputs.Also include card for when action is finished. + + +___ + +
+ + +
+AYON Applications: Remove djvview group from default applications #6188 + +The djv does not have group defined in models so the values are not used anywhere. + + +___ + +
+ + +
+General: added fallback for broken ffprobe return #6189 + +Customer provided .exr returned width and height equal to 0 which caused error in `extract_thumbnail`. This tries to use oiiotool to get metadata about file, in our case it read it correctly. + + +___ + +
+ + +
+Photoshop: High scaling in UIs #6190 + +Use `get_openpype_qt_app` to create `QApplication` in Photoshop. + + +___ + +
+ + +
+Ftrack: Status update settings are not case insensitive. #6195 + +Make values for project_settings/ftrack/events/status_update case insensitive. + + +___ + +
+ + +
+Thumbnail product filtering #6197 + +This PR introduces subset filtering for thumbnail extraction. This is to skip passes like zdepth which is not needed and can cause issues with extraction. Also speeds up publishing. + + +___ + +
+ + +
+TimersManager: Idle dialog always on top #6201 + +Make stop timer dialog always on tophttps://app.clickup.com/t/6658547/OP-8033 + + +___ + +
+ + +
+AfterEffects: added toggle for applying values from DB during creation #6204 + +Previously values (resolution, duration) from Asset (eg. DB) were applied explicitly when instance of `render` product type was created. This PR adds toggle to Settings to disable this. (This allows artist to publish non standard length of composition, disabling of `Validate Scene Settings` is still required.) + + +___ + +
+ + +
+Unreal: Update plugin commit #6208 + +Updated unreal plugin to latest main. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Traypublisher: editorial avoid audio tracks processing #6038 + +Avoiding audio tracks from EDL editorial publishing. + + +___ + +
+ + +
+Resolve Inventory offsets clips when swapping versions #6128 + +Swapped version retain the offset and IDT of the timelime clip.closes: https://github.com/ynput/OpenPype/issues/6125 + + +___ + +
+ + +
+Publisher window as dialog #6176 + +Changing back Publisher window to QDialog. + + +___ + +
+ + +
+Nuke: Validate write node fix error report - OP-8088 #6183 + +Report error was not printing the expected values from settings, but instead the values on the write node, leading to confusing messages like: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Correct: `rgb` > Wrong: `rgb` +``` +This PR changes the error report to: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Expected: `['rg']` > Current: `rgb` +``` + + + +___ + +
+ + +
+Nuke: Camera product type loaded is not updating - OP-7973 #6184 + +When updating the camera this error would appear: +``` +(...)openpype/hosts/nuke/plugins/load/load_camera_abc.py", line 142, in update + camera_node = nuke.toNode(object_name) +TypeError: toNode() argument 1 must be str, not Node +``` + + + +___ + +
+ + +
+AYON settings: Use bundle name as variant in dev mode #6187 + +Make sure the bundle name is used in dev mode for settings variant. + + +___ + +
+ + +
+Fusion: fix unwanted change to field name in Settings #6193 + +It should be `image_format` but in previous refactoring PR it fell back to original `output_formats` which caused enum not to show up and propagate into plugin. + + +___ + +
+ + +
+Bugfix: AYON menu disappeared when the workspace has been changed in 3dsMax #6200 + +AYON plugins are not correctly registered when switching to different workspaces. + + +___ + +
+ + +
+TrayPublisher: adding settings category to base creator classes #6202 + +Settings are resolving correctly as they suppose to. + + +___ + +
+ + +
+Nuke: expose knobs backward compatibility fix - OP-8164 #6211 + +Fix backwards compatibility for settings `project_settings/nuke/create/CreateWriteRender/exposed_knobs`. + + +___ + +
+ + +
+AE: fix local render doesn't push thumbnail to Ftrack #6212 + +Without thumbnail review is not clickable from main Versions list + + +___ + +
+ + +
+Nuke: openpype expose knobs validator - OP-8166 #6213 + +Fix exposed knobs validator for backwards compatibility with missing settings. + + +___ + +
+ + +
+Ftrack: Post-launch hook fix value lowering #6221 + +Fix lowerin of values in status mapping. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Maya: Remove `shelf` class and shelf build on maya `userSetup.py` #5837 + +Remove shelf builder logic. It appeared to be unused and had bugs. + + +___ + +
+ +### **Merged pull requests** + + +
+Max: updated implementation of save_scene + small QOL improvements to host #6186 + +- Removed `has_unsaved_changes` from Max host as it looks to have been unused and unimplemented. +- Added and implemented `workfile_has_unsaved_changes` to Max host. +- Mirrored the Houdini host to implement the above into `save_scene` publish for Max. +- Added a line to `startup.ms` which opens the usual 'default' menu inside of Max (see screenshots).Current (Likely opens this menu due to one or more of the startup scripts used to insert OP menu):New: + + +___ + +
+ + +
+Fusion: Use better resolution of Ayon apps on 4k display #6199 + +Changes size (makes it smaller) of Ayon apps (Workfiles, Loader) in Fusion on high definitions displays. + + +___ + +
+ + +
+Update CONTRIBUTING.md #6210 + +Updating contributing guidelines to reflect the EOL state of repository +___ + +
+ + +
+Deadline: Remove redundant instance_skeleton_data code - OP-8269 #6219 + +This PR https://github.com/ynput/OpenPype/pull/5186 re-introduced code about for the `instance_skeleton_data` but its actually not used since this variable gets overwritten later. + + +___ + +
+ + + + ## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6) diff --git a/openpype/version.py b/openpype/version.py index 64d8075c4a..a389280775 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.5" +__version__ = "3.18.7" diff --git a/pyproject.toml b/pyproject.toml index 453833aae2..eef6a2e978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.6" # OpenPype +version = "3.18.7" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f06658af081435a879f336b6d962dbc669aafe13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Feb 2024 13:06:09 +0000 Subject: [PATCH 087/244] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4d48212d4a..7fe6c79259 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.2 - 3.15.11-nightly.1 - 3.15.10 - - 3.15.10-nightly.2 validations: required: true - type: dropdown From 698e5db01e882f72f36d45563be5843c74494057 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Feb 2024 15:48:51 +0000 Subject: [PATCH 088/244] Working version of attributes on extractor --- openpype/hosts/maya/api/alembic.py | 1 + .../create/create_animation_pointcache.py | 221 +-------------- .../plugins/publish/collect_pointcache.py | 5 - .../plugins/publish/extract_pointcache.py | 262 ++++++++++++++---- .../defaults/project_settings/maya.json | 26 ++ .../schemas/schema_maya_create.json | 104 ------- .../schemas/schema_maya_publish.json | 179 ++++++++++-- openpype/tools/publisher/widgets/widgets.py | 2 +- 8 files changed, 404 insertions(+), 396 deletions(-) diff --git a/openpype/hosts/maya/api/alembic.py b/openpype/hosts/maya/api/alembic.py index 63f826299f..b657262b4d 100644 --- a/openpype/hosts/maya/api/alembic.py +++ b/openpype/hosts/maya/api/alembic.py @@ -70,6 +70,7 @@ def extract_alembic( worldSpace=False, writeColorSets=False, writeCreases=False, + writeNormals=False, writeFaceSets=False, writeUVSets=False, writeVisibility=False diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 353a4402ee..6fe078f2ad 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,26 +1,16 @@ from maya import cmds from openpype.hosts.maya.api import lib, plugin -from openpype.hosts.maya.api.alembic import ALEMBIC_ARGS from openpype.lib import ( BoolDef, - TextDef, NumberDef, - EnumDef, - UISeparatorDef, - UILabelDef, ) from openpype.pipeline import CreatedInstance def _get_animation_attr_defs(cls): - """Get Animation generic definitions. - - The line is blurry between what's "Animation" generic and "Alembic", - but the rule of thumb is that whatever "AlembicExport -h" accepts - is "Alembic" and the other ones are "Animation". - """ + """Get Animation generic definitions.""" defs = lib.collect_animation_defs() defs.extend( [ @@ -30,205 +20,16 @@ def _get_animation_attr_defs(cls): BoolDef( "includeParentHierarchy", label="Include Parent Hierarchy" ), + BoolDef( + "includeUserDefinedAttributes", + label="Include User Defined Attributes" + ), ] ) return defs -def _get_abc_export_flags(cls): - """Get two sets with the Alembic Export flags. - - Alembic flags are treated as booleans, so here we get all the possible - options, and work out a list with all the ones that can be toggled by - the user, and the ones defined in the settings. - """ - - # The Arguments that can be modified by the Publisher - abc_export_overrides = set(getattr(cls, "abc_export_overrides", set())) - - # What we have set in the Settings as defaults. - default_abc_export_flags = set(getattr(cls, "abc_export_flags", set())) - - # Set of un-toggleable flags, specified by the settings - abc_export_flags = { - arg - for arg in default_abc_export_flags - if arg not in abc_export_overrides - } - - # Set of all the available Alembic Export Flags - abc_boolean_flags = { - arg for arg, arg_type in ALEMBIC_ARGS.items() if arg_type is bool - } - - # Set of togglable flags - abc_export_toggleable_flags = { - arg for arg in abc_export_overrides if arg in abc_boolean_flags - } - return abc_export_flags, abc_export_toggleable_flags - - -def _get_animation_abc_attr_defs(cls): - """Get definitions relating to Alembic. - - An admin can define in settings the default arguments, which are then not - modifiable by the person publishing, unless they are added to the Alembic - Overrides setting, which is mapped to `abc_args_overrides`. - - Most of the Alembic Arguments are flags, treated as booleans, and there are - two possible lists: the defaults (from settings) and the the toggleable by - the user, these two define an EnumDef respectively. - - We use a combination of the two above to only show a muiltiselection - dropdown for booleans, and disabling the non-boolean arguments on the - interface. - - There's also a new separator so it's clearer what belongs to common - Animation publishes versus what is Almebic specific, the line is blurry, - but the rule of thumb is that whatever "AlembicExport -h" accepts is - "Alembic" and the other ones are "Animation". - """ - abc_defs = None - abc_defs = [ - UISeparatorDef("sep_alembic_options"), - UILabelDef("Alembic Options"), - ] - - # The Arguments that can be modified by the Publisher - abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) - - # What we have set in the Settings as defaults. - default_abc_export_flags = set(getattr(cls, "abc_export_flags", set())) - - ( - abc_export_flags, - abc_export_toggleable_flags, - ) = _get_abc_export_flags(cls) - - abc_defs.append( - EnumDef( - "abcExportFlags", - list(abc_export_flags), - default=list(abc_export_flags), - multiselection=True, - label="Settings Defined Arguments", - disabled=True, - hidden=True, - ) - ) - - # Only display Boolan flags that the Admin defined as overrideable - abc_export_toggleable_defaults = [ - arg - for arg in abc_export_toggleable_flags - if arg in default_abc_export_flags - ] - abc_defs.append( - EnumDef( - "abcExportTogglableFlags", - list(abc_export_toggleable_flags) - if abc_export_toggleable_flags - else [""], - default=abc_export_toggleable_defaults, - multiselection=True, - label="Export Flags", - disabled=True if not abc_export_toggleable_flags else False, - ) - ) - - abc_defs.append( - TextDef( - "attr", - label="Custom Attributes", - default=getattr(cls, "attr", None), - placeholder="attr1; attr2; ...", - disabled=True if "attr" not in abc_args_overrides else False, - ) - ) - - abc_defs.append( - TextDef( - "attrPrefix", - label="Custom Attributes Prefix", - default=getattr(cls, "attrPrefix", None), - placeholder="prefix1; prefix2; ...", - disabled=True if "attrPrefix" not in abc_args_overrides else False, - ) - ) - - abc_defs.append( - EnumDef( - "dataFormat", - label="Data Format", - default=getattr(cls, "dataFormat", None), - items=["ogawa", "HDF"], - disabled=True if "dataFormat" not in abc_args_overrides else False, - ) - ) - - abc_defs.append( - NumberDef( - "preRollStartFrame", - label="Start frame for preroll", - default=getattr(cls, "preRollStartFrame", None), - tooltip=( - "The frame to start scene evaluation at. This is used to set" - " the starting frame for time dependent translations and can" - " be used to evaluate run-up that isn't actually translated." - ), - disabled=True - if "preRollStartFrame" not in abc_args_overrides - else False, - ) - ) - - return abc_defs - - -def _ensure_defaults(cls, instance_data): - """Ensure we get default values when an attribute is not overrideable. - - In instances where an attribute used to be modifiable, and then was locked - again, we want to make sure that we pass the default (what's on the - settings) instead of any value that might have been stored in the scene - when the attribute was modifiable. - """ - abc_args_overrides = set(getattr(cls, "abc_args_overrides", set())) - creator_attr = instance_data["creator_attributes"] - attr_default = getattr(cls, "attr", "") - - if "attr" not in abc_args_overrides: - creator_attr["attr"] = attr_default - - if "attrPrefix" not in abc_args_overrides: - creator_attr["attrPrefix"] = getattr(cls, "attrPrefix", "") - - if "dataFormat" not in abc_args_overrides: - creator_attr["dataFormat"] = getattr(cls, "dataFormat", "") - - if "preRollStartFrame" not in abc_args_overrides: - creator_attr["preRollStartFrame"] = getattr( - cls, "preRollStartFrame", "" - ) - - ( - abc_boolean_defaults, - abc_boolean_overrides, - ) = _get_abc_export_flags(cls) - - creator_attr["abcExportFlags"] = list(abc_boolean_defaults) - - if creator_attr.get("abcExportTogglableFlags", []): - abc_boolean_args = creator_attr["abcExportTogglableFlags"].copy() - - creator_attr["abcExportTogglableFlags"] = [ - arg for arg in abc_boolean_args if arg not in abc_boolean_overrides - ] - - return instance_data - - class CreateAnimation(plugin.MayaHiddenCreator): """Animation output for character rigs @@ -257,18 +58,12 @@ class CreateAnimation(plugin.MayaHiddenCreator): for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) - _ensure_defaults(self, node_data) - created_instance = CreatedInstance.from_existing(node_data, self) self._add_instance_to_context(created_instance) def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) - abc_defs = _get_animation_abc_attr_defs(self) - if abc_defs: - defs.extend(abc_defs) - return defs @@ -292,18 +87,12 @@ class CreatePointCache(plugin.MayaCreator): for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) - _ensure_defaults(self, node_data) - created_instance = CreatedInstance.from_existing(node_data, self) self._add_instance_to_context(created_instance) def get_instance_attr_defs(self): super(CreatePointCache, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) - abc_defs = _get_animation_abc_attr_defs(self) - if abc_defs: - defs.extend(abc_defs) - return defs def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 389f16a5bf..5578a57f31 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -10,11 +10,6 @@ class CollectPointcache(pyblish.api.InstancePlugin): families = ["pointcache"] label = "Collect Pointcache" hosts = ["maya"] - legacy_settings = { - "write_color_sets": "writeColorSets", - "write_face_sets": "writeFaceSets", - "include_user_defined_attributes": "includeUserDefinedAttributes" - } def process(self, instance): if instance.data.get("farm"): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 4bfd5e2fe0..26957b02f5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -9,9 +9,18 @@ from openpype.hosts.maya.api.lib import ( maintained_selection, iter_visible_nodes_in_range, ) +from openpype.lib import ( + BoolDef, + TextDef, + NumberDef, + EnumDef, + UISeparatorDef, + UILabelDef, +) +from openpype.pipeline.publish import OpenPypePyblishPluginMixin -class ExtractAlembic(publish.Extractor): +class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, @@ -25,6 +34,19 @@ class ExtractAlembic(publish.Extractor): hosts = ["maya"] families = ["pointcache", "model", "vrayproxy.alembic"] targets = ["local", "remote"] + flags = [] + attr = [] + attrPrefix = [] + dataFormat = "ogawa" + melPerFrameCallback = "" + melPostJobCallback = "" + preRollStartFrame = 0 + pythonPerFrameCallback = "" + pythonPostJobCallback = "" + stripNamespaces = -1 + userAttr = "" + userAttrPrefix = "" + export_overrides = [] def process(self, instance): if instance.data.get("farm"): @@ -37,20 +59,22 @@ class ExtractAlembic(publish.Extractor): start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - # Collect Alembic Arguments - creator_attributes = instance.data.get("creator_attributes") - abc_flags = creator_attributes.get( - "abcExportTogglableFlags" - ) + creator_attributes.get("abcExportTogglableFlags") + attribute_values = self.get_attr_values_from_data( + instance.data + ) - abc_attrs = [ + attrs = [ attr.strip() - for attr in creator_attributes.get("attr", "").split(";") + for attr in attribute_values.get("attr", "").split(";") + if attr.strip() ] + attrs += instance.data.get("userDefinedAttributes", []) + attrs += ["cbId"] - abc_attr_prefixes = [ - attr_prefix.strip() - for attr_prefix in instance.data.get("attrPrefix", "").split(";") + attr_prefixes = [ + attr.strip() + for attr in attribute_values.get("attrPrefix", "").split(";") + if attr.strip() ] self.log.debug("Extracting pointcache...") @@ -60,51 +84,52 @@ class ExtractAlembic(publish.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) - abc_root = None + root = None if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - abc_root = roots + root = roots - abc_writeUVSets = False - - if int(cmds.about(version=True)) >= 2017: - # Since Maya 2017 alembic supports multiple uv sets - write them. - if "writeUVSets" in abc_flags: - abc_writeUVSets = True - - extract_abc_args = { + args = { "file": path, - "attr": abc_attrs, - "attrPrefix": abc_attr_prefixes, - "dataFormat": creator_attributes.get("dataFormat", "ogawa"), + "attr": attrs, + "attrPrefix": attr_prefixes, + "dataFormat": attribute_values.get("dataFormat", "ogawa"), "endFrame": end, - "eulerFilter": True if "eulerFilter" in abc_flags else False, - "noNormals": True if "noNormals" in abc_flags else False, - "preRoll": True if "preRoll" in abc_flags else False, - "preRollStartFrame": creator_attributes.get( + "eulerFilter": False, + "noNormals": False, + "preRoll": False, + "preRollStartFrame": attribute_values.get( "preRollStartFrame", 0 ), - "renderableOnly": True if "renderableOnly" in abc_flags else False, - "root": abc_root, - "selection": True, # Should this stay like so? + "renderableOnly": False, + "root": root, + "selection": True, "startFrame": start, - "step": creator_attributes.get("step", 1.0), - "stripNamespaces": True, - "uvWrite": True if "uvWrite" in abc_flags else False, - "verbose": True if "verbose" in abc_flags else False, - "wholeFrameGeo": True if "wholeFrameGeo" in abc_flags else False, - "worldSpace": True if "worldSpace" in abc_flags else False, - "writeColorSets": True if "writeColorSets" in abc_flags else False, - "writeCreases": True if "writeCreases" in abc_flags else False, - "writeFaceSets": True if "writeFaceSets" in abc_flags else False, - "writeUVSets": abc_writeUVSets, - "writeVisibility": True - if "writeVisibility" in abc_flags - else False, + "step": instance.data.get( + "creator_attributes", {} + ).get("step", 1.0), + "stripNamespaces": False, + "uvWrite": False, + "verbose": False, + "wholeFrameGeo": False, + "worldSpace": False, + "writeColorSets": False, + "writeCreases": False, + "writeFaceSets": False, + "writeUVSets": False, + "writeVisibility": False, } + # Export flags are defined as default enabled flags excluding flags + # that are exposed to the user, plus the flags the user has enabled + # when publishing. + flags = list(set(self.flags) - set(self.export_overrides)) + flags += attribute_values["flag_overrides"] + for flag in flags: + args[flag] = True + if instance.data.get("visibleOnly", False): # If we only want to include nodes that are visible in the frame # range then we need to do our own check. Alembic's `visibleOnly` @@ -121,10 +146,10 @@ class ExtractAlembic(publish.Extractor): cmds.select(nodes, noExpand=True) self.log.debug( "Running `extract_alembic` with the arguments: {}".format( - extract_abc_args + args ) ) - extract_alembic(**extract_abc_args) + extract_alembic(**args) if "representations" not in instance.data: instance.data["representations"] = [] @@ -148,17 +173,17 @@ class ExtractAlembic(publish.Extractor): return path = path.replace(".abc", "_proxy.abc") - extract_abc_args["file"] = path + args["file"] = path if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - extract_abc_args["root"] = instance.data["proxyRoots"] + args["root"] = instance.data["proxyRoots"] with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(instance.data["proxy"]) - extract_alembic(**extract_abc_args) + extract_alembic(**args) representation = { "name": "proxy", @@ -172,9 +197,146 @@ class ExtractAlembic(publish.Extractor): def get_members_and_roots(self, instance): return instance[:], instance.data.get("setMembers") + @classmethod + def get_attribute_defs(cls): + override_defs = { + "attr": { + "def": TextDef, + "kwargs": { + "label": "Custom Attributes", + "placeholder": "attr1; attr2; ...", + } + }, + "attrPrefix": { + "def": TextDef, + "kwargs": { + "label": "Custom Attributes Prefix", + "placeholder": "prefix1; prefix2; ...", + } + }, + "dataFormat": { + "def": EnumDef, + "kwargs": { + "label": "Data Format", + "items": ["ogawa", "HDF"], + } + }, + "melPerFrameCallback": { + "def": TextDef, + "kwargs": { + "label": "melPerFrameCallback", + } + }, + "melPostJobCallback": { + "def": TextDef, + "kwargs": { + "label": "melPostJobCallback", + } + }, + "preRollStartFrame": { + "def": NumberDef, + "kwargs": { + "label": "Start frame for preroll", + "tooltip": ( + "The frame to start scene evaluation at. This is used" + " to set the starting frame for time dependent " + "translations and can be used to evaluate run-up that" + " isn't actually translated." + ), + } + }, + "pythonPerFrameCallback": { + "def": TextDef, + "kwargs": { + "label": "pythonPerFrameCallback", + } + }, + "pythonPostJobCallback": { + "def": TextDef, + "kwargs": { + "label": "pythonPostJobCallback", + } + }, + "stripNamespaces": { + "def": NumberDef, + "kwargs": { + "label": "stripNamespaces", + "tooltip": ( + "If this flag is present namespaces will be stripped " + "off of the node before being written to Alembic. The " + "int after the flag specifies how many namespaces will" + " be stripped off of the node name. Be careful that " + "the new stripped name does not collide with other " + "sibling node names.\n\nExamples:\n taco:foo:bar would" + " be written as just bar with -sn 0\ntaco:foo:bar " + "would be written as foo:bar with -sn 1" + ), + } + }, + "userAttr": { + "def": TextDef, + "kwargs": { + "label": "userAttr", + } + }, + "userAttrPrefix": { + "def": TextDef, + "kwargs": { + "label": "userAttrPrefix", + } + }, + "visibleOnly": { + "def": BoolDef, + "kwargs": { + "label": "Visible Only", + } + } + } + + defs = super(ExtractAlembic, cls).get_attribute_defs() + + defs.extend([ + UISeparatorDef("sep_alembic_options"), + UILabelDef("Alembic Options"), + ]) + + # The Arguments that can be modified by the Publisher + export_overrides = set(getattr(cls, "export_overrides", set())) + + # What we have set in the Settings as defaults. + flags = set(getattr(cls, "flags", set())) + + enabled_flags = [x for x in flags if x in export_overrides] + flag_overrides = export_overrides - set(override_defs.keys()) + defs.append( + EnumDef( + "flag_overrides", + flag_overrides, + default=enabled_flags, + multiselection=True, + label="Export Flags", + ) + ) + + for key, value in override_defs.items(): + if key not in export_overrides: + continue + + kwargs = value["kwargs"] + kwargs["default"] = getattr(cls, key, None) + defs.append( + value["def"](key, **value["kwargs"]) + ) + + defs.append( + UISeparatorDef("sep_alembic_options") + ) + + return defs + class ExtractAnimation(ExtractAlembic): - label = "Extract Animation" + label = "Extract Animation (Alembic)" families = ["animation"] def get_members_and_roots(self, instance): diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 214a4f737a..1319a41f51 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1150,6 +1150,32 @@ "pointcache", "model", "vrayproxy.alembic" + ], + "flags": [ + "stripNamespaces", + "writeColorSets", + "writeNormals", + "worldSpace" + ], + "attr": "", + "attrPrefix": "", + "dataFormat": "ogawa", + "melPerFrameCallback": "", + "melPostJobCallback": "", + "preRollStartFrame": 0, + "pythonPerFrameCallback": "", + "pythonPostJobCallback": "", + "userAttr": "", + "userAttrPrefix": "", + "visibleOnly": false, + "export_overrides": [ + "attr", + "attrPrefix", + "worldSpace", + "writeNormals", + "writeFaceSets", + "renderableOnly", + "visibleOnly" ] }, "ExtractObj": { 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 6f9ead7fec..7539d82436 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 @@ -158,114 +158,10 @@ "key": "refresh", "label": "Refresh" }, - { - "type": "enum", - "key": "abc_export_flags", - "multiselection": true, - "label": "Export Flags (.abc)", - "enum_items": [ - {"autoSubd": "autoSubd"}, - {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, - {"eulerFilter": "eulerFilter"}, - {"noNormals": "noNormals"}, - {"preRoll": "preRoll"}, - {"renderableOnly": "renderableOnly"}, - {"selection": "selection"}, - {"stripNamespaces": "stripNamespaces"}, - {"uvWrite": "uvWrite"}, - {"uvsOnly": "uvsOnly"}, - {"verbose": "verbose"}, - {"wholeFrameGeo": "wholeFrameGeo"}, - {"worldSpace": "worldSpace"}, - {"writeColorSets": "writeColorSets"}, - {"writeCreases": "writeCreases"}, - {"writeFaceSets": "writeFaceSets"}, - {"writeUVSets": "writeUVSets"}, - {"writeVisibility": "writeVisibility"} - ] - }, - { - "type": "enum", - "key": "abc_export_overrides", - "multiselection": true, - "label": "Export Overrides (.abc)", - "enum_items": [ - {"attr": "attr"}, - {"attrPrefix": "attrPrefix"}, - {"autoSubd": "autoSubd"}, - {"dataFormat": "dataFormat"}, - {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, - {"endFrame": "endFrame"}, - {"eulerFilter": "eulerFilter"}, - {"frameRange": "frameRange"}, - {"frameRelativeSample": "frameRelativeSample"}, - {"melPerFrameCallback": "melPerFrameCallback"}, - {"melPostJobCallback": "melPostJobCallback"}, - {"noNormals": "noNormals"}, - {"preRoll": "preRoll"}, - {"preRollStartFrame": "preRollStartFrame"}, - {"pythonPerFrameCallback": "pythonPerFrameCallback"}, - {"pythonPostJobCallback": "pythonPostJobCallback"}, - {"renderableOnly": "renderableOnly"}, - {"root": "root"}, - {"selection": "selection"}, - {"startFrame": "startFrame"}, - {"step": "step"}, - {"stripNamespaces": "stripNamespaces"}, - {"userAttr": "userAttr"}, - {"userAttrPrefix": "userAttrPrefix"}, - {"uvWrite": "uvWrite"}, - {"uvsOnly": "uvsOnly"}, - {"verbose": "verbose"}, - {"wholeFrameGeo": "wholeFrameGeo"}, - {"worldSpace": "worldSpace"}, - {"writeColorSets": "writeColorSets"}, - {"writeCreases": "writeCreases"}, - {"writeFaceSets": "writeFaceSets"}, - {"writeUVSets": "writeUVSets"}, - {"writeVisibility": "writeVisibility"} - ] - }, - { - "type": "number", - "key": "step", - "label": "Step", - "minimum": 0.0, - "decimal": 4 - }, { "type": "boolean", "key": "visibleOnly", "label": "Visible Only default" - }, - { - "type": "text", - "key": "attr", - "label": "Attributes" - }, - { - "type": "text", - "key": "attrPrefix", - "label": "Attr Prefix" - }, - { - "type": "enum", - "key": "dataFormat", - "label": "Data Format", - "enum_items": [ - { - "ogawa": "ogawa" - }, - { - "HDF": "HDF" - } - ] - }, - { - "type": "number", - "key": "preRollStartFrame", - "label": "Pre Roll Start Frame", - "minimum": 0 } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index d2e7c51e24..9af9dbb32b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -750,26 +750,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "ExtractAlembic", - "label": "Extract Alembic", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - } - ] - }, { "type": "dict", "collapsible": true, @@ -1008,6 +988,165 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractAlembic", + "label": "Extract Pointcache/Animation", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Export Defaults" + }, + { + "type": "enum", + "key": "flags", + "multiselection": true, + "label": "Flags", + "enum_items": [ + {"autoSubd": "autoSubd"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, + {"eulerFilter": "eulerFilter"}, + {"noNormals": "noNormals"}, + {"preRoll": "preRoll"}, + {"renderableOnly": "renderableOnly"}, + {"stripNamespaces": "stripNamespaces"}, + {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, + {"verbose": "verbose"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeFaceSets": "writeFaceSets"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + }, + { + "type": "text", + "key": "attr", + "label": "Attributes" + }, + { + "type": "text", + "key": "attrPrefix", + "label": "Attr Prefix" + }, + { + "type": "enum", + "key": "dataFormat", + "label": "Data Format", + "enum_items": [ + { + "ogawa": "ogawa" + }, + { + "HDF": "HDF" + } + ] + }, + { + "type": "text", + "key": "melPerFrameCallback", + "label": "melPerFrameCallback" + }, + { + "type": "text", + "key": "melPostJobCallback", + "label": "melPostJobCallback" + }, + { + "type": "number", + "key": "preRollStartFrame", + "label": "Pre Roll Start Frame", + "minimum": 0 + }, + { + "type": "text", + "key": "pythonPerFrameCallback", + "label": "pythonPerFrameCallback" + }, + { + "type": "text", + "key": "pythonPostJobCallback", + "label": "pythonPostJobCallback" + }, + { + "type": "text", + "key": "userAttr", + "label": "userAttr" + }, + { + "type": "text", + "key": "userAttrPrefix", + "label": "userAttrPrefix" + }, + { + "type": "boolean", + "key": "visibleOnly", + "label": "visibleOnly" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "These attributes are exposed to the user when publishing with default values from above." + }, + { + "type": "enum", + "key": "export_overrides", + "multiselection": true, + "label": "Export Overrides", + "enum_items": [ + {"attr": "attr"}, + {"attrPrefix": "attrPrefix"}, + {"autoSubd": "autoSubd"}, + {"dataFormat": "dataFormat"}, + {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, + {"eulerFilter": "eulerFilter"}, + {"melPerFrameCallback": "melPerFrameCallback"}, + {"melPostJobCallback": "melPostJobCallback"}, + {"noNormals": "noNormals"}, + {"preRoll": "preRoll"}, + {"preRollStartFrame": "preRollStartFrame"}, + {"pythonPerFrameCallback": "pythonPerFrameCallback"}, + {"pythonPostJobCallback": "pythonPostJobCallback"}, + {"renderableOnly": "renderableOnly"}, + {"stripNamespaces": "stripNamespaces"}, + {"userAttr": "userAttr"}, + {"userAttrPrefix": "userAttrPrefix"}, + {"uvWrite": "uvWrite"}, + {"uvsOnly": "uvsOnly"}, + {"verbose": "verbose"}, + {"visibleOnly": "visibleOnly"}, + {"wholeFrameGeo": "wholeFrameGeo"}, + {"worldSpace": "worldSpace"}, + {"writeColorSets": "writeColorSets"}, + {"writeCreases": "writeCreases"}, + {"writeFaceSets": "writeFaceSets"}, + {"writeNormals": "writeNormals"}, + {"writeUVSets": "writeUVSets"}, + {"writeVisibility": "writeVisibility"} + ] + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index ecccc4e0c8..2b497707fa 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -1485,7 +1485,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget): class PublishPluginAttrsWidget(QtWidgets.QWidget): """Widget showing publsish plugin attributes for selected instances. - Attributes are defined on publish plugins. Publihs plugin may define + Attributes are defined on publish plugins. Publish plugin may define attribute definitions but must inherit `OpenPypePyblishPluginMixin` (~/openpype/pipeline/publish). At the moment requires to implement `get_attribute_defs` and `convert_attribute_values` class methods. From 4e8ea037b37f9a02787f0fef2a9a4fa1dca364e0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Feb 2024 16:39:01 +0000 Subject: [PATCH 089/244] Tidy up changes. --- .../defaults/project_settings/maya.json | 43 +----- .../schemas/schema_maya_create.json | 123 +----------------- website/docs/artist_hosts_maya.md | 18 --- 3 files changed, 4 insertions(+), 180 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 1319a41f51..03c6f79eff 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -559,28 +559,10 @@ "CreateAnimation": { "default_variants": [], "step": 1.0, - "abc_export_flags": [ - "writeColorSets", - "worldSpace", - "writeNormals" - ], - "abc_export_overrides": [ - "step", - "includeParentHierarchy", - "writeNormals", - "includeUserDefinedAttributes", - "attr", - "attrPrefix" - ], "includeParentHierarchy": false, "farm": false, "priority": 50, - "attr": "", - "attrPrefix": "", - "dataFormat": "ogawa", - "preRollStartFrame": 0, - "refresh": false, - "visibleOnly": false + "refresh": false }, "CreateModel": { "enabled": true, @@ -598,33 +580,10 @@ "Main" ], "step": 1.0, - "abc_export_flags": [ - "selection", - "uvWrite", - "writeCreases", - "writeVisibility" - ], - "abc_export_overrides": [ - "attr", - "attrPrefix", - "step", - "writeColorSets", - "writeFaceSets", - "renderableOnly", - "worldSpace" - ], - "renderableOnly": false, - "visibleOnly": false, "includeParentHierarchy": false, "farm": false, "priority": 50, - "attr": "cbId", - "attrPrefix": "", - "dataFormat": "ogawa", - "preRollStartFrame": 0, "refresh": false, - "write_color_sets": false, - "write_face_sets": false, "include_user_defined_attributes": false }, "CreateProxyAlembic": { 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 7539d82436..16355eb1a2 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 @@ -160,8 +160,8 @@ }, { "type": "boolean", - "key": "visibleOnly", - "label": "Visible Only default" + "key": "include_user_defined_attributes", + "label": "Include User Defined Attributes" } ] }, @@ -213,74 +213,6 @@ "label": "Default Variants", "object_type": "text" }, - { - "type": "enum", - "key": "abc_export_flags", - "multiselection": true, - "label": "Export Flags (.abc)", - "enum_items": [ - {"autoSubd": "autoSubd"}, - {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, - {"eulerFilter": "eulerFilter"}, - {"noNormals": "noNormals"}, - {"preRoll": "preRoll"}, - {"renderableOnly": "renderableOnly"}, - {"selection": "selection"}, - {"stripNamespaces": "stripNamespaces"}, - {"uvWrite": "uvWrite"}, - {"uvsOnly": "uvsOnly"}, - {"verbose": "verbose"}, - {"wholeFrameGeo": "wholeFrameGeo"}, - {"worldSpace": "worldSpace"}, - {"writeColorSets": "writeColorSets"}, - {"writeCreases": "writeCreases"}, - {"writeFaceSets": "writeFaceSets"}, - {"writeUVSets": "writeUVSets"}, - {"writeVisibility": "writeVisibility"} - ] - }, - { - "type": "enum", - "key": "abc_export_overrides", - "multiselection": true, - "label": "Export Overrides (.abc)", - "enum_items": [ - {"attr": "attr"}, - {"attrPrefix": "attrPrefix"}, - {"autoSubd": "autoSubd"}, - {"dataFormat": "dataFormat"}, - {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, - {"endFrame": "endFrame"}, - {"eulerFilter": "eulerFilter"}, - {"frameRange": "frameRange"}, - {"frameRelativeSample": "frameRelativeSample"}, - {"melPerFrameCallback": "melPerFrameCallback"}, - {"melPostJobCallback": "melPostJobCallback"}, - {"noNormals": "noNormals"}, - {"preRoll": "preRoll"}, - {"preRollStartFrame": "preRollStartFrame"}, - {"pythonPerFrameCallback": "pythonPerFrameCallback"}, - {"pythonPostJobCallback": "pythonPostJobCallback"}, - {"renderableOnly": "renderableOnly"}, - {"root": "root"}, - {"selection": "selection"}, - {"startFrame": "startFrame"}, - {"step": "step"}, - {"stripNamespaces": "stripNamespaces"}, - {"userAttr": "userAttr"}, - {"userAttrPrefix": "userAttrPrefix"}, - {"uvWrite": "uvWrite"}, - {"uvsOnly": "uvsOnly"}, - {"verbose": "verbose"}, - {"wholeFrameGeo": "wholeFrameGeo"}, - {"worldSpace": "worldSpace"}, - {"writeColorSets": "writeColorSets"}, - {"writeCreases": "writeCreases"}, - {"writeFaceSets": "writeFaceSets"}, - {"writeUVSets": "writeUVSets"}, - {"writeVisibility": "writeVisibility"} - ] - }, { "type": "number", "key": "step", @@ -288,16 +220,6 @@ "minimum": 0.0, "decimal": 4 }, - { - "type": "boolean", - "key": "renderableOnly", - "label": "Renderable Only default" - }, - { - "type": "boolean", - "key": "visibleOnly", - "label": "Visible Only default" - }, { "type": "boolean", "key": "includeParentHierarchy", @@ -314,54 +236,15 @@ "label": "Priority default", "minimum": 0 }, - { - "type": "text", - "key": "attr", - "label": "Attr default" - }, - { - "type": "text", - "key": "attrPrefix", - "label": "Attr Prefix default" - }, - { - "type": "enum", - "key": "dataFormat", - "label": "Data Format default", - "enum_items": [ - { - "ogawa": "ogawa" - }, - { - "HDF": "HDF" - } - ] - }, - { - "type": "number", - "key": "preRollStartFrame", - "label": "Pre Roll Start Frame default", - "minimum": 0 - }, { "type": "boolean", "key": "refresh", "label": "Refresh default" }, - { - "type": "boolean", - "key": "write_color_sets", - "label": "DEPRECATED! Write Color Sets" - }, - { - "type": "boolean", - "key": "write_face_sets", - "label": "DEPRECATED! Write Face Sets" - }, { "type": "boolean", "key": "include_user_defined_attributes", - "label": "DEPRECATED! Include User Defined Attributes" + "label": "Include User Defined Attributes" } ] }, diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 60e28f6a05..364461f4b6 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -347,24 +347,6 @@ Example setup: - **Include User Defined Attribudes**: include all user defined attributes in the publish. - **Farm**: if your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle this attribute in created pointcache instance to True. - **Priority**: Farm priority. -- **Euler Filter**: Apply Euler filter while sampling rotations. -- **No Normals**: Present normal data for Alembic poly meshes will not be written. -- **Pre Roll**: This frame range will not be sampled. -- **Renderable Only**: Non-renderable hierarchy (invisible, or templated) will not be written out. -- **UV Write**: Uv data for PolyMesh and SubD shapes will be written to the Alembic file. Only the current uv map is used. -- **Write Color Sets**: Write all color sets on MFnMeshes as color 3 or color 4 indexed geometry parameters with face varying scope. -- **Write Face Sets**: Write all Face sets on MFnMeshes. -- **Whole Frame Geo**: Data for geometry will only be written out on whole frames. -- **World Space**: Any root nodes will be stored in world space. -- **Write Visibility**: Visibility state will be stored in the Alembic file. Otherwise everything written out is treated as visible. -- **Write UV Sets**: Write all uv sets on MFnMeshes as vector 2 indexed geometry parameters with face varying scope. -- **Write Creases**: If the mesh has crease edges or crease vertices, the mesh (OPolyMesh) would now be written out as an OSubD and crease info will be stored in the Alembic file. Otherwise, creases info won't be preserved in Alembic file unless a custom Boolean attribute SubDivisionMesh has been added to mesh node and its value is true. -- **Data Format**: The data format to use to write the file. Can be either "HDF" or "Ogawa". -- **Strip Namespaces**: Namespaces will be stripped off of the node before being written to Alembic. The int specifies how many namespaces will be stripped off of the node name. Be careful that the new stripped name does not collide with other sibling node names. -- **Verbose**: Prints the current frame that is being evaluated. -- **Pre Roll Start Frame**: The frame to start scene evaluation at. This is used to set the starting frame for time dependent translations and can be used to evaluate run-up that isn't actually translated. -- **Include Parent Hierarchy**: Set the root nodes if we don't want to include parents. The roots are to be considered the ones that are the actual direct members of the set. -- **Visible Only**: Does not filter out nodes that are only hidden on some frames as it counts "animated" or "connected" visibilities as if it's always visible. ### Loading Point Caches From 70982d9d799c1b31e7f95a2ddff06f2f1bd1bd09 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Feb 2024 18:31:16 +0000 Subject: [PATCH 090/244] Backwards compatibility wip --- .../create/create_animation_pointcache.py | 18 ++++++++++++++++++ .../maya/plugins/publish/collect_animation.py | 7 ++++++- .../maya/plugins/publish/collect_pointcache.py | 5 +++++ .../maya/plugins/publish/extract_pointcache.py | 17 ----------------- .../defaults/project_settings/maya.json | 5 +++-- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 6fe078f2ad..739a0fbb26 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -5,6 +5,7 @@ from openpype.hosts.maya.api import lib, plugin from openpype.lib import ( BoolDef, NumberDef, + TextDef, ) from openpype.pipeline import CreatedInstance @@ -30,6 +31,21 @@ def _get_animation_attr_defs(cls): return defs +def _get_legacy_attr_defs(cls): + """These attributes are defined to hide legacy attributes in the publisher + from the user.""" + return [ + BoolDef("writeColorSets", label="writeColorSets", hidden=True), + BoolDef("writeNormals", label="writeNormals", hidden=True), + BoolDef("writeFaceSets", label="writeFaceSets", hidden=True), + BoolDef("renderableOnly", label="renderableOnly", hidden=True), + BoolDef("visibleOnly", label="visibleOnly", hidden=True), + BoolDef("worldSpace", label="worldSpace", hidden=True), + TextDef("attr", label="attr", hidden=True), + TextDef("attrPrefix", label="attrPrefix", hidden=True), + ] + + class CreateAnimation(plugin.MayaHiddenCreator): """Animation output for character rigs @@ -64,6 +80,7 @@ class CreateAnimation(plugin.MayaHiddenCreator): def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) + defs += _get_legacy_attr_defs(self) return defs @@ -93,6 +110,7 @@ class CreatePointCache(plugin.MayaCreator): def get_instance_attr_defs(self): super(CreatePointCache, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) + defs += _get_legacy_attr_defs(self) return defs def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 33b3d102c2..8628622bdd 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -17,7 +17,7 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 families = ["animation"] - label = "Collect Animation Output Geometry" + label = "Collect Animation" hosts = ["maya"] ignore_type = ["constraints"] @@ -57,3 +57,8 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + + # User defined attributes. + instance.data["includeUserDefinedAttributes"] = ( + instance.data["creator_attributes"]["includeUserDefinedAttributes"] + ) diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 5578a57f31..8b4289ed80 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -45,3 +45,8 @@ class CollectPointcache(pyblish.api.InstancePlugin): if proxy_set: instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) + + # User defined attributes. + instance.data["includeUserDefinedAttributes"] = ( + instance.data["creator_attributes"]["includeUserDefinedAttributes"] + ) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 26957b02f5..68fadef6ca 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -43,7 +43,6 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): preRollStartFrame = 0 pythonPerFrameCallback = "" pythonPostJobCallback = "" - stripNamespaces = -1 userAttr = "" userAttrPrefix = "" export_overrides = [] @@ -257,22 +256,6 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): "label": "pythonPostJobCallback", } }, - "stripNamespaces": { - "def": NumberDef, - "kwargs": { - "label": "stripNamespaces", - "tooltip": ( - "If this flag is present namespaces will be stripped " - "off of the node before being written to Alembic. The " - "int after the flag specifies how many namespaces will" - " be stripped off of the node name. Be careful that " - "the new stripped name does not collide with other " - "sibling node names.\n\nExamples:\n taco:foo:bar would" - " be written as just bar with -sn 0\ntaco:foo:bar " - "would be written as foo:bar with -sn 1" - ), - } - }, "userAttr": { "def": TextDef, "kwargs": { diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 03c6f79eff..6d85c8e394 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -562,7 +562,8 @@ "includeParentHierarchy": false, "farm": false, "priority": 50, - "refresh": false + "refresh": false, + "include_user_defined_attributes": false }, "CreateModel": { "enabled": true, @@ -1112,7 +1113,6 @@ ], "flags": [ "stripNamespaces", - "writeColorSets", "writeNormals", "worldSpace" ], @@ -1131,6 +1131,7 @@ "attr", "attrPrefix", "worldSpace", + "writeColorSets", "writeNormals", "writeFaceSets", "renderableOnly", From 7ea09acb3307507576ea248eb996f5431ce71c2c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 Feb 2024 03:24:33 +0000 Subject: [PATCH 091/244] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index a389280775..95203e17c9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7" +__version__ = "3.18.8-nightly.1" From 51026fbca803ec1281fdc30bc4c55314f36825fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Feb 2024 03:25:06 +0000 Subject: [PATCH 092/244] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7fe6c79259..c01ab5122c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.18.8-nightly.1 - 3.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.3 - 3.15.11-nightly.2 - 3.15.11-nightly.1 - - 3.15.10 validations: required: true - type: dropdown From 64b366ca3d703fcd1cde53c917be3ab0a0b79d0f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Feb 2024 12:08:44 +0000 Subject: [PATCH 093/244] Backwards compatibility --- .../create/create_animation_pointcache.py | 61 ++++++++++++++----- .../plugins/publish/extract_pointcache.py | 1 + 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 739a0fbb26..791aa0072b 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -5,7 +5,6 @@ from openpype.hosts.maya.api import lib, plugin from openpype.lib import ( BoolDef, NumberDef, - TextDef, ) from openpype.pipeline import CreatedInstance @@ -31,19 +30,45 @@ def _get_animation_attr_defs(cls): return defs -def _get_legacy_attr_defs(cls): - """These attributes are defined to hide legacy attributes in the publisher - from the user.""" - return [ - BoolDef("writeColorSets", label="writeColorSets", hidden=True), - BoolDef("writeNormals", label="writeNormals", hidden=True), - BoolDef("writeFaceSets", label="writeFaceSets", hidden=True), - BoolDef("renderableOnly", label="renderableOnly", hidden=True), - BoolDef("visibleOnly", label="visibleOnly", hidden=True), - BoolDef("worldSpace", label="worldSpace", hidden=True), - TextDef("attr", label="attr", hidden=True), - TextDef("attrPrefix", label="attrPrefix", hidden=True), +def extract_alembic_attributes(node_data, class_name): + """This is a legacy transfer of creator attributes to publish attributes + for ExtractAlembic/ExtractAnimation plugin. + """ + publish_attributes = node_data["publish_attributes"] + + if class_name in publish_attributes: + return node_data + + extract_alembic_flags = [ + "writeColorSets", + "writeFaceSets", + "writeNormals", + "renderableOnly", + "visibleOnly", + "worldSpace", + "renderableOnly" ] + extract_alembic_attributes = [ + "attr", + "attrPrefix", + "visibleOnly" + ] + attributes = extract_alembic_flags + extract_alembic_attributes + plugin_attributes = {"flag_overrides": []} + for attr in attributes: + if attr not in node_data["creator_attributes"].keys(): + continue + value = node_data["creator_attributes"].pop(attr) + + if value and attr in extract_alembic_flags: + plugin_attributes["flag_overrides"].append(attr) + + if attr in extract_alembic_attributes: + plugin_attributes[attr] = value + + publish_attributes[class_name] = plugin_attributes + + return node_data class CreateAnimation(plugin.MayaHiddenCreator): @@ -74,13 +99,17 @@ class CreateAnimation(plugin.MayaHiddenCreator): for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) + + node_data = extract_alembic_attributes( + node_data, "ExtractAnimation" + ) + created_instance = CreatedInstance.from_existing(node_data, self) self._add_instance_to_context(created_instance) def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) - defs += _get_legacy_attr_defs(self) return defs @@ -104,13 +133,15 @@ class CreatePointCache(plugin.MayaCreator): for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) + + node_data = extract_alembic_attributes(node_data, "ExtractAlembic") + created_instance = CreatedInstance.from_existing(node_data, self) self._add_instance_to_context(created_instance) def get_instance_attr_defs(self): super(CreatePointCache, self).get_instance_attr_defs() defs = _get_animation_attr_defs(self) - defs += _get_legacy_attr_defs(self) return defs def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 68fadef6ca..92033d57c4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -45,6 +45,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): pythonPostJobCallback = "" userAttr = "" userAttrPrefix = "" + visibleOnly = False export_overrides = [] def process(self, instance): From 05dd980b80da8fa772579498c75575be3d02bb3d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Feb 2024 12:12:58 +0000 Subject: [PATCH 094/244] Revert AYON settings --- server_addon/maya/server/settings/creators.py | 135 ------------------ 1 file changed, 135 deletions(-) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 492b2c180d..d719993685 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -1,5 +1,3 @@ -from typing import Literal - from ayon_server.settings import ( BaseSettingsModel, SettingsField, @@ -59,74 +57,6 @@ class BasicExportMeshModel(BaseSettingsModel): ) -def alembic_booleans_enum(): - return [ - "autoSubd", - "dontSkipUnwritten", - "eulerFilter", - "noNormals", - "preRoll", - "renderableOnly", - "selection", - "stripNamespaces", - "uvWrite", - "uvsOnly", - "verbose", - "wholeFrameGeo", - "worldSpace", - "writeColorSets", - "writeCreases", - "writeFaceSets", - "writeUVSets", - "writeVisibility", - ] - -def alembic_arguments_enum(): - return [ - "attr", - "attrPrefix", - "autoSubd", - "dataFormat", - "dontSkipUnwrittenFrames", - "endFrame", - "eulerFilter", - "frameRange", - "frameRelativeSample", - "melPerFrameCallback", - "melPostJobCallback", - "noNormals", - "preRoll", - "preRollStartFrame", - "pythonPerFrameCallback", - "pythonPostJobCallback", - "renderableOnly", - "root", - "selection", - "startFrame", - "step", - "stripNamespaces", - "userAttr", - "userAttrPrefix", - "uvWrite", - "uvsOnly", - "verbose", - "wholeFrameGeo", - "worldSpace", - "writeColorSets", - "writeCreases", - "writeFaceSets", - "writeUVSets", - "writeVisibility", - ] - -AlembicDataFormat = Literal["ogawa", "hdf5"] - -def alembic_data_formats(): - return [ - "ogawa", - "hdf5" - ] - class CreateAnimationModel(BaseSettingsModel): write_color_sets: bool = SettingsField(title="Write Color Sets") write_face_sets: bool = SettingsField(title="Write Face Sets") @@ -148,20 +78,6 @@ class CreateAnimationModel(BaseSettingsModel): title="Submit to the Farm") attr: str = SettingsField(title="Attributes") attr_prefix: str = SettingsField(title="Attributes Prefix") - data_format: AlembicDataFormat = SettingsField( - enum_resolver=alembic_data_formats, - title="Data Format", - ) - abc_export_flags: list[str] = SettingsField( - default_factory=list, - enum_resolver=alembic_booleans_enum, - title="Export Flags (.abc)", - ) - abc_export_overrides: list[str] = SettingsField( - default_factory=list, - enum_resolver=alembic_arguments_enum, - title="Export Overrides (.abc)", - ) class CreatePointCacheModel(BaseSettingsModel): @@ -185,20 +101,6 @@ class CreatePointCacheModel(BaseSettingsModel): title="Submit to the Farm") attr: str = SettingsField(title="Attributes") attr_prefix: str = SettingsField(title="Attributes Prefix") - data_format: AlembicDataFormat = SettingsField( - enum_resolver=alembic_data_formats, - title="Data Format", - ) - abc_export_flags: list[str] = SettingsField( - default_factory=list, - enum_resolver=alembic_booleans_enum, - title="Export Flags (.abc)", - ) - abc_export_overrides: list[str] = SettingsField( - default_factory=list, - enum_resolver=alembic_arguments_enum, - title="Export Overrides (.abc)", - ) class CreateProxyAlembicModel(BaseSettingsModel): @@ -402,26 +304,8 @@ DEFAULT_CREATORS_SETTINGS = { "Main" ], "step": 1.0, - "abc_export_flags": [ - "writeColorSets", - "visibleOnly", - "worldSpace", - "writeNormals" - ], - "abc_export_overrides": [ - "step", - "includeParentHierarchy", - "writeNormals", - "includeUserDefinedAttributes", - "attr", - "attrPrefix" - ], "farm": False, "priority": 50, - "attr": "", - "attr_prefix": "", - "data_format": "ogawa", - "pre_roll_start_frame": 0, "refresh": False, }, "CreateModel": { @@ -443,28 +327,9 @@ DEFAULT_CREATORS_SETTINGS = { "Main" ], "step": 1.0, - "abc_export_flags": [ - "selection", - "uvWrite", - "writeCreases", - "writeVisibility" - ], - "abc_export_overrides": [ - "attr", - "attrPrefix", - "step", - "writeColorSets", - "writeFaceSets", - "renderableOnly", - "worldSpace" - ], "include_parent_hierarchy": False, "farm": False, "priority": 50, - "attr": "cbId", - "attr_prefix": "", - "data_format": "ogawa", - "pre_roll_start_frame": 0, "refresh": False }, "CreateProxyAlembic": { From c123e831d5468b17a9e66567cc08d08e0d3ae726 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Feb 2024 12:16:38 +0000 Subject: [PATCH 095/244] revert ayon settings --- server_addon/maya/server/settings/creators.py | 35 ++----------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index d719993685..5f3b850a1f 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -68,16 +68,6 @@ class CreateAnimationModel(BaseSettingsModel): default_factory=list, title="Default Products" ) - priority: int = SettingsField( - title="Farm Job Priority") - pre_roll_start_frame: int = SettingsField(title="Pre Roll Start Frame") - refresh: bool = SettingsField( - title="Refresh") - step: int = SettingsField(title="Step") - farm: bool = SettingsField( - title="Submit to the Farm") - attr: str = SettingsField(title="Attributes") - attr_prefix: str = SettingsField(title="Attributes Prefix") class CreatePointCacheModel(BaseSettingsModel): @@ -91,16 +81,6 @@ class CreatePointCacheModel(BaseSettingsModel): default_factory=list, title="Default Products" ) - priority: int = SettingsField( - title="Farm Job Priority") - pre_roll_start_frame: int = SettingsField(title="Pre Roll Start Frame") - refresh: bool = SettingsField( - title="Refresh") - step: int = SettingsField(title="Step") - farm: bool = SettingsField( - title="Submit to the Farm") - attr: str = SettingsField(title="Attributes") - attr_prefix: str = SettingsField(title="Attributes Prefix") class CreateProxyAlembicModel(BaseSettingsModel): @@ -298,15 +278,11 @@ DEFAULT_CREATORS_SETTINGS = { "CreateAnimation": { "write_color_sets": False, "write_face_sets": False, - "include_user_defined_attributes": False, "include_parent_hierarchy": False, + "include_user_defined_attributes": False, "default_variants": [ "Main" - ], - "step": 1.0, - "farm": False, - "priority": 50, - "refresh": False, + ] }, "CreateModel": { "enabled": True, @@ -325,12 +301,7 @@ DEFAULT_CREATORS_SETTINGS = { "include_user_defined_attributes": False, "default_variants": [ "Main" - ], - "step": 1.0, - "include_parent_hierarchy": False, - "farm": False, - "priority": 50, - "refresh": False + ] }, "CreateProxyAlembic": { "enabled": True, From 1f1d7c3a231c9377eb4bf5c090f946fdc5ae8d95 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Feb 2024 12:51:14 +0000 Subject: [PATCH 096/244] Missing writeNormals flag --- .../schemas/projects_schema/schemas/schema_maya_publish.json | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 9af9dbb32b..38b56b58ae 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -1033,6 +1033,7 @@ {"worldSpace": "worldSpace"}, {"writeColorSets": "writeColorSets"}, {"writeFaceSets": "writeFaceSets"}, + {"writeNormals": "writeNormals"}, {"writeUVSets": "writeUVSets"}, {"writeVisibility": "writeVisibility"} ] From 669e0a79550766030d7e573b45c41176bb9d1266 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Feb 2024 13:16:30 +0000 Subject: [PATCH 097/244] Add OP settings and convert in plugin --- .../plugins/publish/collect_clip_effects.py | 10 +++++--- .../defaults/project_settings/hiero.json | 4 +++ .../projects_schema/schema_project_hiero.json | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index d7f646ebc9..44767e458a 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -72,9 +72,13 @@ class CollectClipEffects(pyblish.api.InstancePlugin): subset_split.insert(0, "effect") - effect_categories = { - x["name"]: x["effect_classes"] for x in self.effect_categories - } + # Need to convert to dict for AYON settings. This isinstance check can + # be removed in the future when OpenPype is no longer. + effect_categories = self.effect_categories + if isinstance(self.effect_categories, list): + effect_categories = { + x["name"]: x["effect_classes"] for x in self.effect_categories + } category_by_effect = {"": ""} for key, values in effect_categories.items(): diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 9c83733b09..efd80a8876 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -69,6 +69,10 @@ "tags_addition": [ "review" ] + }, + "CollectClipEffects": { + "enabled": true, + "effect_categories": {} } }, "filters": {}, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index d80edf902b..73bd475815 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -312,6 +312,31 @@ "label": "Tags addition" } ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectClipEffects", + "label": "Collect Clip Effects", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "key": "effect_categories", + "label": "Effect Categories", + "object_type": { + "type": "list", + "key": "effects_classes", + "object_type": "text" + } + } + ] } ] }, From 81c657227ef19487c03a91d92bebca1705cbb830 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Feb 2024 17:12:51 +0000 Subject: [PATCH 098/244] Use folderpath when collecting the render instance --- .../hosts/unreal/plugins/publish/collect_render_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py index dad0310dfc..d7b9191fa3 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py @@ -64,7 +64,7 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): new_data = new_instance.data - new_data["asset"] = seq_name + new_data["asset"] = f"/{s.get('output')}" new_data["setMembers"] = seq_name new_data["family"] = "render" new_data["families"] = ["render", "review"] From 1dcdb311054a42b8a85c56664553dd3118370aec Mon Sep 17 00:00:00 2001 From: Jose Caraballo Date: Mon, 26 Feb 2024 17:39:55 +0100 Subject: [PATCH 099/244] Pass correct arguments to function. --- openpype/tools/publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 13d007dd35..41039a5be0 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,7 @@ class PublisherController(BasePublisherController): "title": "Action failed", "message": "Action failed.", "traceback": "".join( - traceback.format_exception(exception) + traceback.format_exception(type(exception), exception, exception.__traceback__) ), "label": action.__name__, "identifier": action.id From 98deab1f638ac83b03c9b34d82e634db16690f83 Mon Sep 17 00:00:00 2001 From: Jose Caraballo Date: Mon, 26 Feb 2024 17:51:48 +0100 Subject: [PATCH 100/244] Fix line length. --- openpype/tools/publisher/control.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 41039a5be0..f9b8bcc512 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,11 @@ class PublisherController(BasePublisherController): "title": "Action failed", "message": "Action failed.", "traceback": "".join( - traceback.format_exception(type(exception), exception, exception.__traceback__) + traceback.format_exception( + type(exception), + exception, + exception.__traceback__ + ) ), "label": action.__name__, "identifier": action.id From 3a3b535001460d4e20cbccc3e05ca265872c4037 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Mar 2024 11:01:59 +0000 Subject: [PATCH 101/244] Fix submit_max_deadline imports (#6235) --- .../deadline/plugins/publish/submit_max_deadline.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index e31de0a101..07bbb1cacb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -16,11 +16,6 @@ from openpype.pipeline.publish.lib import ( replace_with_published_scene_path ) from openpype.pipeline.publish import KnownPublishError -from openpype.hosts.max.api.lib import ( - get_current_renderer, - get_multipass_setting -) -from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.lib import is_running_from_build @@ -294,6 +289,9 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, Args: infos(dict): a dictionary with plugin info. """ + from openpype.hosts.max.api.lib import get_current_renderer + from openpype.hosts.max.api.lib_rendersettings import RenderSettings + instance = self._instance # set the target camera plugin_info = copy.deepcopy(self.plugin_info) @@ -359,6 +357,8 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info_list (list): A list of multiple job infos plugin_info_list (list): A list of multiple plugin infos """ + from openpype.hosts.max.api.lib import get_multipass_setting + job_info_list = [] plugin_info_list = [] instance = self._instance From 73bc49750eb1f5a71d063082e5a2500c152b303f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:36:47 +0100 Subject: [PATCH 102/244] Update openpype/hosts/resolve/api/lib.py --- openpype/hosts/resolve/api/lib.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index e9c4921bb5..96caacf8d1 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -279,10 +279,6 @@ def create_timeline_item( # timing variables if all([timeline_in, source_start, source_end]): fps = timeline.GetSetting("timelineFrameRate") - # Strangely, Resolve seem to output '23' instead of 23.976 - if fps == '23': - fps = 23.976 - duration = source_end - source_start timecode_in = frames_to_timecode(timeline_in, fps) timecode_out = frames_to_timecode(timeline_in + duration, fps) From 531fd55bee86d6a1d6d9947059745570f0c49ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:37:50 +0100 Subject: [PATCH 103/244] Update openpype/hosts/resolve/api/plugin.py --- openpype/hosts/resolve/api/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 2781ab6b75..36f5a2db7d 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -408,11 +408,12 @@ class ClipLoader: _clip_property = media_pool_item.GetClipProperty source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) - + source_duration = int(_clip_property("Frames")) + # Trim clip start if slate is present if "slate" in self.data["versionData"]["families"]: source_in += 1 - source_duration = source_out - source_in + 1 + source_duration = source_out - source_in + 1 if not self.with_handles: # Load file without the handles of the source media From 7d2ede8d6533c717daea77e199f15fc0b823d56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:39:27 +0100 Subject: [PATCH 104/244] Update openpype/hosts/resolve/api/plugin.py --- openpype/hosts/resolve/api/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 36f5a2db7d..fc7900321c 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -409,7 +409,6 @@ class ClipLoader: source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) source_duration = int(_clip_property("Frames")) - # Trim clip start if slate is present if "slate" in self.data["versionData"]["families"]: source_in += 1 From 2e76f273c752df404b7e55079d9cd8a047d98f79 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 09:30:33 +0000 Subject: [PATCH 105/244] Change labels and export_overrides key --- .../settings/defaults/project_settings/maya.json | 2 +- .../schemas/schema_maya_publish.json | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 6d85c8e394..7d0af6feb4 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1127,7 +1127,7 @@ "userAttr": "", "userAttrPrefix": "", "visibleOnly": false, - "export_overrides": [ + "overrides": [ "attr", "attrPrefix", "worldSpace", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 38b56b58ae..88f3d476ca 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -1017,7 +1017,7 @@ "type": "enum", "key": "flags", "multiselection": true, - "label": "Flags", + "label": "Export Flags", "enum_items": [ {"autoSubd": "autoSubd"}, {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, @@ -1041,12 +1041,12 @@ { "type": "text", "key": "attr", - "label": "Attributes" + "label": "Custom Attributes" }, { "type": "text", "key": "attrPrefix", - "label": "Attr Prefix" + "label": "Custom Attributes Prefix" }, { "type": "enum", @@ -1100,7 +1100,7 @@ { "type": "boolean", "key": "visibleOnly", - "label": "visibleOnly" + "label": "Visible Only" }, { "type": "splitter" @@ -1111,12 +1111,12 @@ }, { "type": "enum", - "key": "export_overrides", + "key": "overrides", "multiselection": true, - "label": "Export Overrides", + "label": "Exposed Overrides", "enum_items": [ - {"attr": "attr"}, - {"attrPrefix": "attrPrefix"}, + {"attr": "Custom Attributes"}, + {"attrPrefix": "Custom Attributes Prefix"}, {"autoSubd": "autoSubd"}, {"dataFormat": "dataFormat"}, {"dontSkipUnwrittenFrames": "dontSkipUnwrittenFrames"}, From 370e422e1aac0c1e339c5791aabb44e437ce6538 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 09:30:52 +0000 Subject: [PATCH 106/244] Fix plugin --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 92033d57c4..a951f9b8ca 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -285,13 +285,13 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): ]) # The Arguments that can be modified by the Publisher - export_overrides = set(getattr(cls, "export_overrides", set())) + overrides = set(getattr(cls, "overrides", set())) # What we have set in the Settings as defaults. flags = set(getattr(cls, "flags", set())) - enabled_flags = [x for x in flags if x in export_overrides] - flag_overrides = export_overrides - set(override_defs.keys()) + enabled_flags = [x for x in flags if x in overrides] + flag_overrides = overrides - set(override_defs.keys()) defs.append( EnumDef( "flag_overrides", @@ -303,7 +303,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): ) for key, value in override_defs.items(): - if key not in export_overrides: + if key not in overrides: continue kwargs = value["kwargs"] From 49cfea34c7893cd221a2fbf2973241b353a4923a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 01:25:46 +0100 Subject: [PATCH 107/244] Tweak message formatting --- .../hosts/houdini/plugins/publish/validate_cop_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index 95414ae7f1..8d3b248ecd 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -26,7 +26,7 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Output node(s) `{}` are incorrect. " + ("Output node '{}' are incorrect. " "See plug-in log for details.").format(invalid), title=self.label ) From 1626182e925b1d270017a8594a2cb9246cf49c83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 01:28:07 +0100 Subject: [PATCH 108/244] Make singular --- .../hosts/houdini/plugins/publish/validate_cop_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index 8d3b248ecd..a6a7044f77 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -26,7 +26,7 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Output node '{}' are incorrect. " + ("Output node '{}' is incorrect. " "See plug-in log for details.").format(invalid), title=self.label ) From 9666352b6e625f8b45e577bfea9bc39f18af7585 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 12:08:35 +0100 Subject: [PATCH 109/244] Cleanup logic --- .../plugins/publish/validate_cop_output_node.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index a6a7044f77..59bb8e66f1 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -36,18 +36,9 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): import hou - try: - output_node = instance.data["output_node"] - except KeyError: - six.reraise( - PublishValidationError, - PublishValidationError( - "Can't determine COP output node.", - title=cls.__name__), - sys.exc_info()[2] - ) + output_node = instance.data.get("output_node") - if output_node is None: + if not output_node: node = hou.node(instance.data.get("instance_node")) cls.log.error( "COP Output node in '%s' does not exist. " From 3def9fe29e19691ca8f7d1514ca74677c8edd00a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 28 Mar 2024 10:47:41 +0000 Subject: [PATCH 110/244] Validate alembic options defaults --- .../create/create_animation_pointcache.py | 4 +- .../plugins/publish/extract_pointcache.py | 17 ++- .../validate_alembic_options_defaults.py | 106 ++++++++++++++++++ 3 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py diff --git a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py index 791aa0072b..e44b0c7b27 100644 --- a/openpype/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_animation_pointcache.py @@ -54,14 +54,14 @@ def extract_alembic_attributes(node_data, class_name): "visibleOnly" ] attributes = extract_alembic_flags + extract_alembic_attributes - plugin_attributes = {"flag_overrides": []} + plugin_attributes = {"flags": []} for attr in attributes: if attr not in node_data["creator_attributes"].keys(): continue value = node_data["creator_attributes"].pop(attr) if value and attr in extract_alembic_flags: - plugin_attributes["flag_overrides"].append(attr) + plugin_attributes["flags"].append(attr) if attr in extract_alembic_attributes: plugin_attributes[attr] = value diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index a951f9b8ca..79e07cf732 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -46,7 +46,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): userAttr = "" userAttrPrefix = "" visibleOnly = False - export_overrides = [] + overrides = [] def process(self, instance): if instance.data.get("farm"): @@ -122,11 +122,10 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): "writeVisibility": False, } - # Export flags are defined as default enabled flags excluding flags - # that are exposed to the user, plus the flags the user has enabled - # when publishing. - flags = list(set(self.flags) - set(self.export_overrides)) - flags += attribute_values["flag_overrides"] + # Export flags are defined as default enabled flags plus publisher + # enabled flags. + non_exposed_flags = list(set(self.flags) - set(self.overrides)) + flags = attribute_values["flags"] + non_exposed_flags for flag in flags: args[flag] = True @@ -291,11 +290,11 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): flags = set(getattr(cls, "flags", set())) enabled_flags = [x for x in flags if x in overrides] - flag_overrides = overrides - set(override_defs.keys()) + flags = overrides - set(override_defs.keys()) defs.append( EnumDef( - "flag_overrides", - flag_overrides, + "flags", + flags, default=enabled_flags, multiselection=True, label="Export Flags", diff --git a/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py new file mode 100644 index 0000000000..f0bcf3de1f --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -0,0 +1,106 @@ +import pyblish.api + +from openpype.pipeline import OptionalPyblishPluginMixin +from openpype.pipeline.publish import RepairAction, PublishValidationError + + +class ValidateAlembicOptionsDefaults( + pyblish.api.InstancePlugin, OptionalPyblishPluginMixin +): + """Validate the attributes on the instance are defaults.""" + + order = pyblish.api.ValidatorOrder + families = ["pointcache", "animation"] + hosts = ["maya"] + label = "Validate Alembic Options Defaults" + actions = [RepairAction] + optional = True + + @classmethod + def _get_plugin_name(self, publish_attributes): + for key in ["ExtractAnimation", "ExtractAlembic"]: + if key in publish_attributes.keys(): + return key + + @classmethod + def _get_settings(self, context): + maya_settings = context.data["project_settings"]["maya"] + settings = maya_settings["publish"]["ExtractAlembic"] + # Flags are a special case since they are a combination of overrides + # and default flags from the settings. + settings["flags"] = [ + x for x in settings["flags"] if x in settings["overrides"] + ] + return settings + + @classmethod + def _get_publish_attributes(self, instance): + attributes = instance.data["publish_attributes"][ + self._get_plugin_name( + instance.data["publish_attributes"] + ) + ] + + settings = self._get_settings(instance.context) + + # Flags are a special case since they are a combination of exposed + # flags and default flags from the settings. So we need to add the + # default flags from the settings and ensure unique items. + non_exposed_flags = [ + x for x in settings["flags"] if x not in settings["overrides"] + ] + attributes["flags"] = attributes["flags"] + non_exposed_flags + + return attributes + + def process(self, instance): + if not self.is_active(instance.data): + return + + settings = self._get_settings(instance.context) + + attributes = self._get_publish_attributes(instance) + + msg = ( + "Alembic Extract setting \"{}\" is not the default value:" + "\nCurrent: {}" + "\nDefault Value: {}\n" + ) + errors = [] + for key, value in attributes.items(): + default_value = settings[key] + + # Lists are best to compared sorted since we cant rely on the order + # of the items. + if isinstance(value, list): + value = sorted(value) + default_value = sorted(default_value) + + if value != default_value: + errors.append(msg.format(key, value, default_value)) + + if errors: + raise PublishValidationError("\n".join(errors)) + + @classmethod + def repair(cls, instance): + # Find create instance twin. + create_context = instance.context.data["create_context"] + create_instance = None + for Instance in create_context.instances: + if Instance.data["instance_id"] == instance.data["instance_id"]: + create_instance = Instance + break + + assert create_instance is not None + + # Set the settings values on the create context then save to workfile. + publish_attributes = instance.data["publish_attributes"] + plugin_name = cls._get_plugin_name(publish_attributes) + attributes = cls._get_publish_attributes(instance) + settings = cls._get_settings(instance.context) + create_publish_attributes = create_instance.data["publish_attributes"] + for key, value in attributes.items(): + create_publish_attributes[plugin_name][key] = settings[key] + + create_context.save_changes() From b645c62ab2f59ac6ed7a92f2113564ab4c28ec33 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 28 Mar 2024 10:52:27 +0000 Subject: [PATCH 111/244] Hound --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index f0bcf3de1f..e16196a6d3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/openpype/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -100,7 +100,7 @@ class ValidateAlembicOptionsDefaults( attributes = cls._get_publish_attributes(instance) settings = cls._get_settings(instance.context) create_publish_attributes = create_instance.data["publish_attributes"] - for key, value in attributes.items(): + for key in attributes.keys(): create_publish_attributes[plugin_name][key] = settings[key] create_context.save_changes() From bf18daafe5c5d3128158b536cbd796fe856f81fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:42:00 +0100 Subject: [PATCH 112/244] Maya - Allow loading a published workfile as template --- .../maya/plugins/load/load_as_template.py | 39 +++++++++++++++++++ .../workfile/workfile_template_builder.py | 19 ++++----- 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 client/ayon_core/hosts/maya/plugins/load/load_as_template.py diff --git a/client/ayon_core/hosts/maya/plugins/load/load_as_template.py b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py new file mode 100644 index 0000000000..a251f1c52e --- /dev/null +++ b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py @@ -0,0 +1,39 @@ +from openpype.lib import ( + BoolDef +) +from openpype.pipeline import ( + load, + registered_host +) +from openpype.hosts.maya.api.workfile_template_builder import ( + MayaTemplateBuilder +) + + +class LoadAsTemplate(load.LoaderPlugin): + """Load workfile as a template """ + + families = ["workfile"] + label = "Load as template" + representations = ["ma", "mb"] + icon = "wrench" + color = "#775555" + order = 10 + + options = [ + BoolDef("keep_placeholders", + label="Keep Placeholders", + default=False), + BoolDef("create_first_version", + label="Create First Version", + default=False), + ] + + def load(self, context, name, namespace, data): + keep_placeholders = data.get("keep_placeholders", False) + create_first_version = data.get("create_first_version", False) + path = self.filepath_from_context(context) + builder = MayaTemplateBuilder(registered_host()) + builder.build_template(template_path=path, + keep_placeholders=keep_placeholders, + create_first_version=create_first_version) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..53f4bf8c32 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -498,15 +498,16 @@ class AbstractTemplateBuilder(object): process if version is created """ - template_preset = self.get_template_preset() - - if template_path is None: - template_path = template_preset["path"] - - if keep_placeholders is None: - keep_placeholders = template_preset["keep_placeholder"] - if create_first_version is None: - create_first_version = template_preset["create_first_version"] + if any(value is None for value in [template_path, + keep_placeholders, + create_first_version]): + template_preset = self.get_template_preset() + if template_path is None: + template_path = template_preset["path"] + if keep_placeholders is None: + keep_placeholders = template_preset["keep_placeholder"] + if create_first_version is None: + create_first_version = template_preset["create_first_version"] # check if first version is created created_version_workfile = False From bd2527ebe6cb1a856ae0e4882668d12f0739c56f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:46:23 +0100 Subject: [PATCH 113/244] Improve type hints --- .../pipeline/workfile/workfile_template_builder.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 53f4bf8c32..7faa67af04 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -16,6 +16,7 @@ import re import collections import copy from abc import ABCMeta, abstractmethod +from typing import TypedDict import six from ayon_api import ( @@ -52,6 +53,14 @@ from ayon_core.pipeline.create import ( _NOT_SET = object() +class TemplatePresetDict(TypedDict): + """Dictionary with `path`, `keep_placeholder` and `create_first_version` + settings from the template preset for current context.""" + path: str + keep_placeholder: bool + create_first_version: bool + + class TemplateNotFound(Exception): """Exception raised when template does not exist.""" pass @@ -773,7 +782,9 @@ class AbstractTemplateBuilder(object): - 'project_settings/{host name}/templated_workfile_build/profiles' Returns: - str: Path to a template file with placeholders. + TemplatePresetDict: Dictionary with `path`, `keep_placeholder` and + `create_first_version` settings from the template preset + for current context. Raises: TemplateProfileNotFound: When profiles are not filled. From ed68f16b720c05d1efef74de43eee428773a2c41 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:51:38 +0100 Subject: [PATCH 114/244] Fix refactor --- .../ayon_core/hosts/maya/plugins/load/load_as_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_as_template.py b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py index a251f1c52e..5c546a1837 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_as_template.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py @@ -1,11 +1,11 @@ -from openpype.lib import ( +from ayon_core.lib import ( BoolDef ) -from openpype.pipeline import ( +from ayon_core.pipeline import ( load, registered_host ) -from openpype.hosts.maya.api.workfile_template_builder import ( +from ayon_core.hosts.maya.api.workfile_template_builder import ( MayaTemplateBuilder ) @@ -13,7 +13,7 @@ from openpype.hosts.maya.api.workfile_template_builder import ( class LoadAsTemplate(load.LoaderPlugin): """Load workfile as a template """ - families = ["workfile"] + product_types = {"workfile"} label = "Load as template" representations = ["ma", "mb"] icon = "wrench" From 04f57187040d5c8698b4c90cda3dbbfae050d9f5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:55:56 +0100 Subject: [PATCH 115/244] Bugfix: refactor `family` -> `product_type` --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 7faa67af04..fb357d8b9b 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1480,7 +1480,7 @@ class PlaceholderLoadMixin(object): product_name_regex = None if product_name_regex_value: product_name_regex = re.compile(product_name_regex_value) - product_type = placeholder.data["family"] + product_type = placeholder.data["product_type"] builder_type = placeholder.data["builder_type"] folder_ids = [] From 766d4d1f1613d0b80872de59ce481b675d2fc10f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 1 Apr 2024 09:46:13 +0100 Subject: [PATCH 116/244] Add submodule --- client/ayon_core/hosts/unreal/integration | 1 + 1 file changed, 1 insertion(+) create mode 160000 client/ayon_core/hosts/unreal/integration diff --git a/client/ayon_core/hosts/unreal/integration b/client/ayon_core/hosts/unreal/integration new file mode 160000 index 0000000000..04b35dbf5f --- /dev/null +++ b/client/ayon_core/hosts/unreal/integration @@ -0,0 +1 @@ +Subproject commit 04b35dbf5fc42d905281fc30d3a22b139c1855e5 From fec4610344a6586aa92d7b3aef50922d02d0f875 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 1 Apr 2024 12:11:19 +0100 Subject: [PATCH 117/244] Fix publishing and add settings. --- .../create/create_animation_pointcache.py | 20 +-- .../validate_alembic_options_defaults.py | 4 +- .../maya/server/settings/publishers.py | 143 ++++++++++++++++-- server_addon/maya/server/version.py | 2 +- 4 files changed, 143 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 967c7b0e2d..6ade9eaeb5 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -1,12 +1,12 @@ from maya import cmds -from openpype.hosts.maya.api import lib, plugin +from ayon_core.hosts.maya.api import lib, plugin -from openpype.lib import ( +from ayon_core.lib import ( BoolDef, NumberDef, ) -from openpype.pipeline import CreatedInstance +from ayon_core.pipeline import CreatedInstance def _get_animation_attr_defs(cls): @@ -91,11 +91,12 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_user_defined_attributes = False def collect_instances(self): + key = "maya_cached_instance_data" try: - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + cached_subsets = self.collection_shared_data[key] except KeyError: - self.cache_subsets(self.collection_shared_data) - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + self.cache_instance_data(self.collection_shared_data) + cached_subsets = self.collection_shared_data[key] for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) @@ -125,11 +126,12 @@ class CreatePointCache(plugin.MayaCreator): include_user_defined_attributes = False def collect_instances(self): + key = "maya_cached_instance_data" try: - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + cached_subsets = self.collection_shared_data[key] except KeyError: - self.cache_subsets(self.collection_shared_data) - cached_subsets = self.collection_shared_data["maya_cached_subsets"] + self.cache_instance_data(self.collection_shared_data) + cached_subsets = self.collection_shared_data[key] for node in cached_subsets.get(self.identifier, []): node_data = self.read_instance_node(node) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index e16196a6d3..0a9fc0c6e5 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -1,7 +1,7 @@ import pyblish.api -from openpype.pipeline import OptionalPyblishPluginMixin -from openpype.pipeline.publish import RepairAction, PublishValidationError +from ayon_core.pipeline import OptionalPyblishPluginMixin +from ayon_core.pipeline.publish import RepairAction, PublishValidationError class ValidateAlembicOptionsDefaults( diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index f1e63f36be..0e12d10c75 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -35,6 +35,76 @@ def angular_unit_enum(): ] +def extract_alembic_flags_enum(): + """Get flags for alembic extraction enumerator.""" + return [ + {"label": "autoSubd", "value": "autoSubd"}, + { + "label": "dontSkipUnwrittenFrames", + "value": "dontSkipUnwrittenFrames" + }, + {"label": "eulerFilter", "value": "eulerFilter"}, + {"label": "noNormals", "value": "noNormals"}, + {"label": "preRoll", "value": "preRoll"}, + {"label": "renderableOnly", "value": "renderableOnly"}, + {"label": "stripNamespaces", "value": "stripNamespaces"}, + {"label": "uvWrite", "value": "uvWrite"}, + {"label": "uvsOnly", "value": "uvsOnly"}, + {"label": "verbose", "value": "verbose"}, + {"label": "wholeFrameGeo", "value": "wholeFrameGeo"}, + {"label": "worldSpace", "value": "worldSpace"}, + {"label": "writeColorSets", "value": "writeColorSets"}, + {"label": "writeFaceSets", "value": "writeFaceSets"}, + {"label": "writeNormals", "value": "writeNormals"}, + {"label": "writeUVSets", "value": "writeUVSets"}, + {"label": "writeVisibility", "value": "writeVisibility"} + ] + + +def extract_alembic_data_format_enum(): + return [ + {"label": "ogawa", "value": "ogawa"}, + {"label": "HDF", "value": "HDF"} + ] + + +def extract_alembic_overrides_enum(): + return [ + {"value": "attr", "label": "Custom Attributes"}, + {"value": "attrPrefix", "label": "Custom Attributes Prefix"}, + {"value": "autoSubd", "label": "autoSubd"}, + {"value": "dataFormat", "label": "dataFormat"}, + { + "value": "dontSkipUnwrittenFrames", + "label": "dontSkipUnwrittenFrames" + }, + {"value": "eulerFilter", "label": "eulerFilter"}, + {"value": "melPerFrameCallback", "label": "melPerFrameCallback"}, + {"value": "melPostJobCallback", "label": "melPostJobCallback"}, + {"value": "noNormals", "label": "noNormals"}, + {"value": "preRoll", "label": "preRoll"}, + {"value": "preRollStartFrame", "label": "Pre Roll Start Frame"}, + {"value": "pythonPerFrameCallback", "label": "pythonPerFrameCallback"}, + {"value": "pythonPostJobCallback", "label": "pythonPostJobCallback"}, + {"value": "renderableOnly", "label": "renderableOnly"}, + {"value": "stripNamespaces", "label": "stripNamespaces"}, + {"value": "userAttr", "label": "userAttr"}, + {"value": "userAttrPrefix", "label": "userAttrPrefix"}, + {"value": "uvWrite", "label": "uvWrite"}, + {"value": "uvsOnly", "label": "uvsOnly"}, + {"value": "verbose", "label": "verbose"}, + {"value": "visibleOnly", "label": "Visible Only"}, + {"value": "wholeFrameGeo", "label": "wholeFrameGeo"}, + {"value": "worldSpace", "label": "worldSpace"}, + {"value": "writeColorSets", "label": "writeColorSets"}, + {"value": "writeCreases", "label": "writeCreases"}, + {"value": "writeFaceSets", "label": "writeFaceSets"}, + {"value": "writeNormals", "label": "writeNormals"}, + {"value": "writeUVSets", "label": "writeUVSets"}, + {"value": "writeVisibility", "label": "writeVisibility"} + ] + + class BasicValidateModel(BaseSettingsModel): enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") @@ -309,6 +379,25 @@ class ExtractAlembicModel(BaseSettingsModel): description="List of attribute prefixes for attributes that will be " "included in the alembic export.", ) + flags: list[str] = SettingsField( + enum_resolver=extract_alembic_flags_enum, title="Export Flags" + ) + attr: str = SettingsField(title="Custom Attributes") + attrPrefix: str = SettingsField(title="Custom Attributes Prefix") + dataFormat: str = SettingsField( + enum_resolver=extract_alembic_data_format_enum, title="Data Format" + ) + melPerFrameCallback: str = SettingsField(title="melPerFrameCallback") + melPostFrameCallback: str = SettingsField(title="melPostFrameCallback") + preRollStartFrame: int = SettingsField(title="Pre Roll Start Frame") + pythonPerFrameCallback: str = SettingsField(title="pythonPerFrameCallback") + pythonPostJobCallback: str = SettingsField(title="pythonPostJobCallback") + userAttr: str = SettingsField(title="userAttr") + userAttrPrefix: str = SettingsField(title="userAttrPrefix") + visibleOnly: bool = SettingsField(title="Visible Only") + overrides: list[str] = SettingsField( + enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides" + ) class ExtractObjModel(BaseSettingsModel): @@ -665,10 +754,6 @@ class PublishersModel(BaseSettingsModel): title="Extract Proxy Alembic", section="Model Extractors", ) - ExtractAlembic: ExtractAlembicModel = SettingsField( - default_factory=ExtractAlembicModel, - title="Extract Alembic", - ) ExtractObj: ExtractObjModel = SettingsField( default_factory=ExtractObjModel, title="Extract OBJ" @@ -799,6 +884,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ExtractGPUCacheModel, title="Extract GPU Cache", ) + ExtractAlembic: ExtractAlembicModel = SettingsField( + default_factory=ExtractAlembicModel, + title="Extract Alembic", + ) DEFAULT_SUFFIX_NAMING = { @@ -1188,16 +1277,6 @@ DEFAULT_PUBLISH_SETTINGS = { "proxyAbc" ] }, - "ExtractAlembic": { - "enabled": True, - "families": [ - "pointcache", - "model", - "vrayproxy.alembic" - ], - "bake_attributes": [], - "bake_attribute_prefixes": [] - }, "ExtractObj": { "enabled": False, "optional": True, @@ -1353,5 +1432,41 @@ DEFAULT_PUBLISH_SETTINGS = { "optimizeAnimationsForMotionBlur": True, "writeMaterials": True, "useBaseTessellation": True + }, + "ExtractAlembic": { + "enabled": True, + "families": [ + "pointcache", + "model", + "vrayproxy.alembic" + ], + "bake_attributes": [], + "bake_attribute_prefixes": [], + "flags": [ + "stripNamespaces", + "writeNormals", + "worldSpace" + ], + "attr": "", + "attrPrefix": "", + "dataFormat": "ogawa", + "melPerFrameCallback": "", + "melPostFrameCallback": "", + "preRollStartFrame": 0, + "pythonPerFrameCallback": "", + "pythonPostJobCallback": "", + "userAttr": "", + "userAttrPrefix": "", + "visibleOnly": False, + "overrides": [ + "attr", + "attrPrefix", + "worldSpace", + "writeColorSets", + "writeNormals", + "writeFaceSets", + "renderableOnly", + "visibleOnly" + ] } } diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 71b4bc4ca6..1a4f79a972 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.12" +__version__ = "0.1.13" From 5885d7e63c9a0125d7255d78fad7d776e52d2260 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 1 Apr 2024 12:21:53 +0100 Subject: [PATCH 118/244] Fix bad merge. --- .../hiero/plugins/publish/collect_clip_effects.py | 10 +++------- client/ayon_core/hosts/max/startup/startup.ms | 3 --- client/ayon_core/hosts/resolve/api/plugin.py | 4 ---- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/hosts/hiero/plugins/publish/collect_clip_effects.py b/client/ayon_core/hosts/hiero/plugins/publish/collect_clip_effects.py index 0a90c87b55..bfc63f2551 100644 --- a/client/ayon_core/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/client/ayon_core/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -73,13 +73,9 @@ class CollectClipEffects(pyblish.api.InstancePlugin): product_name_split.insert(0, "effect") - # Need to convert to dict for AYON settings. This isinstance check can - # be removed in the future when OpenPype is no longer. - effect_categories = self.effect_categories - if isinstance(self.effect_categories, list): - effect_categories = { - x["name"]: x["effect_classes"] for x in self.effect_categories - } + effect_categories = { + x["name"]: x["effect_classes"] for x in self.effect_categories + } category_by_effect = {"": ""} for key, values in effect_categories.items(): diff --git a/client/ayon_core/hosts/max/startup/startup.ms b/client/ayon_core/hosts/max/startup/startup.ms index e25766c3fe..c5b4f0e526 100644 --- a/client/ayon_core/hosts/max/startup/startup.ms +++ b/client/ayon_core/hosts/max/startup/startup.ms @@ -7,9 +7,6 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath - - /*opens the create menu on startup to ensure users are presented with a useful default view.*/ - max create mode /*opens the create menu on startup to ensure users are presented with a useful default view.*/ max create mode diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index ffff2588e2..0b339cdf7c 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -410,10 +410,6 @@ class ClipLoader: source_in = int(_clip_property("Start")) source_out = int(_clip_property("End")) source_duration = int(_clip_property("Frames")) - # Trim clip start if slate is present - if "slate" in self.data["versionData"]["families"]: - source_in += 1 - source_duration = source_out - source_in + 1 # Trim clip start if slate is present if "slate" in self.data["versionAttributes"]["families"]: From 1eebe2ee77c5d37f0523ecfef5250ad438f65874 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 2 Apr 2024 10:03:50 +0100 Subject: [PATCH 119/244] Settings for ValidateAlembicOptionsDefaults. --- server_addon/maya/server/settings/publishers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 0e12d10c75..9b10125d1c 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -749,6 +749,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Alembic Visible Node", ) + ValidateAlembicOptionsDefaults: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Alembic Options Defaults" + ) ExtractProxyAlembic: ExtractProxyAlembicModel = SettingsField( default_factory=ExtractProxyAlembicModel, title="Extract Proxy Alembic", @@ -1397,6 +1401,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "validate_shapes": True }, + "ValidateAlembicOptionsDefaults": { + "enabled": True, + "optional": True, + "active": True + }, "ExtractPlayblast": DEFAULT_PLAYBLAST_SETTING, "ExtractMayaSceneRaw": { "enabled": True, From eb0de8685408c95f63f8e4d9e15b1c0be926779e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Apr 2024 14:43:37 +0200 Subject: [PATCH 120/244] Remove `TypedDict` to support Py 3.7 (e.g. Maya 2022) --- .../pipeline/workfile/workfile_template_builder.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index fb357d8b9b..cd63198317 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -16,7 +16,6 @@ import re import collections import copy from abc import ABCMeta, abstractmethod -from typing import TypedDict import six from ayon_api import ( @@ -53,14 +52,6 @@ from ayon_core.pipeline.create import ( _NOT_SET = object() -class TemplatePresetDict(TypedDict): - """Dictionary with `path`, `keep_placeholder` and `create_first_version` - settings from the template preset for current context.""" - path: str - keep_placeholder: bool - create_first_version: bool - - class TemplateNotFound(Exception): """Exception raised when template does not exist.""" pass @@ -782,14 +773,14 @@ class AbstractTemplateBuilder(object): - 'project_settings/{host name}/templated_workfile_build/profiles' Returns: - TemplatePresetDict: Dictionary with `path`, `keep_placeholder` and + dict: Dictionary with `path`, `keep_placeholder` and `create_first_version` settings from the template preset for current context. Raises: TemplateProfileNotFound: When profiles are not filled. TemplateLoadFailed: Profile was found but path is not set. - TemplateNotFound: Path was set but file does not exists. + TemplateNotFound: Path was set but file does not exist. """ host_name = self.host_name From ab408bd177972e1d49778fa5da1c59c7fcba04ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:16:06 +0200 Subject: [PATCH 121/244] Update client/ayon_core/pipeline/workfile/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index cd63198317..22c732a07a 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1471,7 +1471,9 @@ class PlaceholderLoadMixin(object): product_name_regex = None if product_name_regex_value: product_name_regex = re.compile(product_name_regex_value) - product_type = placeholder.data["product_type"] + product_type = placeholder.data.get("product_type") + if product_type is None: + product_type = placeholder.data["family"] builder_type = placeholder.data["builder_type"] folder_ids = [] From 75dbba65bf02b0e3d16fc7ecb04363f940807d72 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:16:17 +0200 Subject: [PATCH 122/244] Update client/ayon_core/pipeline/workfile/workfile_template_builder.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../pipeline/workfile/workfile_template_builder.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 22c732a07a..ceac5405c5 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -498,9 +498,14 @@ class AbstractTemplateBuilder(object): process if version is created """ - if any(value is None for value in [template_path, - keep_placeholders, - create_first_version]): + if any( + value is None + for value in [ + template_path, + keep_placeholders, + create_first_version, + ] + ): template_preset = self.get_template_preset() if template_path is None: template_path = template_preset["path"] From a7ee5154b9867c53d0efe192b9f1f12001e032e1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 4 Apr 2024 11:35:26 +0100 Subject: [PATCH 123/244] BigRoy feedback --- .../validate_alembic_options_defaults.py | 10 +- .../maya/server/settings/publishers.py | 118 ++++++++++-------- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 0a9fc0c6e5..47ccab8137 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -17,13 +17,13 @@ class ValidateAlembicOptionsDefaults( optional = True @classmethod - def _get_plugin_name(self, publish_attributes): + def _get_plugin_name(cls, publish_attributes): for key in ["ExtractAnimation", "ExtractAlembic"]: if key in publish_attributes.keys(): return key @classmethod - def _get_settings(self, context): + def _get_settings(cls, context): maya_settings = context.data["project_settings"]["maya"] settings = maya_settings["publish"]["ExtractAlembic"] # Flags are a special case since they are a combination of overrides @@ -34,14 +34,14 @@ class ValidateAlembicOptionsDefaults( return settings @classmethod - def _get_publish_attributes(self, instance): + def _get_publish_attributes(cls, instance): attributes = instance.data["publish_attributes"][ - self._get_plugin_name( + cls._get_plugin_name( instance.data["publish_attributes"] ) ] - settings = self._get_settings(instance.context) + settings = cls._get_settings(instance.context) # Flags are a special case since they are a combination of exposed # flags and default flags from the settings. So we need to add the diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index b704a5f3a8..f8b3bc0d2d 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -38,26 +38,26 @@ def angular_unit_enum(): def extract_alembic_flags_enum(): """Get flags for alembic extraction enumerator.""" return [ - {"label": "autoSubd", "value": "autoSubd"}, + {"value": "autoSubd", "label": "Auto Subd"}, { - "label": "dontSkipUnwrittenFrames", - "value": "dontSkipUnwrittenFrames" + "value": "dontSkipUnwrittenFrames", + "label": "Dont Skip Unwritten Frames" }, - {"label": "eulerFilter", "value": "eulerFilter"}, - {"label": "noNormals", "value": "noNormals"}, - {"label": "preRoll", "value": "preRoll"}, - {"label": "renderableOnly", "value": "renderableOnly"}, - {"label": "stripNamespaces", "value": "stripNamespaces"}, - {"label": "uvWrite", "value": "uvWrite"}, - {"label": "uvsOnly", "value": "uvsOnly"}, - {"label": "verbose", "value": "verbose"}, - {"label": "wholeFrameGeo", "value": "wholeFrameGeo"}, - {"label": "worldSpace", "value": "worldSpace"}, - {"label": "writeColorSets", "value": "writeColorSets"}, - {"label": "writeFaceSets", "value": "writeFaceSets"}, - {"label": "writeNormals", "value": "writeNormals"}, - {"label": "writeUVSets", "value": "writeUVSets"}, - {"label": "writeVisibility", "value": "writeVisibility"} + {"value": "eulerFilter", "label": "Euler Filter"}, + {"value": "noNormals", "label": "No Normals"}, + {"value": "preRoll", "label": "Pre Roll"}, + {"value": "renderableOnly", "label": "Renderable Only"}, + {"value": "stripNamespaces", "label": "Strip Namespaces"}, + {"value": "uvWrite", "label": "UV Write"}, + {"value": "uvsOnly", "label": "UVs Only"}, + {"value": "verbose", "label": "Verbose"}, + {"value": "wholeFrameGeo", "label": "Whole Frame Geo"}, + {"value": "worldSpace", "label": "World Space"}, + {"value": "writeColorSets", "label": "Write Color Sets"}, + {"value": "writeFaceSets", "label": "Write Face Sets"}, + {"value": "writeNormals", "label": "Write Normals"}, + {"value": "writeUVSets", "label": "Write UV Sets"}, + {"value": "writeVisibility", "label": "Write Visibility"} ] @@ -70,38 +70,44 @@ def extract_alembic_data_format_enum(): def extract_alembic_overrides_enum(): return [ - {"value": "attr", "label": "Custom Attributes"}, - {"value": "attrPrefix", "label": "Custom Attributes Prefix"}, - {"value": "autoSubd", "label": "autoSubd"}, - {"value": "dataFormat", "label": "dataFormat"}, + {"label": "Custom Attributes", "value": "attr"}, + {"label": "Custom Attributes Prefix", "value": "attrPrefix"}, + {"label": "Auto Subd", "value": "autoSubd"}, + {"label": "Data Format", "value": "dataFormat"}, { - "value": "dontSkipUnwrittenFrames", - "label": "dontSkipUnwrittenFrames" + "label": "Dont Skip Unwritten Frames", + "value": "dontSkipUnwrittenFrames" }, - {"value": "eulerFilter", "label": "eulerFilter"}, - {"value": "melPerFrameCallback", "label": "melPerFrameCallback"}, - {"value": "melPostJobCallback", "label": "melPostJobCallback"}, - {"value": "noNormals", "label": "noNormals"}, - {"value": "preRoll", "label": "preRoll"}, - {"value": "preRollStartFrame", "label": "Pre Roll Start Frame"}, - {"value": "pythonPerFrameCallback", "label": "pythonPerFrameCallback"}, - {"value": "pythonPostJobCallback", "label": "pythonPostJobCallback"}, - {"value": "renderableOnly", "label": "renderableOnly"}, - {"value": "stripNamespaces", "label": "stripNamespaces"}, - {"value": "userAttr", "label": "userAttr"}, - {"value": "userAttrPrefix", "label": "userAttrPrefix"}, - {"value": "uvWrite", "label": "uvWrite"}, - {"value": "uvsOnly", "label": "uvsOnly"}, - {"value": "verbose", "label": "verbose"}, - {"value": "visibleOnly", "label": "Visible Only"}, - {"value": "wholeFrameGeo", "label": "wholeFrameGeo"}, - {"value": "worldSpace", "label": "worldSpace"}, - {"value": "writeColorSets", "label": "writeColorSets"}, - {"value": "writeCreases", "label": "writeCreases"}, - {"value": "writeFaceSets", "label": "writeFaceSets"}, - {"value": "writeNormals", "label": "writeNormals"}, - {"value": "writeUVSets", "label": "writeUVSets"}, - {"value": "writeVisibility", "label": "writeVisibility"} + {"label": "Euler Filter", "value": "eulerFilter"}, + {"label": "Mel Per Frame Callback", "value": "melPerFrameCallback"}, + {"label": "Mel Post Job Callback", "value": "melPostJobCallback"}, + {"label": "No Normals", "value": "noNormals"}, + {"label": "Pre Roll", "value": "preRoll"}, + {"label": "Pre Roll Start Frame", "value": "preRollStartFrame"}, + { + "label": "Python Per Frame Callback", + "value": "pythonPerFrameCallback" + }, + { + "label": "Python Post Job Callback", + "value": "pythonPostJobCallback" + }, + {"label": "Renderable Only", "value": "renderableOnly"}, + {"label": "Strip Namespaces", "value": "stripNamespaces"}, + {"label": "User Attr", "value": "userAttr"}, + {"label": "User Attr Prefix", "value": "userAttrPrefix"}, + {"label": "UV Write", "value": "uvWrite"}, + {"label": "UVs Only", "value": "uvsOnly"}, + {"label": "Verbose", "value": "verbose"}, + {"label": "Visible Only", "value": "visibleOnly"}, + {"label": "Whole FrameGeo", "value": "wholeFrameGeo"}, + {"label": "World Space", "value": "worldSpace"}, + {"label": "Write Color Sets", "value": "writeColorSets"}, + {"label": "Write Creases", "value": "writeCreases"}, + {"label": "Write Face Sets", "value": "writeFaceSets"}, + {"label": "Write Normals", "value": "writeNormals"}, + {"label": "Write UV Sets", "value": "writeUVSets"}, + {"label": "Write Visibility", "value": "writeVisibility"} ] @@ -387,13 +393,17 @@ class ExtractAlembicModel(BaseSettingsModel): dataFormat: str = SettingsField( enum_resolver=extract_alembic_data_format_enum, title="Data Format" ) - melPerFrameCallback: str = SettingsField(title="melPerFrameCallback") - melPostFrameCallback: str = SettingsField(title="melPostFrameCallback") + melPerFrameCallback: str = SettingsField(title="Mel Per Frame Callback") + melPostFrameCallback: str = SettingsField(title="Mel Post Frame Callback") preRollStartFrame: int = SettingsField(title="Pre Roll Start Frame") - pythonPerFrameCallback: str = SettingsField(title="pythonPerFrameCallback") - pythonPostJobCallback: str = SettingsField(title="pythonPostJobCallback") - userAttr: str = SettingsField(title="userAttr") - userAttrPrefix: str = SettingsField(title="userAttrPrefix") + pythonPerFrameCallback: str = SettingsField( + title="Python Per Frame Callback" + ) + pythonPostJobCallback: str = SettingsField( + title="Python Post Job Callback" + ) + userAttr: str = SettingsField(title="User Attr") + userAttrPrefix: str = SettingsField(title="User Attr Prefix") visibleOnly: bool = SettingsField(title="Visible Only") overrides: list[str] = SettingsField( enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides" From bbcee5fd6b352e6ef309e00199139b02dbbe014b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 17:18:12 +0200 Subject: [PATCH 124/244] Cleanup code + add description to report, also remove `title=cls.label` because that is the default behavior --- .../plugins/publish/validate_cop_output_node.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index 5796eef1b2..16e72491cc 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -import sys +import hou import pyblish.api -import six from ayon_core.pipeline import PublishValidationError @@ -33,9 +32,6 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - - import hou - output_node = instance.data.get("output_node") if not output_node: @@ -62,8 +58,9 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): # the isinstance check above should be stricter than this category if output_node.type().category().name() != "Cop2": raise PublishValidationError( - ( - "Output node {} is not of category Cop2." - " This is a bug..." - ).format(output_node.path()), - title=cls.label) + f"Output node {output_node.path()} is not of category Cop2.", + description=( + "### Invalid COP output node\n\n" + "The output node path for the instance must be set to a " + "valid COP node path. See the log for more details." + )) From fe92a4233dce65dac26e0fe514105ca5a63416bd Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Apr 2024 17:46:34 +0100 Subject: [PATCH 125/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- .../maya/server/settings/publishers.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index f8b3bc0d2d..5bb930efd7 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -402,8 +402,22 @@ class ExtractAlembicModel(BaseSettingsModel): pythonPostJobCallback: str = SettingsField( title="Python Post Job Callback" ) - userAttr: str = SettingsField(title="User Attr") - userAttrPrefix: str = SettingsField(title="User Attr Prefix") + userAttr: str = SettingsField( + title="User Attr", + placeholder="attr1;attr2", + description=( + "Attributes matching by name will be included in the Alembic export. " + "Attributes should be separated by semi-colon `;`" + ) + ) + userAttrPrefix: str = SettingsField( + title="User Attr Prefix", + placeholder="attr1;attr2", + description=( + "Attributes starting with these prefixes will be included in the Alembic export. " + "Attributes should be separated by semi-colon `;`" + ) + ) visibleOnly: bool = SettingsField(title="Visible Only") overrides: list[str] = SettingsField( enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides" From c78258c927fc2eee089dcb787b36cf6f520a737f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Apr 2024 17:54:04 +0100 Subject: [PATCH 126/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/publish/extract_pointcache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 02e1cde428..2a85eec792 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -290,10 +290,10 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): ]) # The Arguments that can be modified by the Publisher - overrides = set(getattr(cls, "overrides", set())) + overrides = set(cls.overrides) # What we have set in the Settings as defaults. - flags = set(getattr(cls, "flags", set())) + flags = set(cls.flags) enabled_flags = [x for x in flags if x in overrides] flags = overrides - set(override_defs.keys()) From 316e725fda9a1c285dc69020732bb0ed1a771704 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Apr 2024 21:01:00 +0100 Subject: [PATCH 127/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 2a85eec792..d25e377b97 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -150,7 +150,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): with maintained_selection(): cmds.select(nodes, noExpand=True) self.log.debug( - "Running `extract_alembic` with the arguments: {}".format( + "Running `extract_alembic` with arguments: {}".format( args ) ) From 517cc39b6933c4bcff03e36a36938821f749d5ee Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Apr 2024 21:15:23 +0100 Subject: [PATCH 128/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Roy Nieterau --- .../plugins/publish/validate_alembic_options_defaults.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 47ccab8137..fdf1e1c842 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -7,7 +7,10 @@ from ayon_core.pipeline.publish import RepairAction, PublishValidationError class ValidateAlembicOptionsDefaults( pyblish.api.InstancePlugin, OptionalPyblishPluginMixin ): - """Validate the attributes on the instance are defaults.""" + """Validate the attributes on the instance are defaults. + + The defaults are defined in the project settings. + """ order = pyblish.api.ValidatorOrder families = ["pointcache", "animation"] From d07b5d57ea4084b9b6beb6f1d3fcbeb9f8f9575e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Apr 2024 21:32:40 +0100 Subject: [PATCH 129/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index d25e377b97..e0b0aeb73d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -333,7 +333,7 @@ class ExtractAnimation(ExtractAlembic): out_sets = [node for node in instance if node.endswith("out_SET")] if len(out_sets) != 1: raise RuntimeError( - "Couldn't find exactly one out_SET: " "{0}".format(out_sets) + "Couldn't find exactly one out_SET: {0}".format(out_sets) ) out_set = out_sets[0] roots = cmds.sets(out_set, query=True) From a75443556a09dcf51345f3cb8b5d178ac175b8a0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 4 Apr 2024 21:49:13 +0100 Subject: [PATCH 130/244] BigRoy feedback --- .../create/create_animation_pointcache.py | 7 +- .../plugins/publish/extract_pointcache.py | 258 ++++++++++++------ .../maya/server/settings/publishers.py | 166 ++++++++--- 3 files changed, 307 insertions(+), 124 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 6ade9eaeb5..4752e52a0e 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -18,7 +18,12 @@ def _get_animation_attr_defs(cls): NumberDef("priority", label="Farm job Priority", default=50), BoolDef("refresh", label="Refresh viewport during export"), BoolDef( - "includeParentHierarchy", label="Include Parent Hierarchy" + "includeParentHierarchy", + label="Include Parent Hierarchy", + tooltip=( + "Whether to include parent hierarchy of nodes in the " + "publish instance." + ) ), BoolDef( "includeUserDefinedAttributes", diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index e0b0aeb73d..3db6574c85 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -97,7 +97,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): # direct members of the set root = roots - args = { + kwargs = { "file": path, "attr": attrs, "attrPrefix": attr_prefixes, @@ -133,7 +133,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): non_exposed_flags = list(set(self.flags) - set(self.overrides)) flags = attribute_values["flags"] + non_exposed_flags for flag in flags: - args[flag] = True + kwargs[flag] = True if instance.data.get("visibleOnly", False): # If we only want to include nodes that are visible in the frame @@ -150,11 +150,10 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): with maintained_selection(): cmds.select(nodes, noExpand=True) self.log.debug( - "Running `extract_alembic` with arguments: {}".format( - args - ) + "Running `extract_alembic` with the keyword arguments: " + "{}".format(kwargs) ) - extract_alembic(**args) + extract_alembic(**kwargs) if "representations" not in instance.data: instance.data["representations"] = [] @@ -178,17 +177,17 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): return path = path.replace(".abc", "_proxy.abc") - args["file"] = path + kwargs["file"] = path if not instance.data.get("includeParentHierarchy", True): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - args["root"] = instance.data["proxyRoots"] + kwargs["root"] = instance.data["proxyRoots"] with suspended_refresh(suspend=suspend): with maintained_selection(): cmds.select(instance.data["proxy"]) - extract_alembic(**args) + extract_alembic(**kwargs) representation = { "name": "proxy", @@ -205,81 +204,108 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): @classmethod def get_attribute_defs(cls): override_defs = { - "attr": { - "def": TextDef, - "kwargs": { - "label": "Custom Attributes", - "placeholder": "attr1; attr2; ...", - } - }, - "attrPrefix": { - "def": TextDef, - "kwargs": { - "label": "Custom Attributes Prefix", - "placeholder": "prefix1; prefix2; ...", - } - }, - "dataFormat": { - "def": EnumDef, - "kwargs": { - "label": "Data Format", - "items": ["ogawa", "HDF"], - } - }, - "melPerFrameCallback": { - "def": TextDef, - "kwargs": { - "label": "melPerFrameCallback", - } - }, - "melPostJobCallback": { - "def": TextDef, - "kwargs": { - "label": "melPostJobCallback", - } - }, - "preRollStartFrame": { - "def": NumberDef, - "kwargs": { - "label": "Start frame for preroll", - "tooltip": ( - "The frame to start scene evaluation at. This is used" - " to set the starting frame for time dependent " - "translations and can be used to evaluate run-up that" - " isn't actually translated." - ), - } - }, - "pythonPerFrameCallback": { - "def": TextDef, - "kwargs": { - "label": "pythonPerFrameCallback", - } - }, - "pythonPostJobCallback": { - "def": TextDef, - "kwargs": { - "label": "pythonPostJobCallback", - } - }, - "userAttr": { - "def": TextDef, - "kwargs": { - "label": "userAttr", - } - }, - "userAttrPrefix": { - "def": TextDef, - "kwargs": { - "label": "userAttrPrefix", - } - }, - "visibleOnly": { - "def": BoolDef, - "kwargs": { - "label": "Visible Only", - } - } + "attr": TextDef( + "attr", + label="Custom Attributes", + placeholder="attr1;attr2", + default=cls.attr, + tooltip=( + "Attributes matching by name will be included in the " + "Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "attrPrefix": TextDef( + "attrPrefix", + label="Custom Attributes Prefix", + placeholder="prefix1; prefix2; ...", + default=cls.attrPrefix, + tooltip=( + "Attributes starting with these prefixes will be included " + "in the Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "dataFormat": EnumDef( + "dataFormat", + label="Data Format", + items=["ogawa", "HDF"], + default=cls.dataFormat, + tooltip="The data format to use to write the file." + ), + "melPerFrameCallback": TextDef( + "melPerFrameCallback", + label="Mel Per Frame Callback", + default=cls.melPerFrameCallback, + tooltip=( + "When each frame (and the static frame) is evaluated the " + "string specified is evaluated as a Mel command." + ) + ), + "melPostJobCallback": TextDef( + "melPostJobCallback", + label="Mel Post Job Callback", + default=cls.melPostJobCallback, + tooltip=( + "When the translation has finished the string specified " + "is evaluated as a Mel command." + ) + ), + "preRollStartFrame": NumberDef( + "preRollStartFrame", + label="Pre Roll Start Frame", + tooltip=( + "The frame to start scene evaluation at. This is used" + " to set the starting frame for time dependent " + "translations and can be used to evaluate run-up that" + " isn't actually translated." + ), + default=cls.preRollStartFrame + ), + "pythonPerFrameCallback": TextDef( + "pythonPerFrameCallback", + label="Python Per Frame Callback", + default=cls.pythonPerFrameCallback, + tooltip=( + "When each frame (and the static frame) is evaluated the " + "string specified is evaluated as a python command." + ) + ), + "pythonPostJobCallback": TextDef( + "pythonPostJobCallback", + label="Python Post Frame Callback", + default=cls.pythonPostJobCallback, + tooltip=( + "When the translation has finished the string specified " + "is evaluated as a python command." + ) + ), + "userAttr": TextDef( + "userAttr", + label="User Attr", + default=cls.userAttr, + tooltip=( + "Attributes matching by name will be included in the " + "Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "userAttrPrefix": TextDef( + "userAttrPrefix", + label="User Attr Prefix", + default=cls.userAttrPrefix, + tooltip=( + "Attributes starting with these prefixes will be included " + "in the Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "visibleOnly": BoolDef( + "visibleOnly", + label="Visible Only", + default=cls.visibleOnly, + tooltip="Only export dag objects visible during frame range." + ) } defs = super(ExtractAlembic, cls).get_attribute_defs() @@ -297,6 +323,61 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): enabled_flags = [x for x in flags if x in overrides] flags = overrides - set(override_defs.keys()) + + tooltips = { + "autoSubd": ( + "If this flag is present and the mesh has crease edges, crease" + " vertices or holes, the mesh (OPolyMesh) would now be written" + " out as an OSubD and crease info will be stored in the " + "Alembic file. Otherwise, creases info won't be preserved in " + "Alembic file unless a custom Boolean attribute " + "SubDivisionMesh has been added to mesh node and its value is " + "true." + ), + "dontSkipUnwrittenFrames": ( + "When evaluating multiple translate jobs, this decides whether" + " to evaluate frames between jobs when there is a gap in their" + " frame ranges." + ), + "eulerFilter": "Apply Euler filter while sampling rotations.", + "noNormals": ( + "Present normal data for Alembic poly meshes will not be " + "written." + ), + "preRoll": "This frame range will not be sampled.", + "renderableOnly": "Only export renderable visible shapes.", + "stripNamespaces": ( + "Namespaces will be stripped off of the node before being " + "written to Alembic." + ), + "uvWrite": ( + "Uv data for PolyMesh and SubD shapes will be written to the " + "Alembic file." + ), + "uvsOnly": ( + "If this flag is present, only uv data for PolyMesh and SubD " + "shapes will be written to the Alembic file." + ), + "verbose": "Prints the current frame that is being evaluated.", + "wholeFrameGeo": ( + "Data for geometry will only be written out on whole frames." + ), + "worldSpace": "Any root nodes will be stored in world space.", + "writeColorSets": "Write vertex colors with the geometry.", + "writeFaceSets": "Write face sets with the geometry.", + "writeNormals": "Write normals with the deforming geometry.", + "writeUVSets": ( + "Write all uv sets on MFnMeshes as vector 2 indexed geometry" + " parameters with face varying scope." + ), + "writeVisibility": ( + "Visibility state will be stored in the Alembic file. " + "Otherwise everything written out is treated as visible." + ) + } + tooltip = "" + for flag in flags: + tooltip += "{} - {}\n".format(flag, tooltips[flag]) defs.append( EnumDef( "flags", @@ -304,6 +385,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): default=enabled_flags, multiselection=True, label="Export Flags", + tooltip=tooltip, ) ) @@ -311,11 +393,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): if key not in overrides: continue - kwargs = value["kwargs"] - kwargs["default"] = getattr(cls, key, None) - defs.append( - value["def"](key, **value["kwargs"]) - ) + defs.append(value) defs.append( UISeparatorDef("sep_alembic_options") @@ -336,7 +414,7 @@ class ExtractAnimation(ExtractAlembic): "Couldn't find exactly one out_SET: {0}".format(out_sets) ) out_set = out_sets[0] - roots = cmds.sets(out_set, query=True) + roots = cmds.sets(out_set, query=True) or [] # Include all descendants nodes = ( diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 5bb930efd7..ddbac897b5 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -38,26 +38,26 @@ def angular_unit_enum(): def extract_alembic_flags_enum(): """Get flags for alembic extraction enumerator.""" return [ - {"value": "autoSubd", "label": "Auto Subd"}, + {"label": "Auto Subd", "value": "autoSubd"}, { - "value": "dontSkipUnwrittenFrames", - "label": "Dont Skip Unwritten Frames" + "label": "Dont Skip Unwritten Frames", + "value": "dontSkipUnwrittenFrames" }, - {"value": "eulerFilter", "label": "Euler Filter"}, - {"value": "noNormals", "label": "No Normals"}, - {"value": "preRoll", "label": "Pre Roll"}, - {"value": "renderableOnly", "label": "Renderable Only"}, - {"value": "stripNamespaces", "label": "Strip Namespaces"}, - {"value": "uvWrite", "label": "UV Write"}, - {"value": "uvsOnly", "label": "UVs Only"}, - {"value": "verbose", "label": "Verbose"}, - {"value": "wholeFrameGeo", "label": "Whole Frame Geo"}, - {"value": "worldSpace", "label": "World Space"}, - {"value": "writeColorSets", "label": "Write Color Sets"}, - {"value": "writeFaceSets", "label": "Write Face Sets"}, - {"value": "writeNormals", "label": "Write Normals"}, - {"value": "writeUVSets", "label": "Write UV Sets"}, - {"value": "writeVisibility", "label": "Write Visibility"} + {"label": "Euler Filter", "value": "eulerFilter"}, + {"label": "No Normals", "value": "noNormals"}, + {"label": "Pre Roll", "value": "preRoll"}, + {"label": "Renderable Only", "value": "renderableOnly"}, + {"label": "Strip Namespaces", "value": "stripNamespaces"}, + {"label": "UV Write", "value": "uvWrite"}, + {"label": "UVs Only", "value": "uvsOnly"}, + {"label": "Verbose", "value": "verbose"}, + {"label": "Whole Frame Geo", "value": "wholeFrameGeo"}, + {"label": "World Space", "value": "worldSpace"}, + {"label": "Write Color Sets", "value": "writeColorSets"}, + {"label": "Write Face Sets", "value": "writeFaceSets"}, + {"label": "Write Normals", "value": "writeNormals"}, + {"label": "Write UV Sets", "value": "writeUVSets"}, + {"label": "Write Visibility", "value": "writeVisibility"} ] @@ -386,41 +386,141 @@ class ExtractAlembicModel(BaseSettingsModel): "included in the alembic export.", ) flags: list[str] = SettingsField( - enum_resolver=extract_alembic_flags_enum, title="Export Flags" + enum_resolver=extract_alembic_flags_enum, + title="Export Flags", + description=( + "Auto Subd - If this flag is present and the mesh has crease " + "edges, crease vertices or holes, the mesh (OPolyMesh) would now " + "be written out as an OSubD and crease info will be stored in the " + "Alembic file. Otherwise, creases info won't be preserved in " + "Alembic file unless a custom Boolean attribute SubDivisionMesh " + "has been added to mesh node and its value is true.\n" + + "Dont Skip Unwritten Frames - When evaluating multiple translate " + "jobs, this decides whether to evaluate frames between jobs when " + "there is a gap in their frame ranges.\n" + + "Euler Filter - Apply Euler filter while sampling rotations.\n" + + "No Normals - Present normal data for Alembic poly meshes will not" + " be written.\n" + + "Pre Roll - This frame range will not be sampled.\n" + + "Renderable Only - Only export renderable visible shapes.\n" + + "Strip Namespaces - Namespaces will be stripped off of the node " + "before being written to Alembic.\n" + + "UV Write - Uv data for PolyMesh and SubD shapes will be written " + "to the Alembic file.\n" + + "UVs Only - If this flag is present, only uv data for PolyMesh and" + " SubD shapes will be written to the Alembic file.\n" + + "Verbose - Prints the current frame that is being evaluated.\n" + + "Whole Frame Geo - Data for geometry will only be written out on " + "whole frames.\n" + + "World Space - Any root nodes will be stored in world space.\n" + + "Write Color Sets - Write vertex colors with the geometry.\n" + + "Write Face Sets - Write face sets with the geometry.\n" + + "Write Normals - Write normals with the deforming geometry.\n" + + "Write UV Sets - Write all uv sets on MFnMeshes as vector 2 " + "indexed geometry parameters with face varying scope.\n" + + "Write Visibility - Visibility state will be stored in the Alembic" + " file. Otherwise everything written out is treated as visible." + ) + ) + attr: str = SettingsField( + title="Custom Attributes", + placeholder="attr1;attr2", + description=( + "Attributes matching by name will be included in the Alembic " + "export. Attributes should be separated by semi-colon `;`" + ) + ) + attrPrefix: str = SettingsField( + title="Custom Attributes Prefix", + placeholder="attr1;attr2", + description=( + "Attributes starting with these prefixes will be included in the " + "Alembic export. Attributes should be separated by semi-colon `;`" + ) ) - attr: str = SettingsField(title="Custom Attributes") - attrPrefix: str = SettingsField(title="Custom Attributes Prefix") dataFormat: str = SettingsField( - enum_resolver=extract_alembic_data_format_enum, title="Data Format" + enum_resolver=extract_alembic_data_format_enum, + title="Data Format", + description="The data format to use to write the file." + ) + melPerFrameCallback: str = SettingsField( + title="Mel Per Frame Callback", + description=( + "When each frame (and the static frame) is evaluated the string " + "specified is evaluated as a Mel command." + ) + ) + melPostFrameCallback: str = SettingsField( + title="Mel Post Frame Callback", + description=( + "When the translation has finished the string specified is " + "evaluated as a Mel command." + ) ) - melPerFrameCallback: str = SettingsField(title="Mel Per Frame Callback") - melPostFrameCallback: str = SettingsField(title="Mel Post Frame Callback") - preRollStartFrame: int = SettingsField(title="Pre Roll Start Frame") pythonPerFrameCallback: str = SettingsField( - title="Python Per Frame Callback" + title="Python Per Frame Callback", + description=( + "When each frame (and the static frame) is evaluated the string " + "specified is evaluated as a python command." + ) ) pythonPostJobCallback: str = SettingsField( - title="Python Post Job Callback" + title="Python Post Job Callback", + description=( + "When the translation has finished the string specified is " + "evaluated as a python command." + ) + ) + preRollStartFrame: int = SettingsField( + title="Pre Roll Start Frame", + description=( + "The frame to start scene evaluation at. This is used to set the " + "starting frame for time dependent translations and can be used to" + " evaluate run-up that isn't actually translated." + ) ) userAttr: str = SettingsField( title="User Attr", placeholder="attr1;attr2", description=( - "Attributes matching by name will be included in the Alembic export. " - "Attributes should be separated by semi-colon `;`" + "Attributes matching by name will be included in the Alembic " + "export. Attributes should be separated by semi-colon `;`" ) ) userAttrPrefix: str = SettingsField( title="User Attr Prefix", placeholder="attr1;attr2", description=( - "Attributes starting with these prefixes will be included in the Alembic export. " - "Attributes should be separated by semi-colon `;`" + "Attributes starting with these prefixes will be included in the " + "Alembic export. Attributes should be separated by semi-colon `;`" ) ) - visibleOnly: bool = SettingsField(title="Visible Only") + visibleOnly: bool = SettingsField( + title="Visible Only", + description="Only export dag objects visible during frame range." + ) overrides: list[str] = SettingsField( - enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides" + enum_resolver=extract_alembic_overrides_enum, + title="Exposed Overrides", + description=( + "Expose the attribute in this list to the user when publishing." + ) ) From b5a5ad4a28c65f92292854222315cc4768b4af8d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 5 Apr 2024 08:14:23 +0100 Subject: [PATCH 131/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index ddbac897b5..0a9516d68f 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -448,7 +448,7 @@ class ExtractAlembicModel(BaseSettingsModel): ) attrPrefix: str = SettingsField( title="Custom Attributes Prefix", - placeholder="attr1;attr2", + placeholder="prefix1;prefix2", description=( "Attributes starting with these prefixes will be included in the " "Alembic export. Attributes should be separated by semi-colon `;`" From aa2e6c905cfa73f11fa0f6cd181e0e811fe0319c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 5 Apr 2024 08:14:35 +0100 Subject: [PATCH 132/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 0a9516d68f..7cb98a3352 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -505,7 +505,7 @@ class ExtractAlembicModel(BaseSettingsModel): ) userAttrPrefix: str = SettingsField( title="User Attr Prefix", - placeholder="attr1;attr2", + placeholder="prefix1;prefix2", description=( "Attributes starting with these prefixes will be included in the " "Alembic export. Attributes should be separated by semi-colon `;`" From 0e6aa24e97ff714e74434b94c6a7867809a8542b Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 5 Apr 2024 08:14:45 +0100 Subject: [PATCH 133/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 7cb98a3352..521a50fea2 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -100,7 +100,7 @@ def extract_alembic_overrides_enum(): {"label": "UVs Only", "value": "uvsOnly"}, {"label": "Verbose", "value": "verbose"}, {"label": "Visible Only", "value": "visibleOnly"}, - {"label": "Whole FrameGeo", "value": "wholeFrameGeo"}, + {"label": "Whole Frame Geo", "value": "wholeFrameGeo"}, {"label": "World Space", "value": "worldSpace"}, {"label": "Write Color Sets", "value": "writeColorSets"}, {"label": "Write Creases", "value": "writeCreases"}, From df201386f52721803aa6280da236886881ee6887 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 16:04:25 +0200 Subject: [PATCH 134/244] Tweak validation --- .../publish/validate_cop_output_node.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index 16e72491cc..91bd36018a 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -25,9 +25,14 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Output node '{}' is incorrect. " - "See plug-in log for details.").format(invalid), - title=self.label + "Output node '{}' is incorrect. " + "See plug-in log for details.".format(invalid), + title=self.label, + description=( + "### Invalid COP output node\n\n" + "The output node path for the instance must be set to a " + "valid COP node path.\n\nSee the log for more details." + ) ) @classmethod @@ -48,8 +53,8 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): cls.log.error( "Output node %s is not a COP node. " "COP Path must point to a COP node, " - "instead found category type: %s" - % (output_node.path(), output_node.type().category().name()) + "instead found category type: %s", + output_node.path(), output_node.type().category().name() ) return [output_node.path()] @@ -57,10 +62,7 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): # is Cop2 to avoid potential edge case scenarios even though # the isinstance check above should be stricter than this category if output_node.type().category().name() != "Cop2": - raise PublishValidationError( - f"Output node {output_node.path()} is not of category Cop2.", - description=( - "### Invalid COP output node\n\n" - "The output node path for the instance must be set to a " - "valid COP node path. See the log for more details." - )) + cls.log.error( + "Output node %s is not of category Cop2.", output_node.path() + ) + return [output_node.path()] From 6f8ab66eb2684b36326a563bc7c91d792ce88484 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Apr 2024 09:32:22 +0200 Subject: [PATCH 135/244] Update client/ayon_core/hosts/maya/plugins/load/load_as_template.py Co-authored-by: Toke Jepsen --- client/ayon_core/hosts/maya/plugins/load/load_as_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_as_template.py b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py index 5c546a1837..f696d369e3 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_as_template.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_as_template.py @@ -13,7 +13,7 @@ from ayon_core.hosts.maya.api.workfile_template_builder import ( class LoadAsTemplate(load.LoaderPlugin): """Load workfile as a template """ - product_types = {"workfile"} + product_types = {"workfile", "mayaScene"} label = "Load as template" representations = ["ma", "mb"] icon = "wrench" From ddf90da4fdc2284b3ef05eed265d730fad4db23b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 10 Apr 2024 10:13:59 +0100 Subject: [PATCH 136/244] Working version --- client/ayon_core/hosts/hiero/api/events.py | 4 + client/ayon_core/hosts/hiero/api/lib.py | 82 +++++++++++++++---- client/ayon_core/hosts/hiero/api/tags.py | 2 +- client/ayon_core/hosts/nuke/api/lib.py | 75 +++++++++-------- client/ayon_core/hosts/nuke/api/plugin.py | 1 - server_addon/hiero/server/settings/imageio.py | 18 ++-- server_addon/hiero/server/version.py | 2 +- server_addon/nuke/server/settings/imageio.py | 39 +++++++-- server_addon/nuke/server/version.py | 2 +- 9 files changed, 158 insertions(+), 67 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/events.py b/client/ayon_core/hosts/hiero/api/events.py index 304605e24e..663004abd2 100644 --- a/client/ayon_core/hosts/hiero/api/events.py +++ b/client/ayon_core/hosts/hiero/api/events.py @@ -8,6 +8,7 @@ from .lib import ( sync_avalon_data_to_workfile, launch_workfiles_app, before_project_save, + apply_colorspace_project ) from .tags import add_tags_to_workfile from .menu import update_menu_task_label @@ -44,6 +45,8 @@ def afterNewProjectCreated(event): # reset workfiles startup not to open any more in session os.environ["WORKFILES_STARTUP"] = "0" + apply_colorspace_project() + def beforeProjectLoad(event): log.info("before project load event...") @@ -122,6 +125,7 @@ def register_hiero_events(): except RuntimeError: pass + def register_events(): """ Adding all callbacks. diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py index 8682ff7780..aaf99546c7 100644 --- a/client/ayon_core/hosts/hiero/api/lib.py +++ b/client/ayon_core/hosts/hiero/api/lib.py @@ -11,7 +11,6 @@ import warnings import json import ast import secrets -import shutil import hiero from qtpy import QtWidgets, QtCore @@ -36,9 +35,6 @@ from .constants import ( DEFAULT_SEQUENCE_NAME, DEFAULT_BIN_NAME ) -from ayon_core.pipeline.colorspace import ( - get_imageio_config -) class _CTX: @@ -105,9 +101,9 @@ def flatten(list_): def get_current_project(remove_untitled=False): - projects = flatten(hiero.core.projects()) + projects = hiero.core.projects() if not remove_untitled: - return next(iter(projects)) + return projects[0] # if remove_untitled for proj in projects: @@ -1050,18 +1046,68 @@ def _set_hrox_project_knobs(doc, **knobs): def apply_colorspace_project(): - project_name = get_current_project_name() - # get path the the active projects - project = get_current_project(remove_untitled=True) - current_file = project.path() - - # close the active project - project.close() + """Apply colorspaces from settings. + Due to not being able to set the project settings through the Python API, + we need to do use some dubious code to find the widgets and set them. It is + possible to set the project settings without traversing through the widgets + but it involves reading the hrox files from disk with XML, so no in-memory + support. See https://community.foundry.com/discuss/topic/137771/change-a-project-s-default-color-transform-with-python # noqa + for more details. + """ # get presets for hiero + project_name = get_current_project_name() imageio = get_project_settings(project_name)["hiero"]["imageio"] presets = imageio.get("workfile") + # Open Project Settings UI. + for act in hiero.ui.registeredActions(): + if act.objectName() == "foundry.project.settings": + act.trigger() + + # Find widgets from their sibling label. + labels = { + "Working Space:": "workingSpace", + "Viewer:": "viewerLut", + "Thumbnails:": "thumbnailLut", + "Monitor Out:": "monitorOutLut", + "8 Bit Files:": "eightBitLut", + "16 Bit Files:": "sixteenBitLut", + "Log Files:": "logLut", + "Floating Point Files:": "floatLut" + } + widgets = {x: None for x in labels.values()} + + def _recursive_children(widget, labels, widgets): + children = widget.children() + for count, child in enumerate(children): + if isinstance(child, QtWidgets.QLabel): + if child.text() in labels.keys(): + widgets[labels[child.text()]] = children[count + 1] + _recursive_children(child, labels, widgets) + + app = QtWidgets.QApplication.instance() + title = "Project Settings" + for widget in app.topLevelWidgets(): + if isinstance(widget, QtWidgets.QMainWindow): + if widget.windowTitle() != title: + continue + _recursive_children(widget, labels, widgets) + widget.close() + + msg = "Setting value \"{}\" is not a valid option for \"{}\"" + for key, widget in widgets.items(): + options = [widget.itemText(i) for i in range(widget.count())] + setting_value = presets[key] + assert setting_value in options, msg.format(setting_value, key) + widget.setCurrentText(presets[key]) + + # This code block is for setting up project colorspaces for files on disk. + # Due to not having Python API access to set the project settings, the + # Foundry recommended way is to modify the hrox files on disk with XML. See + # this forum thread for more details; + # https://community.foundry.com/discuss/topic/137771/change-a-project-s-default-color-transform-with-python # noqa + ''' # backward compatibility layer # TODO: remove this after some time config_data = get_imageio_config( @@ -1074,6 +1120,13 @@ def apply_colorspace_project(): "ocioConfigName": "custom" }) + # get path the the active projects + project = get_current_project() + current_file = project.path() + + msg = "The project needs to be saved to disk to apply colorspace settings." + assert current_file, msg + # save the workfile as subversion "comment:_colorspaceChange" split_current_file = os.path.splitext(current_file) copy_current_file = current_file @@ -1116,6 +1169,7 @@ def apply_colorspace_project(): # open the file as current project hiero.core.openProject(copy_current_file) + ''' def apply_colorspace_clips(): @@ -1125,10 +1179,8 @@ def apply_colorspace_clips(): # get presets for hiero imageio = get_project_settings(project_name)["hiero"]["imageio"] - from pprint import pprint presets = imageio.get("regexInputs", {}).get("inputs", {}) - pprint(presets) for clip in clips: clip_media_source_path = clip.mediaSource().firstpath() clip_name = clip.name() diff --git a/client/ayon_core/hosts/hiero/api/tags.py b/client/ayon_core/hosts/hiero/api/tags.py index 5abfee75d0..d4acb23493 100644 --- a/client/ayon_core/hosts/hiero/api/tags.py +++ b/client/ayon_core/hosts/hiero/api/tags.py @@ -144,7 +144,7 @@ def add_tags_to_workfile(): # Get project task types. project_name = get_current_project_name() project_entity = ayon_api.get_project(project_name) - task_types = project_entity["taskType"] + task_types = project_entity["taskTypes"] nks_pres_tags["[Tasks]"] = {} log.debug("__ tasks: {}".format(task_types)) for task_type in task_types: diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index 78cbe85097..e3505a16f2 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -1495,18 +1495,28 @@ class WorkfileSettings(object): filter_knobs = [ "viewerProcess", - "wipe_position" + "wipe_position", + "monitorOutOutputTransform" ] + display, viewer = get_viewer_config_from_string( + viewer_dict["viewerProcess"] + ) + viewer_process = create_viewer_profile_string( + viewer, display, path_like=False + ) + display, viewer = get_viewer_config_from_string( + viewer_dict["output_transform"] + ) + output_transform = create_viewer_profile_string( + viewer, display, path_like=False + ) erased_viewers = [] for v in nuke.allNodes(filter="Viewer"): # set viewProcess to preset from settings - v["viewerProcess"].setValue( - str(viewer_dict["viewerProcess"]) - ) + v["viewerProcess"].setValue(viewer_process) - if str(viewer_dict["viewerProcess"]) \ - not in v["viewerProcess"].value(): + if viewer_process not in v["viewerProcess"].value(): copy_inputs = v.dependencies() copy_knobs = {k: v[k].value() for k in v.knobs() if k not in filter_knobs} @@ -1524,11 +1534,11 @@ class WorkfileSettings(object): # set copied knobs for k, v in copy_knobs.items(): - print(k, v) nv[k].setValue(v) # set viewerProcess - nv["viewerProcess"].setValue(str(viewer_dict["viewerProcess"])) + nv["viewerProcess"].setValue(viewer_process) + nv["monitorOutOutputTransform"].setValue(output_transform) if erased_viewers: log.warning( @@ -1547,7 +1557,6 @@ class WorkfileSettings(object): host_name="nuke" ) - viewer_process_settings = imageio_host["viewer"]["viewerProcess"] workfile_settings = imageio_host["workfile"] color_management = workfile_settings["color_management"] native_ocio_config = workfile_settings["native_ocio_config"] @@ -1574,29 +1583,6 @@ class WorkfileSettings(object): residual_path )) - # get monitor lut from settings respecting Nuke version differences - monitor_lut = workfile_settings["thumbnail_space"] - monitor_lut_data = self._get_monitor_settings( - viewer_process_settings, monitor_lut - ) - monitor_lut_data["workingSpaceLUT"] = ( - workfile_settings["working_space"] - ) - - # then set the rest - for knob, value_ in monitor_lut_data.items(): - # skip unfilled ocio config path - # it will be dict in value - if isinstance(value_, dict): - continue - # skip empty values - if not value_: - continue - if self._root_node[knob].value() not in value_: - self._root_node[knob].setValue(str(value_)) - log.debug("nuke.root()['{}'] changed to: {}".format( - knob, value_)) - # set ocio config path if config_data: config_path = config_data["path"].replace("\\", "/") @@ -1611,6 +1597,31 @@ class WorkfileSettings(object): if correct_settings: self._set_ocio_config_path_to_workfile(config_data) + # get monitor lut from settings respecting Nuke version differences + monitor_lut_data = self._get_monitor_settings( + workfile_settings["monitor_out_lut"], + workfile_settings["monitor_lut"] + ) + monitor_lut_data.update({ + "workingSpaceLUT": workfile_settings["working_space"], + "int8Lut": workfile_settings["int_8_lut"], + "int16Lut": workfile_settings["int_16_lut"], + "logLut": workfile_settings["log_lut"], + "floatLut": workfile_settings["float_lut"] + }) + + # then set the rest + for knob, value_ in monitor_lut_data.items(): + # skip unfilled ocio config path + # it will be dict in value + if isinstance(value_, dict): + continue + # skip empty values + if not value_: + continue + self._root_node[knob].setValue(str(value_)) + log.debug("nuke.root()['{}'] changed to: {}".format(knob, value_)) + def _get_monitor_settings(self, viewer_lut, monitor_lut): """ Get monitor settings from viewer and monitor lut diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/client/ayon_core/hosts/nuke/api/plugin.py index 6aa098c558..56c30a8ff5 100644 --- a/client/ayon_core/hosts/nuke/api/plugin.py +++ b/client/ayon_core/hosts/nuke/api/plugin.py @@ -1151,7 +1151,6 @@ def _remove_old_knobs(node): "OpenpypeDataGroup", "OpenpypeDataGroup_End", "deadlinePriority", "deadlineChunkSize", "deadlineConcurrentTasks", "Deadline" ] - print(node.name()) # remove all old knobs for knob in node.allKnobs(): diff --git a/server_addon/hiero/server/settings/imageio.py b/server_addon/hiero/server/settings/imageio.py index f2bc71ac33..9e15e15597 100644 --- a/server_addon/hiero/server/settings/imageio.py +++ b/server_addon/hiero/server/settings/imageio.py @@ -149,15 +149,15 @@ class ImageIOSettings(BaseSettingsModel): DEFAULT_IMAGEIO_SETTINGS = { "workfile": { - "ocioConfigName": "nuke-default", - "workingSpace": "linear", - "viewerLut": "sRGB", - "eightBitLut": "sRGB", - "sixteenBitLut": "sRGB", - "logLut": "Cineon", - "floatLut": "linear", - "thumbnailLut": "sRGB", - "monitorOutLut": "sRGB" + "ocioConfigName": "aces_1.2", + "workingSpace": "role_scene_linear", + "viewerLut": "ACES/sRGB", + "eightBitLut": "role_matte_paint", + "sixteenBitLut": "role_texture_paint", + "logLut": "role_compositing_log", + "floatLut": "role_scene_linear", + "thumbnailLut": "ACES/sRGB", + "monitorOutLut": "ACES/sRGB" }, "regexInputs": { "inputs": [ diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/hiero/server/version.py +++ b/server_addon/hiero/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py index 1b84457133..9cdb0bf1d7 100644 --- a/server_addon/nuke/server/settings/imageio.py +++ b/server_addon/nuke/server/settings/imageio.py @@ -97,8 +97,23 @@ class WorkfileColorspaceSettings(BaseSettingsModel): working_space: str = SettingsField( title="Working Space" ) - thumbnail_space: str = SettingsField( - title="Thumbnail Space" + monitor_lut: str = SettingsField( + title="Thumbnails" + ) + monitor_out_lut: str = SettingsField( + title="Monitor Out" + ) + int_8_lut: str = SettingsField( + title="8-bit Files" + ) + int_16_lut: str = SettingsField( + title="16-bit Files" + ) + log_lut: str = SettingsField( + title="Log Files" + ) + float_lut: str = SettingsField( + title="Float Files" ) @@ -120,6 +135,9 @@ class ViewProcessModel(BaseSettingsModel): viewerProcess: str = SettingsField( title="Viewer Process Name" ) + output_transform: str = SettingsField( + title="Output Transform" + ) class ImageIOConfigModel(BaseSettingsModel): @@ -214,16 +232,23 @@ class ImageIOSettings(BaseSettingsModel): DEFAULT_IMAGEIO_SETTINGS = { "viewer": { - "viewerProcess": "sRGB (default)" + "viewerProcess": "ACES/sRGB", + "output_transform": "ACES/sRGB" }, "baking": { - "viewerProcess": "rec709 (default)" + "viewerProcess": "ACES/Rec.709", + "output_transform": "ACES/Rec.709" }, "workfile": { "color_management": "OCIO", - "native_ocio_config": "nuke-default", - "working_space": "scene_linear", - "thumbnail_space": "sRGB (default)", + "native_ocio_config": "aces_1.2", + "working_space": "role_scene_linear", + "monitor_lut": "ACES/sRGB", + "monitor_out_lut": "ACES/sRGB", + "int_8_lut": "role_matte_paint", + "int_16_lut": "role_texture_paint", + "log_lut": "role_compositing_log", + "float_lut": "role_scene_linear" }, "nodes": { "required_nodes": [ diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 569b1212f7..0c5c30071a 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.10" +__version__ = "0.1.11" From d16945311574f13fcbf9311b58273737bcb438fb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:16:51 +0100 Subject: [PATCH 137/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- .../maya/server/settings/publishers.py | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 521a50fea2..9d7fc9192f 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -389,53 +389,54 @@ class ExtractAlembicModel(BaseSettingsModel): enum_resolver=extract_alembic_flags_enum, title="Export Flags", description=( - "Auto Subd - If this flag is present and the mesh has crease " + "**Auto Subd** - If this flag is present and the mesh has crease " "edges, crease vertices or holes, the mesh (OPolyMesh) would now " "be written out as an OSubD and crease info will be stored in the " "Alembic file. Otherwise, creases info won't be preserved in " "Alembic file unless a custom Boolean attribute SubDivisionMesh " "has been added to mesh node and its value is true.\n" - "Dont Skip Unwritten Frames - When evaluating multiple translate " - "jobs, this decides whether to evaluate frames between jobs when " - "there is a gap in their frame ranges.\n" + "**Dont Skip Unwritten Frames** - When evaluating multiple " + "translate jobs, this decides whether to evaluate frames between " + "jobs when there is a gap in their frame ranges.\n" - "Euler Filter - Apply Euler filter while sampling rotations.\n" + "**Euler Filter** - Apply Euler filter while sampling rotations.\n" - "No Normals - Present normal data for Alembic poly meshes will not" - " be written.\n" + "**No Normals** - Present normal data for Alembic poly meshes will" + " not be written.\n" - "Pre Roll - This frame range will not be sampled.\n" + "**Pre Roll** - This frame range will not be sampled.\n" - "Renderable Only - Only export renderable visible shapes.\n" + "**Renderable Only** - Only export renderable visible shapes.\n" - "Strip Namespaces - Namespaces will be stripped off of the node " - "before being written to Alembic.\n" + "**Strip Namespaces** - Namespaces will be stripped off of the " + "node before being written to Alembic.\n" - "UV Write - Uv data for PolyMesh and SubD shapes will be written " - "to the Alembic file.\n" + "**UV Write** - Uv data for PolyMesh and SubD shapes will be " + "written to the Alembic file.\n" - "UVs Only - If this flag is present, only uv data for PolyMesh and" - " SubD shapes will be written to the Alembic file.\n" + "**UVs Only** - If this flag is present, only uv data for PolyMesh" + " and SubD shapes will be written to the Alembic file.\n" - "Verbose - Prints the current frame that is being evaluated.\n" + "**Verbose** - Prints the current frame that is being evaluated.\n" - "Whole Frame Geo - Data for geometry will only be written out on " - "whole frames.\n" + "**Whole Frame Geo** - Data for geometry will only be written out " + "on whole frames.\n" - "World Space - Any root nodes will be stored in world space.\n" + "**World Space** - Any root nodes will be stored in world space.\n" - "Write Color Sets - Write vertex colors with the geometry.\n" + "**Write Color Sets** - Write vertex colors with the geometry.\n" - "Write Face Sets - Write face sets with the geometry.\n" + "**Write Face Sets** - Write face sets with the geometry.\n" - "Write Normals - Write normals with the deforming geometry.\n" + "**Write Normals** - Write normals with the deforming geometry.\n" - "Write UV Sets - Write all uv sets on MFnMeshes as vector 2 " + "**Write UV Sets** - Write all uv sets on MFnMeshes as vector 2 " "indexed geometry parameters with face varying scope.\n" - "Write Visibility - Visibility state will be stored in the Alembic" - " file. Otherwise everything written out is treated as visible." + "**Write Visibility** - Visibility state will be stored in the " + "Alembic file. Otherwise everything written out is treated as " + "visible." ) ) attr: str = SettingsField( From d8e27456bf572f9dae7bb5c77475c8b49a45bdbe Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:18:22 +0100 Subject: [PATCH 138/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/publish/extract_pointcache.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 3db6574c85..5e5eefae43 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -375,9 +375,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): "Otherwise everything written out is treated as visible." ) } - tooltip = "" - for flag in flags: - tooltip += "{} - {}\n".format(flag, tooltips[flag]) + tooltip = "\n".join(f"{flag} - {tooltips['flag']}" for flag in flags) defs.append( EnumDef( "flags", From 7478d876fb62ac6a5ce240b516e5816ce40d9f3b Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:19:48 +0100 Subject: [PATCH 139/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 5e5eefae43..9b586ee808 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -375,7 +375,7 @@ class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): "Otherwise everything written out is treated as visible." ) } - tooltip = "\n".join(f"{flag} - {tooltips['flag']}" for flag in flags) + tooltip = "\n".join(f"{flag} - {tooltips['flag']}" for flag in sorted(flags)) defs.append( EnumDef( "flags", From 861c2ad8f4cf9b98427ca1f8ce7ab15743a7f7b8 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:28:21 +0100 Subject: [PATCH 140/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py --- .../hosts/maya/plugins/publish/extract_pointcache.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 9b586ee808..5202e0991c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -415,10 +415,9 @@ class ExtractAnimation(ExtractAlembic): roots = cmds.sets(out_set, query=True) or [] # Include all descendants - nodes = ( - roots - + cmds.listRelatives(roots, allDescendents=True, fullPath=True) - or [] - ) + nodes = roots + nodes += cmds.listRelatives( + roots, allDescendents=True, fullPath=True + ) or [] return nodes, roots From 9bcc0389ccf5a5225fcd09aef27571ee0b81c22a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:30:54 +0100 Subject: [PATCH 141/244] Update server_addon/maya/server/settings/publishers.py --- server_addon/maya/server/settings/publishers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 9d7fc9192f..015b7339b3 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -493,7 +493,9 @@ class ExtractAlembicModel(BaseSettingsModel): description=( "The frame to start scene evaluation at. This is used to set the " "starting frame for time dependent translations and can be used to" - " evaluate run-up that isn't actually translated." + " evaluate run-up that isn't actually translated.\n" + "NOTE: preRoll needs to be enabled in the export flags for this " + "start frame to be considered." ) ) userAttr: str = SettingsField( From 23b162a216600a756e4688cf31fa6548d6032e17 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:44:00 +0100 Subject: [PATCH 142/244] Update server_addon/maya/server/settings/publishers.py --- server_addon/maya/server/settings/publishers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 015b7339b3..96364817f5 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -405,7 +405,8 @@ class ExtractAlembicModel(BaseSettingsModel): "**No Normals** - Present normal data for Alembic poly meshes will" " not be written.\n" - "**Pre Roll** - This frame range will not be sampled.\n" + "Pre Roll - When enabled, the pre roll start frame is used to pre " + "roll the export for.\n" "**Renderable Only** - Only export renderable visible shapes.\n" From 85229cef0436cd6976f6fece57ba0948b0d3eaae Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:45:02 +0100 Subject: [PATCH 143/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 5202e0991c..36354365af 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -17,7 +17,7 @@ from ayon_core.lib import ( UISeparatorDef, UILabelDef, ) -from ayon_core.pipeline.publish import OpenPypePyblishPluginMixin +from ayon_core.pipeline.publish import AYONPyblishPluginMixin class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): From 947b008c55fe929c2fdacc5552ab9ee93b366477 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:45:07 +0100 Subject: [PATCH 144/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 36354365af..1486d66e5b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -20,7 +20,7 @@ from ayon_core.lib import ( from ayon_core.pipeline.publish import AYONPyblishPluginMixin -class ExtractAlembic(publish.Extractor, OpenPypePyblishPluginMixin): +class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, From 7dea47f608531ce19688b9fc89ef7067ed730b95 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:47:02 +0100 Subject: [PATCH 145/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 1486d66e5b..b2e925c1b7 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -408,7 +408,7 @@ class ExtractAnimation(ExtractAlembic): # Collect the out set nodes out_sets = [node for node in instance if node.endswith("out_SET")] if len(out_sets) != 1: - raise RuntimeError( + raise KnownPublishError( "Couldn't find exactly one out_SET: {0}".format(out_sets) ) out_set = out_sets[0] From 5471cdc8e32cf73b31842b1acd0b117687d3798d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:47:55 +0100 Subject: [PATCH 146/244] Update client/ayon_core/hosts/maya/api/alembic.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hosts/maya/api/alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index b657262b4d..b67e0e0062 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -4,7 +4,7 @@ import os from maya import cmds # noqa -from openpype.hosts.maya.api.lib import evaluation +from ayon_core.hosts.maya.api.lib import evaluation log = logging.getLogger(__name__) From a7450e4ab5c72e9b33fa6909371dd23f6f717d45 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 11 Apr 2024 19:44:31 +0100 Subject: [PATCH 147/244] Remove export flags. --- .../create/create_animation_pointcache.py | 18 +- .../plugins/publish/extract_pointcache.py | 291 ++++++++++++------ .../validate_alembic_options_defaults.py | 17 +- .../maya/server/settings/publishers.py | 264 +++++++++------- server_addon/maya/server/version.py | 2 +- 5 files changed, 349 insertions(+), 243 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 4752e52a0e..d89f5756b2 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -44,7 +44,10 @@ def extract_alembic_attributes(node_data, class_name): if class_name in publish_attributes: return node_data - extract_alembic_flags = [ + attributes = [ + "attr", + "attrPrefix", + "visibleOnly", "writeColorSets", "writeFaceSets", "writeNormals", @@ -53,22 +56,13 @@ def extract_alembic_attributes(node_data, class_name): "worldSpace", "renderableOnly" ] - extract_alembic_attributes = [ - "attr", - "attrPrefix", - "visibleOnly" - ] - attributes = extract_alembic_flags + extract_alembic_attributes - plugin_attributes = {"flags": []} + plugin_attributes = {} for attr in attributes: if attr not in node_data["creator_attributes"].keys(): continue value = node_data["creator_attributes"].pop(attr) - if value and attr in extract_alembic_flags: - plugin_attributes["flags"].append(attr) - - if attr in extract_alembic_attributes: + if attr in attributes: plugin_attributes[attr] = value publish_attributes[class_name] = plugin_attributes diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index b2e925c1b7..e4dfbac414 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -18,6 +18,7 @@ from ayon_core.lib import ( UILabelDef, ) from ayon_core.pipeline.publish import AYONPyblishPluginMixin +from ayon_core.pipeline import KnownPublishError class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): @@ -36,21 +37,37 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): targets = ["local", "remote"] # From settings - bake_attributes = [] - bake_attribute_prefixes = [] - flags = [] attr = [] attrPrefix = [] + autoSubd = False + bake_attributes = [] + bake_attribute_prefixes = [] dataFormat = "ogawa" + dontSkipUnwrittenFrames = False + eulerFilter = False melPerFrameCallback = "" melPostJobCallback = "" + noNormals = False + overrides = [] + preRoll = False preRollStartFrame = 0 pythonPerFrameCallback = "" pythonPostJobCallback = "" + renderableOnly = False + stripNamespaces = True + uvsOnly = False + uvWrite = False userAttr = "" userAttrPrefix = "" + verbose = False visibleOnly = False - overrides = [] + wholeFrameGeo = False + worldSpace = True + writeColorSets = False + writeFaceSets = False + writeNormals = True + writeUVSets = False + writeVisibility = False def process(self, instance): if instance.data.get("farm"): @@ -101,40 +118,51 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "file": path, "attr": attrs, "attrPrefix": attr_prefixes, - "dataFormat": attribute_values.get("dataFormat", "ogawa"), + "dataFormat": attribute_values.get("dataFormat", self.dataFormat), "endFrame": end, - "eulerFilter": False, - "noNormals": False, - "preRoll": False, - "preRollStartFrame": attribute_values.get( - "preRollStartFrame", 0 + "eulerFilter": attribute_values.get( + "eulerFilter", self.eulerFilter + ), + "noNormals": attribute_values.get("noNormals", self.noNormals), + "preRoll": attribute_values.get("preRoll", self.preRoll), + "preRollStartFrame": attribute_values.get( + "preRollStartFrame", self.preRollStartFrame + ), + "renderableOnly": attribute_values.get( + "renderableOnly", self.renderableOnly ), - "renderableOnly": False, "root": root, "selection": True, "startFrame": start, "step": instance.data.get( "creator_attributes", {} - ).get("step", 1.0), - "stripNamespaces": False, - "uvWrite": False, - "verbose": False, - "wholeFrameGeo": False, - "worldSpace": False, - "writeColorSets": False, - "writeCreases": False, - "writeFaceSets": False, - "writeUVSets": False, - "writeVisibility": False, + ).get("step", 1.0), #missing + "stripNamespaces": attribute_values.get( + "stripNamespaces", self.stripNamespaces + ), + "uvWrite": attribute_values.get("uvWrite", self.uvWrite), + "verbose": attribute_values.get("verbose", self.verbose), + "wholeFrameGeo": attribute_values.get( + "wholeFrameGeo", self.wholeFrameGeo + ), + "worldSpace": attribute_values.get("worldSpace", self.worldSpace), + "writeColorSets": attribute_values.get( + "writeColorSets", self.writeColorSets + ), + "writeCreases": attribute_values.get( + "writeCreases", self.writeCreases + ), + "writeFaceSets": attribute_values.get( + "writeFaceSets", self.writeFaceSets + ), + "writeUVSets": attribute_values.get( + "writeUVSets", self.writeUVSets + ), + "writeVisibility": attribute_values.get( + "writeVisibility", self.writeVisibility + ) } - # Export flags are defined as default enabled flags plus publisher - # enabled flags. - non_exposed_flags = list(set(self.flags) - set(self.overrides)) - flags = attribute_values["flags"] + non_exposed_flags - for flag in flags: - kwargs[flag] = True - if instance.data.get("visibleOnly", False): # If we only want to include nodes that are visible in the frame # range then we need to do our own check. Alembic's `visibleOnly` @@ -226,6 +254,20 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "semi-colon `;`" ) ), + "autoSubd": BoolDef( + "autoSubd", + label="Auto Subd", + default=cls.autoSubd, + tooltip=( + "If this flag is present and the mesh has crease edges, " + "crease vertices or holes, the mesh (OPolyMesh) would now " + "be written out as an OSubD and crease info will be stored" + " in the Alembic file. Otherwise, creases info won't be " + "preserved in Alembic file unless a custom Boolean " + "attribute SubDivisionMesh has been added to mesh node and" + " its value is true." + ) + ), "dataFormat": EnumDef( "dataFormat", label="Data Format", @@ -233,6 +275,22 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): default=cls.dataFormat, tooltip="The data format to use to write the file." ), + "dontSkipUnwrittenFrames": BoolDef( + "dontSkipUnwrittenFrames", + label="Dont Skip Unwritten Frames", + default=cls.dontSkipUnwrittenFrames, + tooltip=( + "When evaluating multiple translate jobs, this decides " + "whether to evaluate frames between jobs when there is a " + "gap in their frame ranges." + ) + ), + "eulerFilter": BoolDef( + "eulerFilter", + label="Euler Filter", + default=cls.eulerFilter, + tooltip="Apply Euler filter while sampling rotations." + ), "melPerFrameCallback": TextDef( "melPerFrameCallback", label="Mel Per Frame Callback", @@ -251,6 +309,21 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "is evaluated as a Mel command." ) ), + "noNormals": BoolDef( + "noNormals", + label="No Normals", + default=cls.noNormals, + tooltip=( + "Present normal data for Alembic poly meshes will not be " + "written." + ) + ), + "preRoll": BoolDef( + "preRoll", + label="Pre Roll", + default=cls.preRoll, + tooltip="This frame range will not be sampled." + ), "preRollStartFrame": NumberDef( "preRollStartFrame", label="Pre Roll Start Frame", @@ -280,6 +353,39 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "is evaluated as a python command." ) ), + "renderableOnly": BoolDef( + "renderableOnly", + label="Renderable Only", + default=cls.renderableOnly, + tooltip="Only export renderable visible shapes." + ), + "stripNamespaces": BoolDef( + "stripNamespaces", + label="Strip Namespaces", + default=cls.stripNamespaces, + tooltip=( + "Namespaces will be stripped off of the node before being " + "written to Alembic." + ) + ), + "uvsOnly": BoolDef( + "uvsOnly", + label="UVs Only", + default=cls.uvsOnly, + tooltip=( + "If this flag is present, only uv data for PolyMesh and " + "SubD shapes will be written to the Alembic file." + ) + ), + "uvWrite": BoolDef( + "uvWrite", + label="UV Write", + default=cls.uvWrite, + tooltip=( + "Uv data for PolyMesh and SubD shapes will be written to " + "the Alembic file." + ) + ), "userAttr": TextDef( "userAttr", label="User Attr", @@ -300,11 +406,68 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "semi-colon `;`" ) ), + "verbose": BoolDef( + "verbose", + label="Verbose", + default=cls.verbose, + tooltip="Prints the current frame that is being evaluated." + ), "visibleOnly": BoolDef( "visibleOnly", label="Visible Only", default=cls.visibleOnly, tooltip="Only export dag objects visible during frame range." + ), + "wholeFrameGeo": BoolDef( + "wholeFrameGeo", + label="Whole Frame Geo", + default=cls.wholeFrameGeo, + tooltip=( + "Data for geometry will only be written out on whole " + "frames." + ) + ), + "worldSpace": BoolDef( + "worldSpace", + label="World Space", + default=cls.worldSpace, + tooltip="Any root nodes will be stored in world space." + ), + "writeColorSets": BoolDef( + "writeColorSets", + label="Write Color Sets", + default=cls.writeColorSets, + tooltip="Write vertex colors with the geometry." + ), + "writeFaceSets": BoolDef( + "writeFaceSets", + label="Write Face Sets", + default=cls.writeFaceSets, + tooltip="Write face sets with the geometry." + ), + "writeNormals": BoolDef( + "writeNormals", + label="Write Normals", + default=cls.writeNormals, + tooltip="Write normals with the deforming geometry." + ), + "writeUVSets": BoolDef( + "writeUVSets", + label="Write UV Sets", + default=cls.writeUVSets, + tooltip=( + "Write all uv sets on MFnMeshes as vector 2 indexed " + "geometry parameters with face varying scope." + ) + ), + "writeVisibility": BoolDef( + "writeVisibility", + label="Write Visibility", + default=cls.writeVisibility, + tooltip=( + "Visibility state will be stored in the Alembic file. " + "Otherwise everything written out is treated as visible." + ) ) } @@ -317,76 +480,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): # The Arguments that can be modified by the Publisher overrides = set(cls.overrides) - - # What we have set in the Settings as defaults. - flags = set(cls.flags) - - enabled_flags = [x for x in flags if x in overrides] - flags = overrides - set(override_defs.keys()) - - tooltips = { - "autoSubd": ( - "If this flag is present and the mesh has crease edges, crease" - " vertices or holes, the mesh (OPolyMesh) would now be written" - " out as an OSubD and crease info will be stored in the " - "Alembic file. Otherwise, creases info won't be preserved in " - "Alembic file unless a custom Boolean attribute " - "SubDivisionMesh has been added to mesh node and its value is " - "true." - ), - "dontSkipUnwrittenFrames": ( - "When evaluating multiple translate jobs, this decides whether" - " to evaluate frames between jobs when there is a gap in their" - " frame ranges." - ), - "eulerFilter": "Apply Euler filter while sampling rotations.", - "noNormals": ( - "Present normal data for Alembic poly meshes will not be " - "written." - ), - "preRoll": "This frame range will not be sampled.", - "renderableOnly": "Only export renderable visible shapes.", - "stripNamespaces": ( - "Namespaces will be stripped off of the node before being " - "written to Alembic." - ), - "uvWrite": ( - "Uv data for PolyMesh and SubD shapes will be written to the " - "Alembic file." - ), - "uvsOnly": ( - "If this flag is present, only uv data for PolyMesh and SubD " - "shapes will be written to the Alembic file." - ), - "verbose": "Prints the current frame that is being evaluated.", - "wholeFrameGeo": ( - "Data for geometry will only be written out on whole frames." - ), - "worldSpace": "Any root nodes will be stored in world space.", - "writeColorSets": "Write vertex colors with the geometry.", - "writeFaceSets": "Write face sets with the geometry.", - "writeNormals": "Write normals with the deforming geometry.", - "writeUVSets": ( - "Write all uv sets on MFnMeshes as vector 2 indexed geometry" - " parameters with face varying scope." - ), - "writeVisibility": ( - "Visibility state will be stored in the Alembic file. " - "Otherwise everything written out is treated as visible." - ) - } - tooltip = "\n".join(f"{flag} - {tooltips['flag']}" for flag in sorted(flags)) - defs.append( - EnumDef( - "flags", - flags, - default=enabled_flags, - multiselection=True, - label="Export Flags", - tooltip=tooltip, - ) - ) - for key, value in override_defs.items(): if key not in overrides: continue diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index fdf1e1c842..5e354b5a65 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -8,7 +8,7 @@ class ValidateAlembicOptionsDefaults( pyblish.api.InstancePlugin, OptionalPyblishPluginMixin ): """Validate the attributes on the instance are defaults. - + The defaults are defined in the project settings. """ @@ -29,11 +29,6 @@ class ValidateAlembicOptionsDefaults( def _get_settings(cls, context): maya_settings = context.data["project_settings"]["maya"] settings = maya_settings["publish"]["ExtractAlembic"] - # Flags are a special case since they are a combination of overrides - # and default flags from the settings. - settings["flags"] = [ - x for x in settings["flags"] if x in settings["overrides"] - ] return settings @classmethod @@ -44,16 +39,6 @@ class ValidateAlembicOptionsDefaults( ) ] - settings = cls._get_settings(instance.context) - - # Flags are a special case since they are a combination of exposed - # flags and default flags from the settings. So we need to add the - # default flags from the settings and ensure unique items. - non_exposed_flags = [ - x for x in settings["flags"] if x not in settings["overrides"] - ] - attributes["flags"] = attributes["flags"] + non_exposed_flags - return attributes def process(self, instance): diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 96364817f5..fdf1815f4b 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -35,32 +35,6 @@ def angular_unit_enum(): ] -def extract_alembic_flags_enum(): - """Get flags for alembic extraction enumerator.""" - return [ - {"label": "Auto Subd", "value": "autoSubd"}, - { - "label": "Dont Skip Unwritten Frames", - "value": "dontSkipUnwrittenFrames" - }, - {"label": "Euler Filter", "value": "eulerFilter"}, - {"label": "No Normals", "value": "noNormals"}, - {"label": "Pre Roll", "value": "preRoll"}, - {"label": "Renderable Only", "value": "renderableOnly"}, - {"label": "Strip Namespaces", "value": "stripNamespaces"}, - {"label": "UV Write", "value": "uvWrite"}, - {"label": "UVs Only", "value": "uvsOnly"}, - {"label": "Verbose", "value": "verbose"}, - {"label": "Whole Frame Geo", "value": "wholeFrameGeo"}, - {"label": "World Space", "value": "worldSpace"}, - {"label": "Write Color Sets", "value": "writeColorSets"}, - {"label": "Write Face Sets", "value": "writeFaceSets"}, - {"label": "Write Normals", "value": "writeNormals"}, - {"label": "Write UV Sets", "value": "writeUVSets"}, - {"label": "Write Visibility", "value": "writeVisibility"} - ] - - def extract_alembic_data_format_enum(): return [ {"label": "ogawa", "value": "ogawa"}, @@ -375,71 +349,6 @@ class ExtractAlembicModel(BaseSettingsModel): families: list[str] = SettingsField( default_factory=list, title="Families") - bake_attributes: list[str] = SettingsField( - default_factory=list, title="Bake Attributes", - description="List of attributes that will be included in the alembic " - "export.", - ) - bake_attribute_prefixes: list[str] = SettingsField( - default_factory=list, title="Bake Attribute Prefixes", - description="List of attribute prefixes for attributes that will be " - "included in the alembic export.", - ) - flags: list[str] = SettingsField( - enum_resolver=extract_alembic_flags_enum, - title="Export Flags", - description=( - "**Auto Subd** - If this flag is present and the mesh has crease " - "edges, crease vertices or holes, the mesh (OPolyMesh) would now " - "be written out as an OSubD and crease info will be stored in the " - "Alembic file. Otherwise, creases info won't be preserved in " - "Alembic file unless a custom Boolean attribute SubDivisionMesh " - "has been added to mesh node and its value is true.\n" - - "**Dont Skip Unwritten Frames** - When evaluating multiple " - "translate jobs, this decides whether to evaluate frames between " - "jobs when there is a gap in their frame ranges.\n" - - "**Euler Filter** - Apply Euler filter while sampling rotations.\n" - - "**No Normals** - Present normal data for Alembic poly meshes will" - " not be written.\n" - - "Pre Roll - When enabled, the pre roll start frame is used to pre " - "roll the export for.\n" - - "**Renderable Only** - Only export renderable visible shapes.\n" - - "**Strip Namespaces** - Namespaces will be stripped off of the " - "node before being written to Alembic.\n" - - "**UV Write** - Uv data for PolyMesh and SubD shapes will be " - "written to the Alembic file.\n" - - "**UVs Only** - If this flag is present, only uv data for PolyMesh" - " and SubD shapes will be written to the Alembic file.\n" - - "**Verbose** - Prints the current frame that is being evaluated.\n" - - "**Whole Frame Geo** - Data for geometry will only be written out " - "on whole frames.\n" - - "**World Space** - Any root nodes will be stored in world space.\n" - - "**Write Color Sets** - Write vertex colors with the geometry.\n" - - "**Write Face Sets** - Write face sets with the geometry.\n" - - "**Write Normals** - Write normals with the deforming geometry.\n" - - "**Write UV Sets** - Write all uv sets on MFnMeshes as vector 2 " - "indexed geometry parameters with face varying scope.\n" - - "**Write Visibility** - Visibility state will be stored in the " - "Alembic file. Otherwise everything written out is treated as " - "visible." - ) - ) attr: str = SettingsField( title="Custom Attributes", placeholder="attr1;attr2", @@ -456,11 +365,44 @@ class ExtractAlembicModel(BaseSettingsModel): "Alembic export. Attributes should be separated by semi-colon `;`" ) ) + autoSubd: bool = SettingsField( + title="Auto Subd", + description=( + "If this flag is present and the mesh has crease edges, crease " + "vertices or holes, the mesh (OPolyMesh) would now be written out " + "as an OSubD and crease info will be stored in the Alembic file. " + "Otherwise, creases info won't be preserved in Alembic file unless" + " a custom Boolean attribute SubDivisionMesh has been added to " + "mesh node and its value is true." + ) + ) + bake_attributes: list[str] = SettingsField( + default_factory=list, title="Bake Attributes", + description="List of attributes that will be included in the alembic " + "export.", + ) + bake_attribute_prefixes: list[str] = SettingsField( + default_factory=list, title="Bake Attribute Prefixes", + description="List of attribute prefixes for attributes that will be " + "included in the alembic export.", + ) dataFormat: str = SettingsField( enum_resolver=extract_alembic_data_format_enum, title="Data Format", description="The data format to use to write the file." ) + dontSkipUnwrittenFrames: bool = SettingsField( + title="Dont Skip Unwritten Frames", + description=( + "When evaluating multiple translate jobs, this decides whether to " + "evaluate frames between jobs when there is a gap in their frame " + "ranges." + ) + ) + eulerFilter: bool = SettingsField( + title="Euler Filter", + description="Apply Euler filter while sampling rotations." + ) melPerFrameCallback: str = SettingsField( title="Mel Per Frame Callback", description=( @@ -475,6 +417,29 @@ class ExtractAlembicModel(BaseSettingsModel): "evaluated as a Mel command." ) ) + noNormals: bool = SettingsField( + title="No Normals", + description=( + "Present normal data for Alembic poly meshes will not be written." + ) + ) + preRoll: bool = SettingsField( + title="Pre Roll", + description=( + "When enabled, the pre roll start frame is used to pre roll the " + "export for." + ) + ) + preRollStartFrame: int = SettingsField( + title="Pre Roll Start Frame", + description=( + "The frame to start scene evaluation at. This is used to set the " + "starting frame for time dependent translations and can be used to" + " evaluate run-up that isn't actually translated.\n" + "NOTE: preRoll needs to be enabled in the export flags for this " + "start frame to be considered." + ) + ) pythonPerFrameCallback: str = SettingsField( title="Python Per Frame Callback", description=( @@ -489,14 +454,15 @@ class ExtractAlembicModel(BaseSettingsModel): "evaluated as a python command." ) ) - preRollStartFrame: int = SettingsField( - title="Pre Roll Start Frame", + renderableOnly: bool = SettingsField( + title="Renderable Only", + description="Only export renderable visible shapes." + ) + stripNamespaces: bool = SettingsField( + title="Strip Namespaces", description=( - "The frame to start scene evaluation at. This is used to set the " - "starting frame for time dependent translations and can be used to" - " evaluate run-up that isn't actually translated.\n" - "NOTE: preRoll needs to be enabled in the export flags for this " - "start frame to be considered." + "Namespaces will be stripped off of the node before being written " + "to Alembic." ) ) userAttr: str = SettingsField( @@ -515,10 +481,64 @@ class ExtractAlembicModel(BaseSettingsModel): "Alembic export. Attributes should be separated by semi-colon `;`" ) ) + uvsOnly: bool = SettingsField( + title="UVs Only", + description=( + "If this flag is present, only uv data for PolyMesh and SubD " + "shapes will be written to the Alembic file." + ) + ) + uvWrite: bool = SettingsField( + title="UV Write", + description=( + "Uv data for PolyMesh and SubD shapes will be written to the " + "Alembic file." + ) + ) + verbose: bool = SettingsField( + title="Verbose", + description="Prints the current frame that is being evaluated." + ) visibleOnly: bool = SettingsField( title="Visible Only", description="Only export dag objects visible during frame range." ) + wholeFrameGeo: bool = SettingsField( + title="Whole Frame Geo", + description=( + "Data for geometry will only be written out on whole frames." + ) + ) + worldSpace: bool = SettingsField( + title="World Space", + description="Any root nodes will be stored in world space." + ) + writeColorSets: bool = SettingsField( + title="Write Color Sets", + description="Write vertex colors with the geometry." + ) + writeFaceSets: bool = SettingsField( + title="Write Face Sets", + description="Write face sets with the geometry." + ) + writeNormals: bool = SettingsField( + title="Write Normals", + description="Write normals with the deforming geometry." + ) + writeUVSets: bool = SettingsField( + title="Write UV Sets", + description=( + "Write all uv sets on MFnMeshes as vector 2 indexed geometry " + "parameters with face varying scope." + ) + ) + writeVisibility: bool = SettingsField( + title="Write Visibility", + description=( + "Visibility state will be stored in the Alembic file. Otherwise " + "everything written out is treated as visible." + ) + ) overrides: list[str] = SettingsField( enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides", @@ -1596,33 +1616,47 @@ DEFAULT_PUBLISH_SETTINGS = { "model", "vrayproxy.alembic" ], - "bake_attributes": [], - "bake_attribute_prefixes": [], - "flags": [ - "stripNamespaces", - "writeNormals", - "worldSpace" - ], "attr": "", "attrPrefix": "", + "autoSubd": False, + "bake_attributes": [], + "bake_attribute_prefixes": [], "dataFormat": "ogawa", + "dontSkipUnwrittenFrames": False, + "eulerFilter": False, "melPerFrameCallback": "", "melPostFrameCallback": "", - "preRollStartFrame": 0, - "pythonPerFrameCallback": "", - "pythonPostJobCallback": "", - "userAttr": "", - "userAttrPrefix": "", - "visibleOnly": False, + "noNormals": False, "overrides": [ "attr", "attrPrefix", + "renderableOnly", + "userAttr", + "userAttrPrefix", + "visibleOnly", "worldSpace", "writeColorSets", - "writeNormals", "writeFaceSets", - "renderableOnly", - "visibleOnly" - ] + "writeNormals" + ], + "preRoll": False, + "preRollStartFrame": 0, + "pythonPerFrameCallback": "", + "pythonPostJobCallback": "", + "renderableOnly": False, + "stripNamespaces": True, + "uvsOnly": False, + "uvWrite": False, + "userAttr": "", + "userAttrPrefix": "", + "verbose": False, + "visibleOnly": False, + "wholeFrameGeo": False, + "worldSpace": True, + "writeColorSets": False, + "writeFaceSets": False, + "writeNormals": True, + "writeUVSets": False, + "writeVisibility": False } } diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 75b463f198..c1b7ff9d79 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.15" +__version__ = "0.1.16" From 953238490bf943c083dcbd975abb9bc55b617588 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:30:19 +0100 Subject: [PATCH 148/244] Update client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/create/create_animation_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index d89f5756b2..a3533beebe 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -58,7 +58,7 @@ def extract_alembic_attributes(node_data, class_name): ] plugin_attributes = {} for attr in attributes: - if attr not in node_data["creator_attributes"].keys(): + if attr not in node_data["creator_attributes"]: continue value = node_data["creator_attributes"].pop(attr) From f7d52df193af987bc6f11ea61aca7abc1b83ddfb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:32:01 +0100 Subject: [PATCH 149/244] Update client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/create/create_animation_pointcache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index a3533beebe..34ab46228e 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -62,8 +62,7 @@ def extract_alembic_attributes(node_data, class_name): continue value = node_data["creator_attributes"].pop(attr) - if attr in attributes: - plugin_attributes[attr] = value + plugin_attributes[attr] = value publish_attributes[class_name] = plugin_attributes From 0d23dbad75df7b2145de6eca2ca1fea81253f884 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:33:11 +0100 Subject: [PATCH 150/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Roy Nieterau --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 5e354b5a65..4b2d780633 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -22,7 +22,7 @@ class ValidateAlembicOptionsDefaults( @classmethod def _get_plugin_name(cls, publish_attributes): for key in ["ExtractAnimation", "ExtractAlembic"]: - if key in publish_attributes.keys(): + if key in publish_attributes: return key @classmethod From f14c7bb64c2bba44f05a487451d3480e9cb83d24 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:34:16 +0100 Subject: [PATCH 151/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Roy Nieterau --- .../plugins/publish/validate_alembic_options_defaults.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 4b2d780633..393ad238cb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -75,10 +75,10 @@ class ValidateAlembicOptionsDefaults( # Find create instance twin. create_context = instance.context.data["create_context"] create_instance = None - for Instance in create_context.instances: - if Instance.data["instance_id"] == instance.data["instance_id"]: - create_instance = Instance - break + create_instance = next( + inst for inst in create_context.instances + if inst.data["instance_id"] == instance.data["instance_id"] + ) assert create_instance is not None From ec39dd23deb0e93b600ccfc79120658a3ca2e53a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:37:23 +0100 Subject: [PATCH 152/244] Update server_addon/maya/server/settings/publishers.py --- server_addon/maya/server/settings/publishers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index fdf1815f4b..203a8cddc8 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -427,7 +427,10 @@ class ExtractAlembicModel(BaseSettingsModel): title="Pre Roll", description=( "When enabled, the pre roll start frame is used to pre roll the " - "export for." + "When enabled, the pre roll start frame is used to being the " + "evaluation of the mesh. From the pre roll start frame to the " + "alembic start frame, will not be written to disk. This can be " + "used for simulation run up." ) ) preRollStartFrame: int = SettingsField( From 46a0594339f31f0b651c8dd10e22688a4a6d0a34 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:39:08 +0100 Subject: [PATCH 153/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index e4dfbac414..514dc4ed38 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -136,7 +136,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "startFrame": start, "step": instance.data.get( "creator_attributes", {} - ).get("step", 1.0), #missing + ).get("step", 1.0), "stripNamespaces": attribute_values.get( "stripNamespaces", self.stripNamespaces ), From 41ef2034c2f8126344989f903c0673cd3ce294f3 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:39:29 +0100 Subject: [PATCH 154/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Roy Nieterau --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 393ad238cb..351bb7387c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -88,7 +88,7 @@ class ValidateAlembicOptionsDefaults( attributes = cls._get_publish_attributes(instance) settings = cls._get_settings(instance.context) create_publish_attributes = create_instance.data["publish_attributes"] - for key in attributes.keys(): + for key in attributes: create_publish_attributes[plugin_name][key] = settings[key] create_context.save_changes() From f63dda7971c31515993602d1124060b5324bba46 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Apr 2024 12:34:04 +0100 Subject: [PATCH 155/244] Reduce code duplication --- .../create/create_animation_pointcache.py | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 34ab46228e..6142b63fb9 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -88,23 +88,10 @@ class CreateAnimation(plugin.MayaHiddenCreator): include_parent_hierarchy = False include_user_defined_attributes = False - def collect_instances(self): - key = "maya_cached_instance_data" - try: - cached_subsets = self.collection_shared_data[key] - except KeyError: - self.cache_instance_data(self.collection_shared_data) - cached_subsets = self.collection_shared_data[key] - - for node in cached_subsets.get(self.identifier, []): - node_data = self.read_instance_node(node) - - node_data = extract_alembic_attributes( - node_data, "ExtractAnimation" - ) - - created_instance = CreatedInstance.from_existing(node_data, self) - self._add_instance_to_context(created_instance) + def read_instance_node(self, node): + node_data = super(CreateAnimation, self).read_instance_node(node) + node_data = extract_alembic_attributes(node_data, "ExtractAnimation") + return node_data def get_instance_attr_defs(self): super(CreateAnimation, self).get_instance_attr_defs() @@ -123,21 +110,10 @@ class CreatePointCache(plugin.MayaCreator): write_face_sets = False include_user_defined_attributes = False - def collect_instances(self): - key = "maya_cached_instance_data" - try: - cached_subsets = self.collection_shared_data[key] - except KeyError: - self.cache_instance_data(self.collection_shared_data) - cached_subsets = self.collection_shared_data[key] - - for node in cached_subsets.get(self.identifier, []): - node_data = self.read_instance_node(node) - - node_data = extract_alembic_attributes(node_data, "ExtractAlembic") - - created_instance = CreatedInstance.from_existing(node_data, self) - self._add_instance_to_context(created_instance) + def read_instance_node(self, node): + node_data = super(CreatePointCache, self).read_instance_node(node) + node_data = extract_alembic_attributes(node_data, "ExtractAlembic") + return node_data def get_instance_attr_defs(self): super(CreatePointCache, self).get_instance_attr_defs() From 6736c971109b7ab319daf12aabb13232130331cb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Apr 2024 12:37:59 +0100 Subject: [PATCH 156/244] Use collect_user_defined_attributes --- .../hosts/maya/plugins/publish/collect_animation.py | 5 ----- .../maya/plugins/publish/collect_user_defined_attributes.py | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py index e8618284dd..4604554aa0 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py @@ -58,8 +58,3 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") - - # User defined attributes. - instance.data["includeUserDefinedAttributes"] = ( - instance.data["creator_attributes"]["includeUserDefinedAttributes"] - ) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py index 16fef2e168..7cce68f149 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py @@ -14,7 +14,10 @@ class CollectUserDefinedAttributes(pyblish.api.InstancePlugin): def process(self, instance): # Collect user defined attributes. - if not instance.data.get("includeUserDefinedAttributes", False): + if not instance.data.get( + "includeUserDefinedAttributes", + instance.data["creator_attributes"]["includeUserDefinedAttributes"] + ): return if "out_hierarchy" in instance.data: From 9a57800d12adf65f627817c5e56c29bb14dc82d2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Apr 2024 12:39:14 +0100 Subject: [PATCH 157/244] Rename extract_alembic --- .../maya/plugins/create/create_animation_pointcache.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 6142b63fb9..597ed5bb64 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -35,7 +35,7 @@ def _get_animation_attr_defs(cls): return defs -def extract_alembic_attributes(node_data, class_name): +def convert_legacy_alembic_creator_attributes(node_data, class_name): """This is a legacy transfer of creator attributes to publish attributes for ExtractAlembic/ExtractAnimation plugin. """ @@ -90,7 +90,9 @@ class CreateAnimation(plugin.MayaHiddenCreator): def read_instance_node(self, node): node_data = super(CreateAnimation, self).read_instance_node(node) - node_data = extract_alembic_attributes(node_data, "ExtractAnimation") + node_data = convert_legacy_alembic_creator_attributes( + node_data, "ExtractAnimation" + ) return node_data def get_instance_attr_defs(self): @@ -112,7 +114,9 @@ class CreatePointCache(plugin.MayaCreator): def read_instance_node(self, node): node_data = super(CreatePointCache, self).read_instance_node(node) - node_data = extract_alembic_attributes(node_data, "ExtractAlembic") + node_data = convert_legacy_alembic_creator_attributes( + node_data, "ExtractAlembic" + ) return node_data def get_instance_attr_defs(self): From 97c7c4edb840544ccc7e2eb8d598e1229790edb8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Apr 2024 14:32:55 +0100 Subject: [PATCH 158/244] Account for no overrides. --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 514dc4ed38..46f9f895d6 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -231,6 +231,9 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): @classmethod def get_attribute_defs(cls): + if not cls.overrides: + return [] + override_defs = { "attr": TextDef( "attr", From 85703e181261c2910e126667354117c45e274c56 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 14:34:56 +0100 Subject: [PATCH 159/244] Update client/ayon_core/hosts/maya/plugins/publish/collect_pointcache.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/publish/collect_pointcache.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/collect_pointcache.py index 8b4289ed80..5578a57f31 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_pointcache.py @@ -45,8 +45,3 @@ class CollectPointcache(pyblish.api.InstancePlugin): if proxy_set: instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) - - # User defined attributes. - instance.data["includeUserDefinedAttributes"] = ( - instance.data["creator_attributes"]["includeUserDefinedAttributes"] - ) From 001398214458c9737a53d71e62859c6bbeb46884 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 14:35:31 +0100 Subject: [PATCH 160/244] Update client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py Co-authored-by: Roy Nieterau --- .../maya/plugins/publish/collect_user_defined_attributes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py index 7cce68f149..3d586d48fb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_user_defined_attributes.py @@ -14,9 +14,8 @@ class CollectUserDefinedAttributes(pyblish.api.InstancePlugin): def process(self, instance): # Collect user defined attributes. - if not instance.data.get( - "includeUserDefinedAttributes", - instance.data["creator_attributes"]["includeUserDefinedAttributes"] + if not instance.data["creator_attributes"].get( + "includeUserDefinedAttributes" ): return From 0ac752830b2a8d2da8c99532b07ef2744665c5f4 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 14:36:02 +0100 Subject: [PATCH 161/244] Update client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py Co-authored-by: Roy Nieterau --- .../maya/plugins/create/create_animation_pointcache.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 597ed5bb64..9c12e10c9d 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -27,7 +27,11 @@ def _get_animation_attr_defs(cls): ), BoolDef( "includeUserDefinedAttributes", - label="Include User Defined Attributes" + label="Include User Defined Attributes", + tooltip=( + "Whether to include all custom maya attributes found " + "on nodes as attributes in the Alembic data." + ) ), ] ) From f27b31def7e795a81c46e7984f1216598c55c773 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 15:07:38 +0100 Subject: [PATCH 162/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Roy Nieterau --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 351bb7387c..3ebdcf4621 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -74,14 +74,11 @@ class ValidateAlembicOptionsDefaults( def repair(cls, instance): # Find create instance twin. create_context = instance.context.data["create_context"] - create_instance = None create_instance = next( inst for inst in create_context.instances if inst.data["instance_id"] == instance.data["instance_id"] ) - assert create_instance is not None - # Set the settings values on the create context then save to workfile. publish_attributes = instance.data["publish_attributes"] plugin_name = cls._get_plugin_name(publish_attributes) From 4b6081a84ae3e8c2701313d99c302eb4247dab55 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 15:08:02 +0100 Subject: [PATCH 163/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 203a8cddc8..ef576772fe 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -439,8 +439,8 @@ class ExtractAlembicModel(BaseSettingsModel): "The frame to start scene evaluation at. This is used to set the " "starting frame for time dependent translations and can be used to" " evaluate run-up that isn't actually translated.\n" - "NOTE: preRoll needs to be enabled in the export flags for this " - "start frame to be considered." + "NOTE: Pre Roll needs to be enabled for this start frame " + "to be considered." ) ) pythonPerFrameCallback: str = SettingsField( From 08a4d48aa19749d342ba56e603860872129be5f0 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 16:49:32 +0100 Subject: [PATCH 164/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 46f9f895d6..204ca6e3d2 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -392,6 +392,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "userAttr": TextDef( "userAttr", label="User Attr", + placeholder="attr1; attr2; ...", default=cls.userAttr, tooltip=( "Attributes matching by name will be included in the " @@ -402,6 +403,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "userAttrPrefix": TextDef( "userAttrPrefix", label="User Attr Prefix", + placeholder="prefix1; prefix2; ...", default=cls.userAttrPrefix, tooltip=( "Attributes starting with these prefixes will be included " From 5cb862cf22efbdb3f127b2a0fdfef5e2d0ed241d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 16:49:54 +0100 Subject: [PATCH 165/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 204ca6e3d2..29a857fed3 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -238,7 +238,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "attr": TextDef( "attr", label="Custom Attributes", - placeholder="attr1;attr2", + placeholder="attr1; attr2; ...", default=cls.attr, tooltip=( "Attributes matching by name will be included in the " From cd8ac63e6ba1fce286274bb991b67337fdf9f195 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 09:52:44 +0100 Subject: [PATCH 166/244] Re-order attributes --- .../plugins/publish/extract_pointcache.py | 201 +++++++++--------- .../maya/server/settings/publishers.py | 170 +++++++-------- 2 files changed, 186 insertions(+), 185 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 29a857fed3..a626e32b10 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -1,4 +1,5 @@ import os +from collections import OrderedDict from maya import cmds @@ -234,29 +235,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): if not cls.overrides: return [] - override_defs = { - "attr": TextDef( - "attr", - label="Custom Attributes", - placeholder="attr1; attr2; ...", - default=cls.attr, - tooltip=( - "Attributes matching by name will be included in the " - "Alembic export. Attributes should be separated by " - "semi-colon `;`" - ) - ), - "attrPrefix": TextDef( - "attrPrefix", - label="Custom Attributes Prefix", - placeholder="prefix1; prefix2; ...", - default=cls.attrPrefix, - tooltip=( - "Attributes starting with these prefixes will be included " - "in the Alembic export. Attributes should be separated by " - "semi-colon `;`" - ) - ), + override_defs = OrderedDict({ "autoSubd": BoolDef( "autoSubd", label="Auto Subd", @@ -271,13 +250,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): " its value is true." ) ), - "dataFormat": EnumDef( - "dataFormat", - label="Data Format", - items=["ogawa", "HDF"], - default=cls.dataFormat, - tooltip="The data format to use to write the file." - ), "dontSkipUnwrittenFrames": BoolDef( "dontSkipUnwrittenFrames", label="Dont Skip Unwritten Frames", @@ -294,24 +266,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): default=cls.eulerFilter, tooltip="Apply Euler filter while sampling rotations." ), - "melPerFrameCallback": TextDef( - "melPerFrameCallback", - label="Mel Per Frame Callback", - default=cls.melPerFrameCallback, - tooltip=( - "When each frame (and the static frame) is evaluated the " - "string specified is evaluated as a Mel command." - ) - ), - "melPostJobCallback": TextDef( - "melPostJobCallback", - label="Mel Post Job Callback", - default=cls.melPostJobCallback, - tooltip=( - "When the translation has finished the string specified " - "is evaluated as a Mel command." - ) - ), "noNormals": BoolDef( "noNormals", label="No Normals", @@ -327,35 +281,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): default=cls.preRoll, tooltip="This frame range will not be sampled." ), - "preRollStartFrame": NumberDef( - "preRollStartFrame", - label="Pre Roll Start Frame", - tooltip=( - "The frame to start scene evaluation at. This is used" - " to set the starting frame for time dependent " - "translations and can be used to evaluate run-up that" - " isn't actually translated." - ), - default=cls.preRollStartFrame - ), - "pythonPerFrameCallback": TextDef( - "pythonPerFrameCallback", - label="Python Per Frame Callback", - default=cls.pythonPerFrameCallback, - tooltip=( - "When each frame (and the static frame) is evaluated the " - "string specified is evaluated as a python command." - ) - ), - "pythonPostJobCallback": TextDef( - "pythonPostJobCallback", - label="Python Post Frame Callback", - default=cls.pythonPostJobCallback, - tooltip=( - "When the translation has finished the string specified " - "is evaluated as a python command." - ) - ), "renderableOnly": BoolDef( "renderableOnly", label="Renderable Only", @@ -389,28 +314,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "the Alembic file." ) ), - "userAttr": TextDef( - "userAttr", - label="User Attr", - placeholder="attr1; attr2; ...", - default=cls.userAttr, - tooltip=( - "Attributes matching by name will be included in the " - "Alembic export. Attributes should be separated by " - "semi-colon `;`" - ) - ), - "userAttrPrefix": TextDef( - "userAttrPrefix", - label="User Attr Prefix", - placeholder="prefix1; prefix2; ...", - default=cls.userAttrPrefix, - tooltip=( - "Attributes starting with these prefixes will be included " - "in the Alembic export. Attributes should be separated by " - "semi-colon `;`" - ) - ), "verbose": BoolDef( "verbose", label="Verbose", @@ -473,8 +376,106 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "Visibility state will be stored in the Alembic file. " "Otherwise everything written out is treated as visible." ) + ), + "preRollStartFrame": NumberDef( + "preRollStartFrame", + label="Pre Roll Start Frame", + tooltip=( + "The frame to start scene evaluation at. This is used" + " to set the starting frame for time dependent " + "translations and can be used to evaluate run-up that" + " isn't actually translated." + ), + default=cls.preRollStartFrame + ), + "dataFormat": EnumDef( + "dataFormat", + label="Data Format", + items=["ogawa", "HDF"], + default=cls.dataFormat, + tooltip="The data format to use to write the file." + ), + "attr": TextDef( + "attr", + label="Custom Attributes", + placeholder="attr1; attr2; ...", + default=cls.attr, + tooltip=( + "Attributes matching by name will be included in the " + "Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "attrPrefix": TextDef( + "attrPrefix", + label="Custom Attributes Prefix", + placeholder="prefix1; prefix2; ...", + default=cls.attrPrefix, + tooltip=( + "Attributes starting with these prefixes will be included " + "in the Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "userAttr": TextDef( + "userAttr", + label="User Attr", + placeholder="attr1; attr2; ...", + default=cls.userAttr, + tooltip=( + "Attributes matching by name will be included in the " + "Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "userAttrPrefix": TextDef( + "userAttrPrefix", + label="User Attr Prefix", + placeholder="prefix1; prefix2; ...", + default=cls.userAttrPrefix, + tooltip=( + "Attributes starting with these prefixes will be included " + "in the Alembic export. Attributes should be separated by " + "semi-colon `;`" + ) + ), + "melPerFrameCallback": TextDef( + "melPerFrameCallback", + label="Mel Per Frame Callback", + default=cls.melPerFrameCallback, + tooltip=( + "When each frame (and the static frame) is evaluated the " + "string specified is evaluated as a Mel command." + ) + ), + "melPostJobCallback": TextDef( + "melPostJobCallback", + label="Mel Post Job Callback", + default=cls.melPostJobCallback, + tooltip=( + "When the translation has finished the string specified " + "is evaluated as a Mel command." + ) + ), + "pythonPerFrameCallback": TextDef( + "pythonPerFrameCallback", + label="Python Per Frame Callback", + default=cls.pythonPerFrameCallback, + tooltip=( + "When each frame (and the static frame) is evaluated the " + "string specified is evaluated as a python command." + ) + ), + "pythonPostJobCallback": TextDef( + "pythonPostJobCallback", + label="Python Post Frame Callback", + default=cls.pythonPostJobCallback, + tooltip=( + "When the translation has finished the string specified " + "is evaluated as a python command." + ) ) - } + }) defs = super(ExtractAlembic, cls).get_attribute_defs() diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index ef576772fe..b5382079e7 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -349,22 +349,6 @@ class ExtractAlembicModel(BaseSettingsModel): families: list[str] = SettingsField( default_factory=list, title="Families") - attr: str = SettingsField( - title="Custom Attributes", - placeholder="attr1;attr2", - description=( - "Attributes matching by name will be included in the Alembic " - "export. Attributes should be separated by semi-colon `;`" - ) - ) - attrPrefix: str = SettingsField( - title="Custom Attributes Prefix", - placeholder="prefix1;prefix2", - description=( - "Attributes starting with these prefixes will be included in the " - "Alembic export. Attributes should be separated by semi-colon `;`" - ) - ) autoSubd: bool = SettingsField( title="Auto Subd", description=( @@ -376,21 +360,6 @@ class ExtractAlembicModel(BaseSettingsModel): "mesh node and its value is true." ) ) - bake_attributes: list[str] = SettingsField( - default_factory=list, title="Bake Attributes", - description="List of attributes that will be included in the alembic " - "export.", - ) - bake_attribute_prefixes: list[str] = SettingsField( - default_factory=list, title="Bake Attribute Prefixes", - description="List of attribute prefixes for attributes that will be " - "included in the alembic export.", - ) - dataFormat: str = SettingsField( - enum_resolver=extract_alembic_data_format_enum, - title="Data Format", - description="The data format to use to write the file." - ) dontSkipUnwrittenFrames: bool = SettingsField( title="Dont Skip Unwritten Frames", description=( @@ -403,20 +372,6 @@ class ExtractAlembicModel(BaseSettingsModel): title="Euler Filter", description="Apply Euler filter while sampling rotations." ) - melPerFrameCallback: str = SettingsField( - title="Mel Per Frame Callback", - description=( - "When each frame (and the static frame) is evaluated the string " - "specified is evaluated as a Mel command." - ) - ) - melPostFrameCallback: str = SettingsField( - title="Mel Post Frame Callback", - description=( - "When the translation has finished the string specified is " - "evaluated as a Mel command." - ) - ) noNormals: bool = SettingsField( title="No Normals", description=( @@ -433,30 +388,6 @@ class ExtractAlembicModel(BaseSettingsModel): "used for simulation run up." ) ) - preRollStartFrame: int = SettingsField( - title="Pre Roll Start Frame", - description=( - "The frame to start scene evaluation at. This is used to set the " - "starting frame for time dependent translations and can be used to" - " evaluate run-up that isn't actually translated.\n" - "NOTE: Pre Roll needs to be enabled for this start frame " - "to be considered." - ) - ) - pythonPerFrameCallback: str = SettingsField( - title="Python Per Frame Callback", - description=( - "When each frame (and the static frame) is evaluated the string " - "specified is evaluated as a python command." - ) - ) - pythonPostJobCallback: str = SettingsField( - title="Python Post Job Callback", - description=( - "When the translation has finished the string specified is " - "evaluated as a python command." - ) - ) renderableOnly: bool = SettingsField( title="Renderable Only", description="Only export renderable visible shapes." @@ -468,22 +399,6 @@ class ExtractAlembicModel(BaseSettingsModel): "to Alembic." ) ) - userAttr: str = SettingsField( - title="User Attr", - placeholder="attr1;attr2", - description=( - "Attributes matching by name will be included in the Alembic " - "export. Attributes should be separated by semi-colon `;`" - ) - ) - userAttrPrefix: str = SettingsField( - title="User Attr Prefix", - placeholder="prefix1;prefix2", - description=( - "Attributes starting with these prefixes will be included in the " - "Alembic export. Attributes should be separated by semi-colon `;`" - ) - ) uvsOnly: bool = SettingsField( title="UVs Only", description=( @@ -542,6 +457,91 @@ class ExtractAlembicModel(BaseSettingsModel): "everything written out is treated as visible." ) ) + preRollStartFrame: int = SettingsField( + title="Pre Roll Start Frame", + description=( + "The frame to start scene evaluation at. This is used to set the " + "starting frame for time dependent translations and can be used to" + " evaluate run-up that isn't actually translated.\n" + "NOTE: Pre Roll needs to be enabled for this start frame " + "to be considered." + ) + ) + dataFormat: str = SettingsField( + enum_resolver=extract_alembic_data_format_enum, + title="Data Format", + description="The data format to use to write the file." + ) + bake_attributes: list[str] = SettingsField( + default_factory=list, title="Bake Attributes", + description="List of attributes that will be included in the alembic " + "export.", + ) + bake_attribute_prefixes: list[str] = SettingsField( + default_factory=list, title="Bake Attribute Prefixes", + description="List of attribute prefixes for attributes that will be " + "included in the alembic export.", + ) + attr: str = SettingsField( + title="Custom Attributes", + placeholder="attr1;attr2", + description=( + "Attributes matching by name will be included in the Alembic " + "export. Attributes should be separated by semi-colon `;`" + ) + ) + attrPrefix: str = SettingsField( + title="Custom Attributes Prefix", + placeholder="prefix1;prefix2", + description=( + "Attributes starting with these prefixes will be included in the " + "Alembic export. Attributes should be separated by semi-colon `;`" + ) + ) + userAttr: str = SettingsField( + title="User Attr", + placeholder="attr1;attr2", + description=( + "Attributes matching by name will be included in the Alembic " + "export. Attributes should be separated by semi-colon `;`" + ) + ) + userAttrPrefix: str = SettingsField( + title="User Attr Prefix", + placeholder="prefix1;prefix2", + description=( + "Attributes starting with these prefixes will be included in the " + "Alembic export. Attributes should be separated by semi-colon `;`" + ) + ) + melPerFrameCallback: str = SettingsField( + title="Mel Per Frame Callback", + description=( + "When each frame (and the static frame) is evaluated the string " + "specified is evaluated as a Mel command." + ) + ) + melPostFrameCallback: str = SettingsField( + title="Mel Post Frame Callback", + description=( + "When the translation has finished the string specified is " + "evaluated as a Mel command." + ) + ) + pythonPerFrameCallback: str = SettingsField( + title="Python Per Frame Callback", + description=( + "When each frame (and the static frame) is evaluated the string " + "specified is evaluated as a python command." + ) + ) + pythonPostJobCallback: str = SettingsField( + title="Python Post Job Callback", + description=( + "When the translation has finished the string specified is " + "evaluated as a python command." + ) + ) overrides: list[str] = SettingsField( enum_resolver=extract_alembic_overrides_enum, title="Exposed Overrides", From f1bf5a5573baa3ad5b4ec480ad8ace661691953a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 09:55:41 +0100 Subject: [PATCH 167/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index b5382079e7..3f39ab6b26 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1634,8 +1634,6 @@ DEFAULT_PUBLISH_SETTINGS = { "attr", "attrPrefix", "renderableOnly", - "userAttr", - "userAttrPrefix", "visibleOnly", "worldSpace", "writeColorSets", From a174f42d646960193d5eca7e83e225cd0f6a30c9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 09:57:39 +0100 Subject: [PATCH 168/244] Use user attr attributes --- .../maya/plugins/publish/extract_pointcache.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index a626e32b10..84ea956326 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -101,6 +101,18 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): ] attr_prefixes += self.bake_attribute_prefixes + user_attrs = [ + attr.strip() + for attr in attribute_values.get("userAttr", "").split(";") + if attr.strip() + ] + + user_attr_prefixes = [ + attr.strip() + for attr in attribute_values.get("userAttrPrefix", "").split(";") + if attr.strip() + ] + self.log.debug("Extracting pointcache..") dirname = self.staging_dir(instance) @@ -119,6 +131,8 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "file": path, "attr": attrs, "attrPrefix": attr_prefixes, + "userAttr": user_attrs, + "userAttrPrefix": user_attr_prefixes, "dataFormat": attribute_values.get("dataFormat", self.dataFormat), "endFrame": end, "eulerFilter": attribute_values.get( From 8dcda0a6b9e4f179e7a61d8f9075a6e2bb3035c4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 09:59:33 +0100 Subject: [PATCH 169/244] Move preRoll --- .../plugins/publish/extract_pointcache.py | 12 +++++------ .../maya/server/settings/publishers.py | 20 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 84ea956326..3a3d09f513 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -289,12 +289,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "written." ) ), - "preRoll": BoolDef( - "preRoll", - label="Pre Roll", - default=cls.preRoll, - tooltip="This frame range will not be sampled." - ), "renderableOnly": BoolDef( "renderableOnly", label="Renderable Only", @@ -391,6 +385,12 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "Otherwise everything written out is treated as visible." ) ), + "preRoll": BoolDef( + "preRoll", + label="Pre Roll", + default=cls.preRoll, + tooltip="This frame range will not be sampled." + ), "preRollStartFrame": NumberDef( "preRollStartFrame", label="Pre Roll Start Frame", diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3f39ab6b26..d2fdd58406 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -378,16 +378,6 @@ class ExtractAlembicModel(BaseSettingsModel): "Present normal data for Alembic poly meshes will not be written." ) ) - preRoll: bool = SettingsField( - title="Pre Roll", - description=( - "When enabled, the pre roll start frame is used to pre roll the " - "When enabled, the pre roll start frame is used to being the " - "evaluation of the mesh. From the pre roll start frame to the " - "alembic start frame, will not be written to disk. This can be " - "used for simulation run up." - ) - ) renderableOnly: bool = SettingsField( title="Renderable Only", description="Only export renderable visible shapes." @@ -457,6 +447,16 @@ class ExtractAlembicModel(BaseSettingsModel): "everything written out is treated as visible." ) ) + preRoll: bool = SettingsField( + title="Pre Roll", + description=( + "When enabled, the pre roll start frame is used to pre roll the " + "When enabled, the pre roll start frame is used to being the " + "evaluation of the mesh. From the pre roll start frame to the " + "alembic start frame, will not be written to disk. This can be " + "used for simulation run up." + ) + ) preRollStartFrame: int = SettingsField( title="Pre Roll Start Frame", description=( From 56faca7c3eaf2e72530c121b160c39df8392b1a3 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 10:31:31 +0100 Subject: [PATCH 170/244] Update server_addon/maya/server/settings/publishers.py Co-authored-by: Roy Nieterau --- server_addon/maya/server/settings/publishers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index d2fdd58406..6a63d88657 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -521,8 +521,8 @@ class ExtractAlembicModel(BaseSettingsModel): "specified is evaluated as a Mel command." ) ) - melPostFrameCallback: str = SettingsField( - title="Mel Post Frame Callback", + melPostJobCallback: str = SettingsField( + title="Mel Post Job Callback", description=( "When the translation has finished the string specified is " "evaluated as a Mel command." From f670d1ae6eabfd32bfab03bde4186885ab779697 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 10:32:01 +0100 Subject: [PATCH 171/244] fix melPostJobCallback --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 6a63d88657..e10df66403 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1628,7 +1628,7 @@ DEFAULT_PUBLISH_SETTINGS = { "dontSkipUnwrittenFrames": False, "eulerFilter": False, "melPerFrameCallback": "", - "melPostFrameCallback": "", + "melPostJobCallback": "", "noNormals": False, "overrides": [ "attr", From c9919ec8801a3112b8147ff31e5f7b611b36f942 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 10:38:45 +0100 Subject: [PATCH 172/244] Missing flags from attributes. --- .../plugins/publish/extract_pointcache.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 3a3d09f513..8ea94d617e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -175,6 +175,30 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): ), "writeVisibility": attribute_values.get( "writeVisibility", self.writeVisibility + ), + "autoSubd": attribute_values.get( + "autoSubd", self.autoSubd + ), + "dontSkipUnwrittenFrames": attribute_values.get( + "dontSkipUnwrittenFrames", self.dontSkipUnwrittenFrames + ), + "uvsOnly": attribute_values.get( + "uvsOnly", self.uvsOnly + ), + "writeNormals": attribute_values.get( + "writeNormals", self.writeNormals + ), + "melPerFrameCallback": attribute_values.get( + "melPerFrameCallback", self.melPerFrameCallback + ), + "melPostJobCallback": attribute_values.get( + "melPostJobCallback", self.melPostJobCallback + ), + "pythonPerFrameCallback": attribute_values.get( + "pythonPerFrameCallback", self.pythonPostJobCallback + ), + "pythonPostJobCallback": attribute_values.get( + "pythonPostJobCallback", self.pythonPostJobCallback ) } From 16d54949cba824db806e41e21edbfa4014ee5e63 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 10:42:17 +0100 Subject: [PATCH 173/244] Remove dontSkipUnwrittenFrames --- .../maya/plugins/publish/extract_pointcache.py | 14 -------------- server_addon/maya/server/settings/publishers.py | 9 --------- 2 files changed, 23 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 8ea94d617e..5318619c6c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -44,7 +44,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): bake_attributes = [] bake_attribute_prefixes = [] dataFormat = "ogawa" - dontSkipUnwrittenFrames = False eulerFilter = False melPerFrameCallback = "" melPostJobCallback = "" @@ -179,9 +178,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "autoSubd": attribute_values.get( "autoSubd", self.autoSubd ), - "dontSkipUnwrittenFrames": attribute_values.get( - "dontSkipUnwrittenFrames", self.dontSkipUnwrittenFrames - ), "uvsOnly": attribute_values.get( "uvsOnly", self.uvsOnly ), @@ -288,16 +284,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): " its value is true." ) ), - "dontSkipUnwrittenFrames": BoolDef( - "dontSkipUnwrittenFrames", - label="Dont Skip Unwritten Frames", - default=cls.dontSkipUnwrittenFrames, - tooltip=( - "When evaluating multiple translate jobs, this decides " - "whether to evaluate frames between jobs when there is a " - "gap in their frame ranges." - ) - ), "eulerFilter": BoolDef( "eulerFilter", label="Euler Filter", diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index e10df66403..869ad6a0a4 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -360,14 +360,6 @@ class ExtractAlembicModel(BaseSettingsModel): "mesh node and its value is true." ) ) - dontSkipUnwrittenFrames: bool = SettingsField( - title="Dont Skip Unwritten Frames", - description=( - "When evaluating multiple translate jobs, this decides whether to " - "evaluate frames between jobs when there is a gap in their frame " - "ranges." - ) - ) eulerFilter: bool = SettingsField( title="Euler Filter", description="Apply Euler filter while sampling rotations." @@ -1625,7 +1617,6 @@ DEFAULT_PUBLISH_SETTINGS = { "bake_attributes": [], "bake_attribute_prefixes": [], "dataFormat": "ogawa", - "dontSkipUnwrittenFrames": False, "eulerFilter": False, "melPerFrameCallback": "", "melPostJobCallback": "", From 8499660acbf9dc0317db2cd6010d65022942a47a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 12:14:24 +0100 Subject: [PATCH 174/244] Only close previous project if its different to current project. --- client/ayon_core/hosts/hiero/api/workio.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 4c2416ca38..9d8b1777e7 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -54,10 +54,9 @@ def open_file(filepath): # open project file hiero.core.openProject(filepath.replace(os.path.sep, "/")) - # close previous project - project.close() - - + # Close previous project if its different to the current project. + if project.path() != filepath: + project.close() return True From eb04a8b5507295a5ae034517e631a4a264800cb9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 12:22:22 +0100 Subject: [PATCH 175/244] Remove noNormals --- .../hosts/maya/plugins/publish/extract_pointcache.py | 11 ----------- server_addon/maya/server/settings/publishers.py | 12 ------------ 2 files changed, 23 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 5318619c6c..7a97030762 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -47,7 +47,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): eulerFilter = False melPerFrameCallback = "" melPostJobCallback = "" - noNormals = False overrides = [] preRoll = False preRollStartFrame = 0 @@ -137,7 +136,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "eulerFilter": attribute_values.get( "eulerFilter", self.eulerFilter ), - "noNormals": attribute_values.get("noNormals", self.noNormals), "preRoll": attribute_values.get("preRoll", self.preRoll), "preRollStartFrame": attribute_values.get( "preRollStartFrame", self.preRollStartFrame @@ -290,15 +288,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): default=cls.eulerFilter, tooltip="Apply Euler filter while sampling rotations." ), - "noNormals": BoolDef( - "noNormals", - label="No Normals", - default=cls.noNormals, - tooltip=( - "Present normal data for Alembic poly meshes will not be " - "written." - ) - ), "renderableOnly": BoolDef( "renderableOnly", label="Renderable Only", diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 869ad6a0a4..2061b97c24 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -48,14 +48,9 @@ def extract_alembic_overrides_enum(): {"label": "Custom Attributes Prefix", "value": "attrPrefix"}, {"label": "Auto Subd", "value": "autoSubd"}, {"label": "Data Format", "value": "dataFormat"}, - { - "label": "Dont Skip Unwritten Frames", - "value": "dontSkipUnwrittenFrames" - }, {"label": "Euler Filter", "value": "eulerFilter"}, {"label": "Mel Per Frame Callback", "value": "melPerFrameCallback"}, {"label": "Mel Post Job Callback", "value": "melPostJobCallback"}, - {"label": "No Normals", "value": "noNormals"}, {"label": "Pre Roll", "value": "preRoll"}, {"label": "Pre Roll Start Frame", "value": "preRollStartFrame"}, { @@ -364,12 +359,6 @@ class ExtractAlembicModel(BaseSettingsModel): title="Euler Filter", description="Apply Euler filter while sampling rotations." ) - noNormals: bool = SettingsField( - title="No Normals", - description=( - "Present normal data for Alembic poly meshes will not be written." - ) - ) renderableOnly: bool = SettingsField( title="Renderable Only", description="Only export renderable visible shapes." @@ -1620,7 +1609,6 @@ DEFAULT_PUBLISH_SETTINGS = { "eulerFilter": False, "melPerFrameCallback": "", "melPostJobCallback": "", - "noNormals": False, "overrides": [ "attr", "attrPrefix", From e75f44f6c1fe200720e6af12f31a8488b825df94 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Apr 2024 15:06:56 +0200 Subject: [PATCH 176/244] Bugfix: Parent to world only if not already at world - support root level placeholders --- client/ayon_core/hosts/maya/api/workfile_template_builder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index 75386d7e64..ddf19125e3 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -331,7 +331,8 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if scene_parent: cmds.parent(node, scene_parent) else: - cmds.parent(node, world=True) + if cmds.listRelatives(node, parent=True): + cmds.parent(node, world=True) holding_sets = cmds.listSets(object=placeholder.scene_identifier) if not holding_sets: From affb42e2eda5eb227f40f10e75db795fcd11ef84 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 14:58:31 +0100 Subject: [PATCH 177/244] Update client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py index 7a97030762..d7f9594374 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -506,7 +506,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): defs.append(value) defs.append( - UISeparatorDef("sep_alembic_options") + UISeparatorDef("sep_alembic_options_end") ) return defs From 9aff3121589d2c1eb594d261dd6c43a6c5f228c8 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 15:00:23 +0100 Subject: [PATCH 178/244] Update client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/maya/plugins/create/create_animation_pointcache.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 9c12e10c9d..5694936a57 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -124,9 +124,7 @@ class CreatePointCache(plugin.MayaCreator): return node_data def get_instance_attr_defs(self): - super(CreatePointCache, self).get_instance_attr_defs() - defs = _get_animation_attr_defs(self) - return defs + return _get_animation_attr_defs(self) def create(self, product_name, instance_data, pre_create_data): instance = super(CreatePointCache, self).create( From 40eea9652c3d6666e0744abfd5340bbd69a13a30 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 15:01:51 +0100 Subject: [PATCH 179/244] Update client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/validate_alembic_options_defaults.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 3ebdcf4621..50dfbb5202 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -74,9 +74,8 @@ class ValidateAlembicOptionsDefaults( def repair(cls, instance): # Find create instance twin. create_context = instance.context.data["create_context"] - create_instance = next( - inst for inst in create_context.instances - if inst.data["instance_id"] == instance.data["instance_id"] + create_instance = create_context.get_instance_by_id( + instance.data["instance_id"]) ) # Set the settings values on the create context then save to workfile. From 77e214d8e6c2ece97ef3243c8e374a4c854f12f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:31:41 +0200 Subject: [PATCH 180/244] add launch command to applications addon --- .../client/ayon_applications/addon.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 0f1b68af0e..624f158baa 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -110,6 +110,26 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): ] } + def launch_application( + self, app_name, project_name, folder_path, task_name + ): + """Launch application. + + Args: + app_name (str): Full application name e.g. 'maya/2024'. + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + + """ + app_manager = self.get_applications_manager() + return app_manager.launch( + app_name, + project_name=project_name, + folder_path=folder_path, + task_name=task_name, + ) + # --- CLI --- def cli(self, addon_click_group): main_group = click_wrap.group( @@ -134,6 +154,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): default=None ) ) + ( + main_group.command( + self._cli_launch_applications, + name="launch", + help="Launch application" + ) + .option("--app", help="Application name") + .option("--project", help="Project name") + .option("--folder", help="Folder path") + .option("--task", help="Task name") + ) # Convert main command to click object and add it to parent group addon_click_group.add_command( main_group.to_click_obj() @@ -171,3 +202,17 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): with open(output_json_path, "w") as file_stream: json.dump(env, file_stream, indent=4) + + def _cli_launch_applications(self, project, folder, task, app): + """Produces json file with environment based on project and app. + + Called by farm integration to propagate environment into farm jobs. + + Args: + project (str): Project name. + folder (str): Folder path. + task (str): Task name. + app (str): Full application name e.g. 'maya/2024'. + + """ + self.launch_application(app, project, folder, task,) From 7f4292c797a66204cad817ec765ef232a755f035 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:32:14 +0200 Subject: [PATCH 181/244] bump version '0.2.1' --- server_addon/applications/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index ce312ed662..bcc91f1d84 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,3 @@ name = "applications" title = "Applications" -version = "0.2.0" +version = "0.2.1" From 787e3ad90d557dd8853151d7093c7ce4a72d5964 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:13 +0200 Subject: [PATCH 182/244] remove trailing comma Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index 624f158baa..a573ee666a 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -215,4 +215,4 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): app (str): Full application name e.g. 'maya/2024'. """ - self.launch_application(app, project, folder, task,) + self.launch_application(app, project, folder, task) From 24c3b2c5c8d9a2f725049b7c7bdb699dadd2d37a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:27:35 +0200 Subject: [PATCH 183/244] fix docstring Co-authored-by: Roy Nieterau --- server_addon/applications/client/ayon_applications/addon.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index a573ee666a..f3ebedd364 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -204,9 +204,7 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): json.dump(env, file_stream, indent=4) def _cli_launch_applications(self, project, folder, task, app): - """Produces json file with environment based on project and app. - - Called by farm integration to propagate environment into farm jobs. + """Launch application. Args: project (str): Project name. From d8081868d740dae87829de5e61988f41884186db Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:31:05 +0200 Subject: [PATCH 184/244] require arguments for application launch --- .../applications/client/ayon_applications/addon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py index f3ebedd364..a8eaa46cad 100644 --- a/server_addon/applications/client/ayon_applications/addon.py +++ b/server_addon/applications/client/ayon_applications/addon.py @@ -160,10 +160,10 @@ class ApplicationsAddon(AYONAddon, IPluginPaths): name="launch", help="Launch application" ) - .option("--app", help="Application name") - .option("--project", help="Project name") - .option("--folder", help="Folder path") - .option("--task", help="Task name") + .option("--app", required=True, help="Application name") + .option("--project", required=True, help="Project name") + .option("--folder", required=True, help="Folder path") + .option("--task", required=True, help="Task name") ) # Convert main command to click object and add it to parent group addon_click_group.add_command( From 5b7511ed84ac348f3a0065b8e77fb2be9e44cdc9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 23:28:54 +0200 Subject: [PATCH 185/244] Workfile Templates: Implement registering and discovering of `PlaceholderPlugin` --- .../hosts/aftereffects/api/pipeline.py | 13 +- .../api/workfile_template_builder.py | 104 +-- .../plugins/template/create_placeholder.py | 49 + .../plugins/template/load_placeholder.py | 60 ++ client/ayon_core/hosts/maya/api/pipeline.py | 12 +- .../maya/api/workfile_template_builder.py | 258 +----- .../maya/plugins/template/load_placeholder.py | 264 ++++++ client/ayon_core/hosts/nuke/api/pipeline.py | 15 +- .../nuke/api/workfile_template_builder.py | 853 +----------------- .../plugins/template/create_placeholder.py | 428 +++++++++ .../nuke/plugins/template/load_placeholder.py | 455 ++++++++++ client/ayon_core/pipeline/__init__.py | 16 + .../ayon_core/pipeline/workfile/__init__.py | 15 + .../workfile/workfile_template_builder.py | 33 +- 14 files changed, 1342 insertions(+), 1233 deletions(-) create mode 100644 client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py create mode 100644 client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py create mode 100644 client/ayon_core/hosts/maya/plugins/template/load_placeholder.py create mode 100644 client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py create mode 100644 client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py diff --git a/client/ayon_core/hosts/aftereffects/api/pipeline.py b/client/ayon_core/hosts/aftereffects/api/pipeline.py index 105fee64b9..214986a2fc 100644 --- a/client/ayon_core/hosts/aftereffects/api/pipeline.py +++ b/client/ayon_core/hosts/aftereffects/api/pipeline.py @@ -8,14 +8,11 @@ from ayon_core.lib import Logger, register_event_callback from ayon_core.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, + register_template_placeholder_plugin_path, AVALON_CONTAINER_ID, AVALON_INSTANCE_ID, AYON_INSTANCE_ID, ) -from ayon_core.hosts.aftereffects.api.workfile_template_builder import ( - AEPlaceholderLoadPlugin, - AEPlaceholderCreatePlugin -) from ayon_core.pipeline.load import any_outdated_containers import ayon_core.hosts.aftereffects @@ -40,6 +37,7 @@ PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): @@ -76,6 +74,7 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) + register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) register_event_callback("application.launched", application_launch) @@ -118,12 +117,6 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): item["id"] = "publish_context" self.stub.imprint(item["id"], item) - def get_workfile_build_placeholder_plugins(self): - return [ - AEPlaceholderLoadPlugin, - AEPlaceholderCreatePlugin - ] - # created instances section def list_instances(self): """List all created instances from current workfile which diff --git a/client/ayon_core/hosts/aftereffects/api/workfile_template_builder.py b/client/ayon_core/hosts/aftereffects/api/workfile_template_builder.py index aa2f36e8aa..99d5bbb938 100644 --- a/client/ayon_core/hosts/aftereffects/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/aftereffects/api/workfile_template_builder.py @@ -1,6 +1,7 @@ import os.path import uuid import shutil +from abc import abstractmethod from ayon_core.pipeline import registered_host from ayon_core.tools.workfile_template_build import ( @@ -9,13 +10,9 @@ from ayon_core.tools.workfile_template_build import ( from ayon_core.pipeline.workfile.workfile_template_builder import ( AbstractTemplateBuilder, PlaceholderPlugin, - LoadPlaceholderItem, - CreatePlaceholderItem, - PlaceholderLoadMixin, - PlaceholderCreateMixin + PlaceholderItem ) from ayon_core.hosts.aftereffects.api import get_stub -from ayon_core.hosts.aftereffects.api.lib import set_settings PLACEHOLDER_SET = "PLACEHOLDERS_SET" PLACEHOLDER_ID = "openpype.placeholder" @@ -51,6 +48,10 @@ class AETemplateBuilder(AbstractTemplateBuilder): class AEPlaceholderPlugin(PlaceholderPlugin): """Contains generic methods for all PlaceholderPlugins.""" + @abstractmethod + def _create_placeholder_item(self, item_data: dict) -> PlaceholderItem: + pass + def collect_placeholders(self): """Collect info from file metadata about created placeholders. @@ -63,17 +64,7 @@ class AEPlaceholderPlugin(PlaceholderPlugin): if item.get("plugin_identifier") != self.identifier: continue - if isinstance(self, AEPlaceholderLoadPlugin): - item = LoadPlaceholderItem(item["uuid"], - item["data"], - self) - elif isinstance(self, AEPlaceholderCreatePlugin): - item = CreatePlaceholderItem(item["uuid"], - item["data"], - self) - else: - raise NotImplementedError(f"Not implemented for {type(self)}") - + item = self._create_placeholder_item(item) output.append(item) return output @@ -135,87 +126,6 @@ class AEPlaceholderPlugin(PlaceholderPlugin): stub.imprint(item_id, container_data) -class AEPlaceholderCreatePlugin(AEPlaceholderPlugin, PlaceholderCreateMixin): - """Adds Create placeholder. - - This adds composition and runs Create - """ - identifier = "aftereffects.create" - label = "AfterEffects create" - - def create_placeholder(self, placeholder_data): - stub = get_stub() - name = "CREATEPLACEHOLDER" - item_id = stub.add_item(name, "COMP") - - self._imprint_item(item_id, name, placeholder_data, stub) - - def populate_placeholder(self, placeholder): - """Replace 'placeholder' with publishable instance. - - Renames prepared composition name, creates publishable instance, sets - frame/duration settings according to DB. - """ - pre_create_data = {"use_selection": True} - item_id, item = self._get_item(placeholder) - get_stub().select_items([item_id]) - self.populate_create_placeholder(placeholder, pre_create_data) - - # apply settings for populated composition - item_id, metadata_item = self._get_item(placeholder) - set_settings(True, True, [item_id]) - - def get_placeholder_options(self, options=None): - return self.get_create_plugin_options(options) - - -class AEPlaceholderLoadPlugin(AEPlaceholderPlugin, PlaceholderLoadMixin): - identifier = "aftereffects.load" - label = "AfterEffects load" - - def create_placeholder(self, placeholder_data): - """Creates AE's Placeholder item in Project items list. - - Sets dummy resolution/duration/fps settings, will be replaced when - populated. - """ - stub = get_stub() - name = "LOADERPLACEHOLDER" - item_id = stub.add_placeholder(name, 1920, 1060, 25, 10) - - self._imprint_item(item_id, name, placeholder_data, stub) - - def populate_placeholder(self, placeholder): - """Use Openpype Loader from `placeholder` to create new FootageItems - - New FootageItems are created, files are imported. - """ - self.populate_load_placeholder(placeholder) - errors = placeholder.get_errors() - stub = get_stub() - if errors: - stub.print_msg("\n".join(errors)) - else: - if not placeholder.data["keep_placeholder"]: - metadata = stub.get_metadata() - for item in metadata: - if not item.get("is_placeholder"): - continue - scene_identifier = item.get("uuid") - if (scene_identifier and - scene_identifier == placeholder.scene_identifier): - stub.delete_item(item["members"][0]) - stub.remove_instance(placeholder.scene_identifier, metadata) - - def get_placeholder_options(self, options=None): - return self.get_load_plugin_options(options) - - def load_succeed(self, placeholder, container): - placeholder_item_id, _ = self._get_item(placeholder) - item_id = container.id - get_stub().add_item_instead_placeholder(placeholder_item_id, item_id) - - def build_workfile_template(*args, **kwargs): builder = AETemplateBuilder(registered_host()) builder.build_template(*args, **kwargs) diff --git a/client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py b/client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py new file mode 100644 index 0000000000..c7927f176f --- /dev/null +++ b/client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py @@ -0,0 +1,49 @@ +from ayon_core.pipeline.workfile.workfile_template_builder import ( + CreatePlaceholderItem, + PlaceholderCreateMixin +) +from ayon_core.hosts.aftereffects.api import get_stub +from ayon_core.hosts.aftereffects.api.lib import set_settings +import ayon_core.hosts.aftereffects.api.workfile_template_builder as wtb + + +class AEPlaceholderCreatePlugin(wtb.AEPlaceholderPlugin, + PlaceholderCreateMixin): + """Adds Create placeholder. + + This adds composition and runs Create + """ + identifier = "aftereffects.create" + label = "AfterEffects create" + + def _create_placeholder_item(self, item_data) -> CreatePlaceholderItem: + return CreatePlaceholderItem( + scene_identifier=item_data["uuid"], + data=item_data["data"], + plugin=self + ) + + def create_placeholder(self, placeholder_data): + stub = get_stub() + name = "CREATEPLACEHOLDER" + item_id = stub.add_item(name, "COMP") + + self._imprint_item(item_id, name, placeholder_data, stub) + + def populate_placeholder(self, placeholder): + """Replace 'placeholder' with publishable instance. + + Renames prepared composition name, creates publishable instance, sets + frame/duration settings according to DB. + """ + pre_create_data = {"use_selection": True} + item_id, item = self._get_item(placeholder) + get_stub().select_items([item_id]) + self.populate_create_placeholder(placeholder, pre_create_data) + + # apply settings for populated composition + item_id, metadata_item = self._get_item(placeholder) + set_settings(True, True, [item_id]) + + def get_placeholder_options(self, options=None): + return self.get_create_plugin_options(options) diff --git a/client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py b/client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py new file mode 100644 index 0000000000..7f7e4f49ce --- /dev/null +++ b/client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py @@ -0,0 +1,60 @@ +from ayon_core.pipeline.workfile.workfile_template_builder import ( + LoadPlaceholderItem, + PlaceholderLoadMixin +) +from ayon_core.hosts.aftereffects.api import get_stub +import ayon_core.hosts.aftereffects.api.workfile_template_builder as wtb + + +class AEPlaceholderLoadPlugin(wtb.AEPlaceholderPlugin, PlaceholderLoadMixin): + identifier = "aftereffects.load" + label = "AfterEffects load" + + def _create_placeholder_item(self, item_data) -> LoadPlaceholderItem: + return LoadPlaceholderItem( + scene_identifier=item_data["uuid"], + data=item_data["data"], + plugin=self + ) + + def create_placeholder(self, placeholder_data): + """Creates AE's Placeholder item in Project items list. + + Sets dummy resolution/duration/fps settings, will be replaced when + populated. + """ + stub = get_stub() + name = "LOADERPLACEHOLDER" + item_id = stub.add_placeholder(name, 1920, 1060, 25, 10) + + self._imprint_item(item_id, name, placeholder_data, stub) + + def populate_placeholder(self, placeholder): + """Use Openpype Loader from `placeholder` to create new FootageItems + + New FootageItems are created, files are imported. + """ + self.populate_load_placeholder(placeholder) + errors = placeholder.get_errors() + stub = get_stub() + if errors: + stub.print_msg("\n".join(errors)) + else: + if not placeholder.data["keep_placeholder"]: + metadata = stub.get_metadata() + for item in metadata: + if not item.get("is_placeholder"): + continue + scene_identifier = item.get("uuid") + if (scene_identifier and + scene_identifier == placeholder.scene_identifier): + stub.delete_item(item["members"][0]) + stub.remove_instance(placeholder.scene_identifier, metadata) + + def get_placeholder_options(self, options=None): + return self.get_load_plugin_options(options) + + def load_succeed(self, placeholder, container): + placeholder_item_id, _ = self._get_item(placeholder) + item_id = container.id + get_stub().add_item_instead_placeholder(placeholder_item_id, item_id) diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 864a0c1599..eca98fa306 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -30,9 +30,11 @@ from ayon_core.pipeline import ( register_loader_plugin_path, register_inventory_action_path, register_creator_plugin_path, + register_template_placeholder_plugin_path, deregister_loader_plugin_path, deregister_inventory_action_path, deregister_creator_plugin_path, + deregister_template_placeholder_plugin_path, AYON_CONTAINER_ID, AVALON_CONTAINER_ID, ) @@ -47,7 +49,6 @@ from ayon_core.hosts.maya import MAYA_ROOT_DIR from ayon_core.hosts.maya.lib import create_workspace_mel from . import menu, lib -from .workfile_template_builder import MayaPlaceholderLoadPlugin from .workio import ( open_file, save_file, @@ -64,6 +65,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") +TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") AVALON_CONTAINERS = ":AVALON_CONTAINERS" @@ -93,7 +95,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - self.log.info(PUBLISH_PATH) + register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) self.log.info("Installing callbacks ... ") register_event_callback("init", on_init) @@ -148,11 +150,6 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def get_containers(self): return ls() - def get_workfile_build_placeholder_plugins(self): - return [ - MayaPlaceholderLoadPlugin - ] - @contextlib.contextmanager def maintained_selection(self): with lib.maintained_selection(): @@ -338,6 +335,7 @@ def uninstall(): deregister_loader_plugin_path(LOAD_PATH) deregister_creator_plugin_path(CREATE_PATH) deregister_inventory_action_path(INVENTORY_PATH) + deregister_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) menu.uninstall() diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index 75386d7e64..cfd416b708 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -1,5 +1,3 @@ -import json - from maya import cmds from ayon_core.pipeline import ( @@ -10,16 +8,13 @@ from ayon_core.pipeline import ( ) from ayon_core.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, - AbstractTemplateBuilder, - PlaceholderPlugin, - LoadPlaceholderItem, - PlaceholderLoadMixin, + AbstractTemplateBuilder ) from ayon_core.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, ) -from .lib import read, imprint, get_reference_node, get_main_window +from .lib import get_main_window PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -91,255 +86,6 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): return True -class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): - identifier = "maya.load" - label = "Maya load" - - def _collect_scene_placeholders(self): - # Cache placeholder data to shared data - placeholder_nodes = self.builder.get_shared_populate_data( - "placeholder_nodes" - ) - if placeholder_nodes is None: - attributes = cmds.ls("*.plugin_identifier", long=True) - placeholder_nodes = {} - for attribute in attributes: - node_name = attribute.rpartition(".")[0] - placeholder_nodes[node_name] = ( - self._parse_placeholder_node_data(node_name) - ) - - self.builder.set_shared_populate_data( - "placeholder_nodes", placeholder_nodes - ) - return placeholder_nodes - - def _parse_placeholder_node_data(self, node_name): - placeholder_data = read(node_name) - parent_name = ( - cmds.getAttr(node_name + ".parent", asString=True) - or node_name.rpartition("|")[0] - or "" - ) - if parent_name: - siblings = cmds.listRelatives(parent_name, children=True) - else: - siblings = cmds.ls(assemblies=True) - node_shortname = node_name.rpartition("|")[2] - current_index = cmds.getAttr(node_name + ".index", asString=True) - if current_index < 0: - current_index = siblings.index(node_shortname) - - placeholder_data.update({ - "parent": parent_name, - "index": current_index - }) - return placeholder_data - - def _create_placeholder_name(self, placeholder_data): - placeholder_name_parts = placeholder_data["builder_type"].split("_") - - pos = 1 - placeholder_product_type = placeholder_data.get("product_type") - if placeholder_product_type is None: - placeholder_product_type = placeholder_data.get("family") - - if placeholder_product_type: - placeholder_name_parts.insert(pos, placeholder_product_type) - pos += 1 - - # add loader arguments if any - loader_args = placeholder_data["loader_args"] - if loader_args: - loader_args = json.loads(loader_args.replace('\'', '\"')) - values = [v for v in loader_args.values()] - for value in values: - placeholder_name_parts.insert(pos, value) - pos += 1 - - placeholder_name = "_".join(placeholder_name_parts) - - return placeholder_name.capitalize() - - def _get_loaded_repre_ids(self): - loaded_representation_ids = self.builder.get_shared_populate_data( - "loaded_representation_ids" - ) - if loaded_representation_ids is None: - try: - containers = cmds.sets("AVALON_CONTAINERS", q=True) - except ValueError: - containers = [] - - loaded_representation_ids = { - cmds.getAttr(container + ".representation") - for container in containers - } - self.builder.set_shared_populate_data( - "loaded_representation_ids", loaded_representation_ids - ) - return loaded_representation_ids - - def create_placeholder(self, placeholder_data): - selection = cmds.ls(selection=True) - if len(selection) > 1: - raise ValueError("More then one item are selected") - - parent = selection[0] if selection else None - - placeholder_data["plugin_identifier"] = self.identifier - - placeholder_name = self._create_placeholder_name(placeholder_data) - - placeholder = cmds.spaceLocator(name=placeholder_name)[0] - if parent: - placeholder = cmds.parent(placeholder, selection[0])[0] - - imprint(placeholder, placeholder_data) - - # Add helper attributes to keep placeholder info - cmds.addAttr( - placeholder, - longName="parent", - hidden=True, - dataType="string" - ) - cmds.addAttr( - placeholder, - longName="index", - hidden=True, - attributeType="short", - defaultValue=-1 - ) - - cmds.setAttr(placeholder + ".parent", "", type="string") - - def update_placeholder(self, placeholder_item, placeholder_data): - node_name = placeholder_item.scene_identifier - new_values = {} - for key, value in placeholder_data.items(): - placeholder_value = placeholder_item.data.get(key) - if value != placeholder_value: - new_values[key] = value - placeholder_item.data[key] = value - - for key in new_values.keys(): - cmds.deleteAttr(node_name + "." + key) - - imprint(node_name, new_values) - - def collect_placeholders(self): - output = [] - scene_placeholders = self._collect_scene_placeholders() - for node_name, placeholder_data in scene_placeholders.items(): - if placeholder_data.get("plugin_identifier") != self.identifier: - continue - - # TODO do data validations and maybe upgrades if they are invalid - output.append( - LoadPlaceholderItem(node_name, placeholder_data, self) - ) - - return output - - def populate_placeholder(self, placeholder): - self.populate_load_placeholder(placeholder) - - def repopulate_placeholder(self, placeholder): - repre_ids = self._get_loaded_repre_ids() - self.populate_load_placeholder(placeholder, repre_ids) - - def get_placeholder_options(self, options=None): - return self.get_load_plugin_options(options) - - def post_placeholder_process(self, placeholder, failed): - """Cleanup placeholder after load of its corresponding representations. - - Args: - placeholder (PlaceholderItem): Item which was just used to load - representation. - failed (bool): Loading of representation failed. - """ - # Hide placeholder and add them to placeholder set - node = placeholder.scene_identifier - - cmds.sets(node, addElement=PLACEHOLDER_SET) - cmds.hide(node) - cmds.setAttr(node + ".hiddenInOutliner", True) - - def delete_placeholder(self, placeholder): - """Remove placeholder if building was successful""" - cmds.delete(placeholder.scene_identifier) - - def load_succeed(self, placeholder, container): - self._parent_in_hierarchy(placeholder, container) - - def _parent_in_hierarchy(self, placeholder, container): - """Parent loaded container to placeholder's parent. - - ie : Set loaded content as placeholder's sibling - - Args: - container (str): Placeholder loaded containers - """ - - if not container: - return - - roots = cmds.sets(container, q=True) or [] - ref_node = None - try: - ref_node = get_reference_node(roots) - except AssertionError as e: - self.log.info(e.args[0]) - - nodes_to_parent = [] - for root in roots: - if ref_node: - ref_root = cmds.referenceQuery(root, nodes=True)[0] - ref_root = ( - cmds.listRelatives(ref_root, parent=True, path=True) or - [ref_root] - ) - nodes_to_parent.extend(ref_root) - continue - if root.endswith("_RN"): - # Backwards compatibility for hardcoded reference names. - refRoot = cmds.referenceQuery(root, n=True)[0] - refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot] - nodes_to_parent.extend(refRoot) - elif root not in cmds.listSets(allSets=True): - nodes_to_parent.append(root) - - elif not cmds.sets(root, q=True): - return - - # Move loaded nodes to correct index in outliner hierarchy - placeholder_form = cmds.xform( - placeholder.scene_identifier, - q=True, - matrix=True, - worldSpace=True - ) - scene_parent = cmds.listRelatives( - placeholder.scene_identifier, parent=True, fullPath=True - ) - for node in set(nodes_to_parent): - cmds.reorder(node, front=True) - cmds.reorder(node, relative=placeholder.data["index"]) - cmds.xform(node, matrix=placeholder_form, ws=True) - if scene_parent: - cmds.parent(node, scene_parent) - else: - cmds.parent(node, world=True) - - holding_sets = cmds.listSets(object=placeholder.scene_identifier) - if not holding_sets: - return - for holding_set in holding_sets: - cmds.sets(roots, forceElement=holding_set) - - def build_workfile_template(*args): builder = MayaTemplateBuilder(registered_host()) builder.build_template() diff --git a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py new file mode 100644 index 0000000000..5bfaae6500 --- /dev/null +++ b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py @@ -0,0 +1,264 @@ +import json + +from maya import cmds + +from ayon_core.pipeline.workfile.workfile_template_builder import ( + PlaceholderPlugin, + LoadPlaceholderItem, + PlaceholderLoadMixin, +) +from ayon_core.hosts.maya.api.lib import ( + read, + imprint, + get_reference_node +) +from ayon_core.hosts.maya.api.workfile_template_builder import PLACEHOLDER_SET + + +class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): + identifier = "maya.load" + label = "Maya load" + + def _collect_scene_placeholders(self): + # Cache placeholder data to shared data + placeholder_nodes = self.builder.get_shared_populate_data( + "placeholder_nodes" + ) + if placeholder_nodes is None: + attributes = cmds.ls("*.plugin_identifier", long=True) + placeholder_nodes = {} + for attribute in attributes: + node_name = attribute.rpartition(".")[0] + placeholder_nodes[node_name] = ( + self._parse_placeholder_node_data(node_name) + ) + + self.builder.set_shared_populate_data( + "placeholder_nodes", placeholder_nodes + ) + return placeholder_nodes + + def _parse_placeholder_node_data(self, node_name): + placeholder_data = read(node_name) + parent_name = ( + cmds.getAttr(node_name + ".parent", asString=True) + or node_name.rpartition("|")[0] + or "" + ) + if parent_name: + siblings = cmds.listRelatives(parent_name, children=True) + else: + siblings = cmds.ls(assemblies=True) + node_shortname = node_name.rpartition("|")[2] + current_index = cmds.getAttr(node_name + ".index", asString=True) + if current_index < 0: + current_index = siblings.index(node_shortname) + + placeholder_data.update({ + "parent": parent_name, + "index": current_index + }) + return placeholder_data + + def _create_placeholder_name(self, placeholder_data): + placeholder_name_parts = placeholder_data["builder_type"].split("_") + + pos = 1 + placeholder_product_type = placeholder_data.get("product_type") + if placeholder_product_type is None: + placeholder_product_type = placeholder_data.get("family") + + if placeholder_product_type: + placeholder_name_parts.insert(pos, placeholder_product_type) + pos += 1 + + # add loader arguments if any + loader_args = placeholder_data["loader_args"] + if loader_args: + loader_args = json.loads(loader_args.replace('\'', '\"')) + values = [v for v in loader_args.values()] + for value in values: + placeholder_name_parts.insert(pos, value) + pos += 1 + + placeholder_name = "_".join(placeholder_name_parts) + + return placeholder_name.capitalize() + + def _get_loaded_repre_ids(self): + loaded_representation_ids = self.builder.get_shared_populate_data( + "loaded_representation_ids" + ) + if loaded_representation_ids is None: + try: + containers = cmds.sets("AVALON_CONTAINERS", q=True) + except ValueError: + containers = [] + + loaded_representation_ids = { + cmds.getAttr(container + ".representation") + for container in containers + } + self.builder.set_shared_populate_data( + "loaded_representation_ids", loaded_representation_ids + ) + return loaded_representation_ids + + def create_placeholder(self, placeholder_data): + selection = cmds.ls(selection=True) + if len(selection) > 1: + raise ValueError("More then one item are selected") + + parent = selection[0] if selection else None + + placeholder_data["plugin_identifier"] = self.identifier + + placeholder_name = self._create_placeholder_name(placeholder_data) + + placeholder = cmds.spaceLocator(name=placeholder_name)[0] + if parent: + placeholder = cmds.parent(placeholder, selection[0])[0] + + imprint(placeholder, placeholder_data) + + # Add helper attributes to keep placeholder info + cmds.addAttr( + placeholder, + longName="parent", + hidden=True, + dataType="string" + ) + cmds.addAttr( + placeholder, + longName="index", + hidden=True, + attributeType="short", + defaultValue=-1 + ) + + cmds.setAttr(placeholder + ".parent", "", type="string") + + def update_placeholder(self, placeholder_item, placeholder_data): + node_name = placeholder_item.scene_identifier + new_values = {} + for key, value in placeholder_data.items(): + placeholder_value = placeholder_item.data.get(key) + if value != placeholder_value: + new_values[key] = value + placeholder_item.data[key] = value + + for key in new_values.keys(): + cmds.deleteAttr(node_name + "." + key) + + imprint(node_name, new_values) + + def collect_placeholders(self): + output = [] + scene_placeholders = self._collect_scene_placeholders() + for node_name, placeholder_data in scene_placeholders.items(): + if placeholder_data.get("plugin_identifier") != self.identifier: + continue + + # TODO do data validations and maybe upgrades if they are invalid + output.append( + LoadPlaceholderItem(node_name, placeholder_data, self) + ) + + return output + + def populate_placeholder(self, placeholder): + self.populate_load_placeholder(placeholder) + + def repopulate_placeholder(self, placeholder): + repre_ids = self._get_loaded_repre_ids() + self.populate_load_placeholder(placeholder, repre_ids) + + def get_placeholder_options(self, options=None): + return self.get_load_plugin_options(options) + + def post_placeholder_process(self, placeholder, failed): + """Cleanup placeholder after load of its corresponding representations. + + Args: + placeholder (PlaceholderItem): Item which was just used to load + representation. + failed (bool): Loading of representation failed. + """ + # Hide placeholder and add them to placeholder set + node = placeholder.scene_identifier + + cmds.sets(node, addElement=PLACEHOLDER_SET) + cmds.hide(node) + cmds.setAttr(node + ".hiddenInOutliner", True) + + def delete_placeholder(self, placeholder): + """Remove placeholder if building was successful""" + cmds.delete(placeholder.scene_identifier) + + def load_succeed(self, placeholder, container): + self._parent_in_hierarchy(placeholder, container) + + def _parent_in_hierarchy(self, placeholder, container): + """Parent loaded container to placeholder's parent. + + ie : Set loaded content as placeholder's sibling + + Args: + container (str): Placeholder loaded containers + """ + + if not container: + return + + roots = cmds.sets(container, q=True) or [] + ref_node = None + try: + ref_node = get_reference_node(roots) + except AssertionError as e: + self.log.info(e.args[0]) + + nodes_to_parent = [] + for root in roots: + if ref_node: + ref_root = cmds.referenceQuery(root, nodes=True)[0] + ref_root = ( + cmds.listRelatives(ref_root, parent=True, path=True) or + [ref_root] + ) + nodes_to_parent.extend(ref_root) + continue + if root.endswith("_RN"): + # Backwards compatibility for hardcoded reference names. + refRoot = cmds.referenceQuery(root, n=True)[0] + refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot] + nodes_to_parent.extend(refRoot) + elif root not in cmds.listSets(allSets=True): + nodes_to_parent.append(root) + + elif not cmds.sets(root, q=True): + return + + # Move loaded nodes to correct index in outliner hierarchy + placeholder_form = cmds.xform( + placeholder.scene_identifier, + q=True, + matrix=True, + worldSpace=True + ) + scene_parent = cmds.listRelatives( + placeholder.scene_identifier, parent=True, fullPath=True + ) + for node in set(nodes_to_parent): + cmds.reorder(node, front=True) + cmds.reorder(node, relative=placeholder.data["index"]) + cmds.xform(node, matrix=placeholder_form, ws=True) + if scene_parent: + cmds.parent(node, scene_parent) + else: + cmds.parent(node, world=True) + + holding_sets = cmds.listSets(object=placeholder.scene_identifier) + if not holding_sets: + return + for holding_set in holding_sets: + cmds.sets(roots, forceElement=holding_set) diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index 0d44aba2f9..bdf601e30d 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -18,6 +18,7 @@ from ayon_core.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, register_inventory_action_path, + register_template_placeholder_plugin_path, AYON_INSTANCE_ID, AVALON_INSTANCE_ID, AVALON_CONTAINER_ID, @@ -52,8 +53,6 @@ from .lib import ( MENU_LABEL, ) from .workfile_template_builder import ( - NukePlaceholderLoadPlugin, - NukePlaceholderCreatePlugin, build_workfile_template, create_placeholder, update_placeholder, @@ -76,6 +75,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") +TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") # registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): @@ -105,18 +105,11 @@ class NukeHost( def get_workfile_extensions(self): return file_extensions() - def get_workfile_build_placeholder_plugins(self): - return [ - NukePlaceholderLoadPlugin, - NukePlaceholderCreatePlugin - ] - def get_containers(self): return ls() def install(self): - ''' Installing all requarements for Nuke host - ''' + """Installing all requirements for Nuke host""" pyblish.api.register_host("nuke") @@ -125,6 +118,7 @@ class NukeHost( register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) + register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) # Register AYON event for workfiles loading. register_event_callback("workio.open_file", check_inventory_versions) @@ -178,7 +172,6 @@ def add_nuke_callbacks(): # set apply all workfile settings on script load and save nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) - if nuke_settings["dirmap"]["enabled"]: log.info("Added Nuke's dir-mapping callback ...") # Add dirmap for file paths. diff --git a/client/ayon_core/hosts/nuke/api/workfile_template_builder.py b/client/ayon_core/hosts/nuke/api/workfile_template_builder.py index 495edd9e5f..aebf91c4a4 100644 --- a/client/ayon_core/hosts/nuke/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/nuke/api/workfile_template_builder.py @@ -1,30 +1,17 @@ import collections import nuke + from ayon_core.pipeline import registered_host from ayon_core.pipeline.workfile.workfile_template_builder import ( AbstractTemplateBuilder, PlaceholderPlugin, - LoadPlaceholderItem, - CreatePlaceholderItem, - PlaceholderLoadMixin, - PlaceholderCreateMixin, ) from ayon_core.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, ) from .lib import ( - find_free_space_to_paste_nodes, - get_extreme_positions, - get_group_io_nodes, imprint, - refresh_node, - refresh_nodes, reset_selection, - get_names_from_nodes, - get_nodes_by_names, - select_nodes, - duplicate_node, - node_tempfile, get_main_window, WorkfileSettings, ) @@ -54,6 +41,7 @@ class NukeTemplateBuilder(AbstractTemplateBuilder): return True + class NukePlaceholderPlugin(PlaceholderPlugin): node_color = 4278190335 @@ -120,843 +108,6 @@ class NukePlaceholderPlugin(PlaceholderPlugin): nuke.delete(placeholder_node) -class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): - identifier = "nuke.load" - label = "Nuke load" - - def _parse_placeholder_node_data(self, node): - placeholder_data = super( - NukePlaceholderLoadPlugin, self - )._parse_placeholder_node_data(node) - - node_knobs = node.knobs() - nb_children = 0 - if "nb_children" in node_knobs: - nb_children = int(node_knobs["nb_children"].getValue()) - placeholder_data["nb_children"] = nb_children - - siblings = [] - if "siblings" in node_knobs: - siblings = node_knobs["siblings"].values() - placeholder_data["siblings"] = siblings - - node_full_name = node.fullName() - placeholder_data["group_name"] = node_full_name.rpartition(".")[0] - placeholder_data["last_loaded"] = [] - placeholder_data["delete"] = False - return placeholder_data - - def _get_loaded_repre_ids(self): - loaded_representation_ids = self.builder.get_shared_populate_data( - "loaded_representation_ids" - ) - if loaded_representation_ids is None: - loaded_representation_ids = set() - for node in nuke.allNodes(): - if "repre_id" in node.knobs(): - loaded_representation_ids.add( - node.knob("repre_id").getValue() - ) - - self.builder.set_shared_populate_data( - "loaded_representation_ids", loaded_representation_ids - ) - return loaded_representation_ids - - def _before_placeholder_load(self, placeholder): - placeholder.data["nodes_init"] = nuke.allNodes() - - def _before_repre_load(self, placeholder, representation): - placeholder.data["last_repre_id"] = representation["id"] - - def collect_placeholders(self): - output = [] - scene_placeholders = self._collect_scene_placeholders() - for node_name, node in scene_placeholders.items(): - plugin_identifier_knob = node.knob("plugin_identifier") - if ( - plugin_identifier_knob is None - or plugin_identifier_knob.getValue() != self.identifier - ): - continue - - placeholder_data = self._parse_placeholder_node_data(node) - # TODO do data validations and maybe updgrades if are invalid - output.append( - LoadPlaceholderItem(node_name, placeholder_data, self) - ) - - return output - - def populate_placeholder(self, placeholder): - self.populate_load_placeholder(placeholder) - - def repopulate_placeholder(self, placeholder): - repre_ids = self._get_loaded_repre_ids() - self.populate_load_placeholder(placeholder, repre_ids) - - def get_placeholder_options(self, options=None): - return self.get_load_plugin_options(options) - - def post_placeholder_process(self, placeholder, failed): - """Cleanup placeholder after load of its corresponding representations. - - Args: - placeholder (PlaceholderItem): Item which was just used to load - representation. - failed (bool): Loading of representation failed. - """ - # deselect all selected nodes - placeholder_node = nuke.toNode(placeholder.scene_identifier) - - # getting the latest nodes added - # TODO get from shared populate data! - nodes_init = placeholder.data["nodes_init"] - nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) - self.log.debug("Loaded nodes: {}".format(nodes_loaded)) - if not nodes_loaded: - return - - placeholder.data["delete"] = True - - nodes_loaded = self._move_to_placeholder_group( - placeholder, nodes_loaded - ) - placeholder.data["last_loaded"] = nodes_loaded - refresh_nodes(nodes_loaded) - - # positioning of the loaded nodes - min_x, min_y, _, _ = get_extreme_positions(nodes_loaded) - for node in nodes_loaded: - xpos = (node.xpos() - min_x) + placeholder_node.xpos() - ypos = (node.ypos() - min_y) + placeholder_node.ypos() - node.setXYpos(xpos, ypos) - refresh_nodes(nodes_loaded) - - # fix the problem of z_order for backdrops - self._fix_z_order(placeholder) - - if placeholder.data.get("keep_placeholder"): - self._imprint_siblings(placeholder) - - if placeholder.data["nb_children"] == 0: - # save initial nodes positions and dimensions, update them - # and set inputs and outputs of loaded nodes - if placeholder.data.get("keep_placeholder"): - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) - - self._set_loaded_connections(placeholder) - - elif placeholder.data["siblings"]: - # create copies of placeholder siblings for the new loaded nodes, - # set their inputs and outputs and update all nodes positions and - # dimensions and siblings names - - siblings = get_nodes_by_names(placeholder.data["siblings"]) - refresh_nodes(siblings) - copies = self._create_sib_copies(placeholder) - new_nodes = list(copies.values()) # copies nodes - self._update_nodes(new_nodes, nodes_loaded) - placeholder_node.removeKnob(placeholder_node.knob("siblings")) - new_nodes_name = get_names_from_nodes(new_nodes) - imprint(placeholder_node, {"siblings": new_nodes_name}) - self._set_copies_connections(placeholder, copies) - - self._update_nodes( - nuke.allNodes(), - new_nodes + nodes_loaded, - 20 - ) - - new_siblings = get_names_from_nodes(new_nodes) - placeholder.data["siblings"] = new_siblings - - else: - # if the placeholder doesn't have siblings, the loaded - # nodes will be placed in a free space - - xpointer, ypointer = find_free_space_to_paste_nodes( - nodes_loaded, direction="bottom", offset=200 - ) - node = nuke.createNode("NoOp") - reset_selection() - nuke.delete(node) - for node in nodes_loaded: - xpos = (node.xpos() - min_x) + xpointer - ypos = (node.ypos() - min_y) + ypointer - node.setXYpos(xpos, ypos) - - placeholder.data["nb_children"] += 1 - reset_selection() - - # go back to root group - nuke.root().begin() - - def _move_to_placeholder_group(self, placeholder, nodes_loaded): - """ - opening the placeholder's group and copying loaded nodes in it. - - Returns : - nodes_loaded (list): the new list of pasted nodes - """ - - groups_name = placeholder.data["group_name"] - reset_selection() - select_nodes(nodes_loaded) - if groups_name: - with node_tempfile() as filepath: - nuke.nodeCopy(filepath) - for node in nuke.selectedNodes(): - nuke.delete(node) - group = nuke.toNode(groups_name) - group.begin() - nuke.nodePaste(filepath) - nodes_loaded = nuke.selectedNodes() - return nodes_loaded - - def _fix_z_order(self, placeholder): - """Fix the problem of z_order when a backdrop is loaded.""" - - nodes_loaded = placeholder.data["last_loaded"] - loaded_backdrops = [] - bd_orders = set() - for node in nodes_loaded: - if isinstance(node, nuke.BackdropNode): - loaded_backdrops.append(node) - bd_orders.add(node.knob("z_order").getValue()) - - if not bd_orders: - return - - sib_orders = set() - for node_name in placeholder.data["siblings"]: - node = nuke.toNode(node_name) - if isinstance(node, nuke.BackdropNode): - sib_orders.add(node.knob("z_order").getValue()) - - if not sib_orders: - return - - min_order = min(bd_orders) - max_order = max(sib_orders) - for backdrop_node in loaded_backdrops: - z_order = backdrop_node.knob("z_order").getValue() - backdrop_node.knob("z_order").setValue( - z_order + max_order - min_order + 1) - - def _imprint_siblings(self, placeholder): - """ - - add siblings names to placeholder attributes (nodes loaded with it) - - add Id to the attributes of all the other nodes - """ - - loaded_nodes = placeholder.data["last_loaded"] - loaded_nodes_set = set(loaded_nodes) - data = {"repre_id": str(placeholder.data["last_repre_id"])} - - for node in loaded_nodes: - node_knobs = node.knobs() - if "builder_type" not in node_knobs: - # save the id of representation for all imported nodes - imprint(node, data) - node.knob("repre_id").setVisible(False) - refresh_node(node) - continue - - if ( - "is_placeholder" not in node_knobs - or ( - "is_placeholder" in node_knobs - and node.knob("is_placeholder").value() - ) - ): - siblings = list(loaded_nodes_set - {node}) - siblings_name = get_names_from_nodes(siblings) - siblings = {"siblings": siblings_name} - imprint(node, siblings) - - def _imprint_inits(self): - """Add initial positions and dimensions to the attributes""" - - for node in nuke.allNodes(): - refresh_node(node) - imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) - node.knob("x_init").setVisible(False) - node.knob("y_init").setVisible(False) - width = node.screenWidth() - height = node.screenHeight() - if "bdwidth" in node.knobs(): - imprint(node, {"w_init": width, "h_init": height}) - node.knob("w_init").setVisible(False) - node.knob("h_init").setVisible(False) - refresh_node(node) - - def _update_nodes( - self, placeholder, nodes, considered_nodes, offset_y=None - ): - """Adjust backdrop nodes dimensions and positions. - - Considering some nodes sizes. - - Args: - nodes (list): list of nodes to update - considered_nodes (list): list of nodes to consider while updating - positions and dimensions - offset (int): distance between copies - """ - - placeholder_node = nuke.toNode(placeholder.scene_identifier) - - min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) - - diff_x = diff_y = 0 - contained_nodes = [] # for backdrops - - if offset_y is None: - width_ph = placeholder_node.screenWidth() - height_ph = placeholder_node.screenHeight() - diff_y = max_y - min_y - height_ph - diff_x = max_x - min_x - width_ph - contained_nodes = [placeholder_node] - min_x = placeholder_node.xpos() - min_y = placeholder_node.ypos() - else: - siblings = get_nodes_by_names(placeholder.data["siblings"]) - minX, _, maxX, _ = get_extreme_positions(siblings) - diff_y = max_y - min_y + 20 - diff_x = abs(max_x - min_x - maxX + minX) - contained_nodes = considered_nodes - - if diff_y <= 0 and diff_x <= 0: - return - - for node in nodes: - refresh_node(node) - - if ( - node == placeholder_node - or node in considered_nodes - ): - continue - - if ( - not isinstance(node, nuke.BackdropNode) - or ( - isinstance(node, nuke.BackdropNode) - and not set(contained_nodes) <= set(node.getNodes()) - ) - ): - if offset_y is None and node.xpos() >= min_x: - node.setXpos(node.xpos() + diff_x) - - if node.ypos() >= min_y: - node.setYpos(node.ypos() + diff_y) - - else: - width = node.screenWidth() - height = node.screenHeight() - node.knob("bdwidth").setValue(width + diff_x) - node.knob("bdheight").setValue(height + diff_y) - - refresh_node(node) - - def _set_loaded_connections(self, placeholder): - """ - set inputs and outputs of loaded nodes""" - - placeholder_node = nuke.toNode(placeholder.scene_identifier) - input_node, output_node = get_group_io_nodes( - placeholder.data["last_loaded"] - ) - for node in placeholder_node.dependent(): - for idx in range(node.inputs()): - if node.input(idx) == placeholder_node and output_node: - node.setInput(idx, output_node) - - for node in placeholder_node.dependencies(): - for idx in range(placeholder_node.inputs()): - if placeholder_node.input(idx) == node and input_node: - input_node.setInput(0, node) - - def _create_sib_copies(self, placeholder): - """ creating copies of the palce_holder siblings (the ones who were - loaded with it) for the new nodes added - - Returns : - copies (dict) : with copied nodes names and their copies - """ - - copies = {} - siblings = get_nodes_by_names(placeholder.data["siblings"]) - for node in siblings: - new_node = duplicate_node(node) - - x_init = int(new_node.knob("x_init").getValue()) - y_init = int(new_node.knob("y_init").getValue()) - new_node.setXYpos(x_init, y_init) - if isinstance(new_node, nuke.BackdropNode): - w_init = new_node.knob("w_init").getValue() - h_init = new_node.knob("h_init").getValue() - new_node.knob("bdwidth").setValue(w_init) - new_node.knob("bdheight").setValue(h_init) - refresh_node(node) - - if "repre_id" in node.knobs().keys(): - node.removeKnob(node.knob("repre_id")) - copies[node.name()] = new_node - return copies - - def _set_copies_connections(self, placeholder, copies): - """Set inputs and outputs of the copies. - - Args: - copies (dict): Copied nodes by their names. - """ - - last_input, last_output = get_group_io_nodes( - placeholder.data["last_loaded"] - ) - siblings = get_nodes_by_names(placeholder.data["siblings"]) - siblings_input, siblings_output = get_group_io_nodes(siblings) - copy_input = copies[siblings_input.name()] - copy_output = copies[siblings_output.name()] - - for node_init in siblings: - if node_init == siblings_output: - continue - - node_copy = copies[node_init.name()] - for node in node_init.dependent(): - for idx in range(node.inputs()): - if node.input(idx) != node_init: - continue - - if node in siblings: - copies[node.name()].setInput(idx, node_copy) - else: - last_input.setInput(0, node_copy) - - for node in node_init.dependencies(): - for idx in range(node_init.inputs()): - if node_init.input(idx) != node: - continue - - if node_init == siblings_input: - copy_input.setInput(idx, node) - elif node in siblings: - node_copy.setInput(idx, copies[node.name()]) - else: - node_copy.setInput(idx, last_output) - - siblings_input.setInput(0, copy_output) - - -class NukePlaceholderCreatePlugin( - NukePlaceholderPlugin, PlaceholderCreateMixin -): - identifier = "nuke.create" - label = "Nuke create" - - def _parse_placeholder_node_data(self, node): - placeholder_data = super( - NukePlaceholderCreatePlugin, self - )._parse_placeholder_node_data(node) - - node_knobs = node.knobs() - nb_children = 0 - if "nb_children" in node_knobs: - nb_children = int(node_knobs["nb_children"].getValue()) - placeholder_data["nb_children"] = nb_children - - siblings = [] - if "siblings" in node_knobs: - siblings = node_knobs["siblings"].values() - placeholder_data["siblings"] = siblings - - node_full_name = node.fullName() - placeholder_data["group_name"] = node_full_name.rpartition(".")[0] - placeholder_data["last_loaded"] = [] - placeholder_data["delete"] = False - return placeholder_data - - def _before_instance_create(self, placeholder): - placeholder.data["nodes_init"] = nuke.allNodes() - - def collect_placeholders(self): - output = [] - scene_placeholders = self._collect_scene_placeholders() - for node_name, node in scene_placeholders.items(): - plugin_identifier_knob = node.knob("plugin_identifier") - if ( - plugin_identifier_knob is None - or plugin_identifier_knob.getValue() != self.identifier - ): - continue - - placeholder_data = self._parse_placeholder_node_data(node) - - output.append( - CreatePlaceholderItem(node_name, placeholder_data, self) - ) - - return output - - def populate_placeholder(self, placeholder): - self.populate_create_placeholder(placeholder) - - def repopulate_placeholder(self, placeholder): - self.populate_create_placeholder(placeholder) - - def get_placeholder_options(self, options=None): - return self.get_create_plugin_options(options) - - def post_placeholder_process(self, placeholder, failed): - """Cleanup placeholder after load of its corresponding representations. - - Args: - placeholder (PlaceholderItem): Item which was just used to load - representation. - failed (bool): Loading of representation failed. - """ - # deselect all selected nodes - placeholder_node = nuke.toNode(placeholder.scene_identifier) - - # getting the latest nodes added - nodes_init = placeholder.data["nodes_init"] - nodes_created = list(set(nuke.allNodes()) - set(nodes_init)) - self.log.debug("Created nodes: {}".format(nodes_created)) - if not nodes_created: - return - - placeholder.data["delete"] = True - - nodes_created = self._move_to_placeholder_group( - placeholder, nodes_created - ) - placeholder.data["last_created"] = nodes_created - refresh_nodes(nodes_created) - - # positioning of the created nodes - min_x, min_y, _, _ = get_extreme_positions(nodes_created) - for node in nodes_created: - xpos = (node.xpos() - min_x) + placeholder_node.xpos() - ypos = (node.ypos() - min_y) + placeholder_node.ypos() - node.setXYpos(xpos, ypos) - refresh_nodes(nodes_created) - - # fix the problem of z_order for backdrops - self._fix_z_order(placeholder) - - if placeholder.data.get("keep_placeholder"): - self._imprint_siblings(placeholder) - - if placeholder.data["nb_children"] == 0: - # save initial nodes positions and dimensions, update them - # and set inputs and outputs of created nodes - - if placeholder.data.get("keep_placeholder"): - self._imprint_inits() - self._update_nodes(placeholder, nuke.allNodes(), nodes_created) - - self._set_created_connections(placeholder) - - elif placeholder.data["siblings"]: - # create copies of placeholder siblings for the new created nodes, - # set their inputs and outputs and update all nodes positions and - # dimensions and siblings names - - siblings = get_nodes_by_names(placeholder.data["siblings"]) - refresh_nodes(siblings) - copies = self._create_sib_copies(placeholder) - new_nodes = list(copies.values()) # copies nodes - self._update_nodes(new_nodes, nodes_created) - placeholder_node.removeKnob(placeholder_node.knob("siblings")) - new_nodes_name = get_names_from_nodes(new_nodes) - imprint(placeholder_node, {"siblings": new_nodes_name}) - self._set_copies_connections(placeholder, copies) - - self._update_nodes( - nuke.allNodes(), - new_nodes + nodes_created, - 20 - ) - - new_siblings = get_names_from_nodes(new_nodes) - placeholder.data["siblings"] = new_siblings - - else: - # if the placeholder doesn't have siblings, the created - # nodes will be placed in a free space - - xpointer, ypointer = find_free_space_to_paste_nodes( - nodes_created, direction="bottom", offset=200 - ) - node = nuke.createNode("NoOp") - reset_selection() - nuke.delete(node) - for node in nodes_created: - xpos = (node.xpos() - min_x) + xpointer - ypos = (node.ypos() - min_y) + ypointer - node.setXYpos(xpos, ypos) - - placeholder.data["nb_children"] += 1 - reset_selection() - - # go back to root group - nuke.root().begin() - - def _move_to_placeholder_group(self, placeholder, nodes_created): - """ - opening the placeholder's group and copying created nodes in it. - - Returns : - nodes_created (list): the new list of pasted nodes - """ - groups_name = placeholder.data["group_name"] - reset_selection() - select_nodes(nodes_created) - if groups_name: - with node_tempfile() as filepath: - nuke.nodeCopy(filepath) - for node in nuke.selectedNodes(): - nuke.delete(node) - group = nuke.toNode(groups_name) - group.begin() - nuke.nodePaste(filepath) - nodes_created = nuke.selectedNodes() - return nodes_created - - def _fix_z_order(self, placeholder): - """Fix the problem of z_order when a backdrop is create.""" - - nodes_created = placeholder.data["last_created"] - created_backdrops = [] - bd_orders = set() - for node in nodes_created: - if isinstance(node, nuke.BackdropNode): - created_backdrops.append(node) - bd_orders.add(node.knob("z_order").getValue()) - - if not bd_orders: - return - - sib_orders = set() - for node_name in placeholder.data["siblings"]: - node = nuke.toNode(node_name) - if isinstance(node, nuke.BackdropNode): - sib_orders.add(node.knob("z_order").getValue()) - - if not sib_orders: - return - - min_order = min(bd_orders) - max_order = max(sib_orders) - for backdrop_node in created_backdrops: - z_order = backdrop_node.knob("z_order").getValue() - backdrop_node.knob("z_order").setValue( - z_order + max_order - min_order + 1) - - def _imprint_siblings(self, placeholder): - """ - - add siblings names to placeholder attributes (nodes created with it) - - add Id to the attributes of all the other nodes - """ - - created_nodes = placeholder.data["last_created"] - created_nodes_set = set(created_nodes) - - for node in created_nodes: - node_knobs = node.knobs() - - if ( - "is_placeholder" not in node_knobs - or ( - "is_placeholder" in node_knobs - and node.knob("is_placeholder").value() - ) - ): - siblings = list(created_nodes_set - {node}) - siblings_name = get_names_from_nodes(siblings) - siblings = {"siblings": siblings_name} - imprint(node, siblings) - - def _imprint_inits(self): - """Add initial positions and dimensions to the attributes""" - - for node in nuke.allNodes(): - refresh_node(node) - imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) - node.knob("x_init").setVisible(False) - node.knob("y_init").setVisible(False) - width = node.screenWidth() - height = node.screenHeight() - if "bdwidth" in node.knobs(): - imprint(node, {"w_init": width, "h_init": height}) - node.knob("w_init").setVisible(False) - node.knob("h_init").setVisible(False) - refresh_node(node) - - def _update_nodes( - self, placeholder, nodes, considered_nodes, offset_y=None - ): - """Adjust backdrop nodes dimensions and positions. - - Considering some nodes sizes. - - Args: - nodes (list): list of nodes to update - considered_nodes (list): list of nodes to consider while updating - positions and dimensions - offset (int): distance between copies - """ - - placeholder_node = nuke.toNode(placeholder.scene_identifier) - - min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) - - diff_x = diff_y = 0 - contained_nodes = [] # for backdrops - - if offset_y is None: - width_ph = placeholder_node.screenWidth() - height_ph = placeholder_node.screenHeight() - diff_y = max_y - min_y - height_ph - diff_x = max_x - min_x - width_ph - contained_nodes = [placeholder_node] - min_x = placeholder_node.xpos() - min_y = placeholder_node.ypos() - else: - siblings = get_nodes_by_names(placeholder.data["siblings"]) - minX, _, maxX, _ = get_extreme_positions(siblings) - diff_y = max_y - min_y + 20 - diff_x = abs(max_x - min_x - maxX + minX) - contained_nodes = considered_nodes - - if diff_y <= 0 and diff_x <= 0: - return - - for node in nodes: - refresh_node(node) - - if ( - node == placeholder_node - or node in considered_nodes - ): - continue - - if ( - not isinstance(node, nuke.BackdropNode) - or ( - isinstance(node, nuke.BackdropNode) - and not set(contained_nodes) <= set(node.getNodes()) - ) - ): - if offset_y is None and node.xpos() >= min_x: - node.setXpos(node.xpos() + diff_x) - - if node.ypos() >= min_y: - node.setYpos(node.ypos() + diff_y) - - else: - width = node.screenWidth() - height = node.screenHeight() - node.knob("bdwidth").setValue(width + diff_x) - node.knob("bdheight").setValue(height + diff_y) - - refresh_node(node) - - def _set_created_connections(self, placeholder): - """ - set inputs and outputs of created nodes""" - - placeholder_node = nuke.toNode(placeholder.scene_identifier) - input_node, output_node = get_group_io_nodes( - placeholder.data["last_created"] - ) - for node in placeholder_node.dependent(): - for idx in range(node.inputs()): - if node.input(idx) == placeholder_node and output_node: - node.setInput(idx, output_node) - - for node in placeholder_node.dependencies(): - for idx in range(placeholder_node.inputs()): - if placeholder_node.input(idx) == node and input_node: - input_node.setInput(0, node) - - def _create_sib_copies(self, placeholder): - """ creating copies of the palce_holder siblings (the ones who were - created with it) for the new nodes added - - Returns : - copies (dict) : with copied nodes names and their copies - """ - - copies = {} - siblings = get_nodes_by_names(placeholder.data["siblings"]) - for node in siblings: - new_node = duplicate_node(node) - - x_init = int(new_node.knob("x_init").getValue()) - y_init = int(new_node.knob("y_init").getValue()) - new_node.setXYpos(x_init, y_init) - if isinstance(new_node, nuke.BackdropNode): - w_init = new_node.knob("w_init").getValue() - h_init = new_node.knob("h_init").getValue() - new_node.knob("bdwidth").setValue(w_init) - new_node.knob("bdheight").setValue(h_init) - refresh_node(node) - - if "repre_id" in node.knobs().keys(): - node.removeKnob(node.knob("repre_id")) - copies[node.name()] = new_node - return copies - - def _set_copies_connections(self, placeholder, copies): - """Set inputs and outputs of the copies. - - Args: - copies (dict): Copied nodes by their names. - """ - - last_input, last_output = get_group_io_nodes( - placeholder.data["last_created"] - ) - siblings = get_nodes_by_names(placeholder.data["siblings"]) - siblings_input, siblings_output = get_group_io_nodes(siblings) - copy_input = copies[siblings_input.name()] - copy_output = copies[siblings_output.name()] - - for node_init in siblings: - if node_init == siblings_output: - continue - - node_copy = copies[node_init.name()] - for node in node_init.dependent(): - for idx in range(node.inputs()): - if node.input(idx) != node_init: - continue - - if node in siblings: - copies[node.name()].setInput(idx, node_copy) - else: - last_input.setInput(0, node_copy) - - for node in node_init.dependencies(): - for idx in range(node_init.inputs()): - if node_init.input(idx) != node: - continue - - if node_init == siblings_input: - copy_input.setInput(idx, node) - elif node in siblings: - node_copy.setInput(idx, copies[node.name()]) - else: - node_copy.setInput(idx, last_output) - - siblings_input.setInput(0, copy_output) - - def build_workfile_template(*args, **kwargs): builder = NukeTemplateBuilder(registered_host()) builder.build_template(*args, **kwargs) diff --git a/client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py b/client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py new file mode 100644 index 0000000000..a5490021e4 --- /dev/null +++ b/client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py @@ -0,0 +1,428 @@ +import nuke + +from ayon_core.pipeline.workfile.workfile_template_builder import ( + CreatePlaceholderItem, + PlaceholderCreateMixin, +) +from ayon_core.hosts.nuke.api.lib import ( + find_free_space_to_paste_nodes, + get_extreme_positions, + get_group_io_nodes, + imprint, + refresh_node, + refresh_nodes, + reset_selection, + get_names_from_nodes, + get_nodes_by_names, + select_nodes, + duplicate_node, + node_tempfile, +) +from ayon_core.hosts.nuke.api.workfile_template_builder import ( + NukePlaceholderPlugin +) + + +class NukePlaceholderCreatePlugin( + NukePlaceholderPlugin, PlaceholderCreateMixin +): + identifier = "nuke.create" + label = "Nuke create" + + def _parse_placeholder_node_data(self, node): + placeholder_data = super( + NukePlaceholderCreatePlugin, self + )._parse_placeholder_node_data(node) + + node_knobs = node.knobs() + nb_children = 0 + if "nb_children" in node_knobs: + nb_children = int(node_knobs["nb_children"].getValue()) + placeholder_data["nb_children"] = nb_children + + siblings = [] + if "siblings" in node_knobs: + siblings = node_knobs["siblings"].values() + placeholder_data["siblings"] = siblings + + node_full_name = node.fullName() + placeholder_data["group_name"] = node_full_name.rpartition(".")[0] + placeholder_data["last_loaded"] = [] + placeholder_data["delete"] = False + return placeholder_data + + def _before_instance_create(self, placeholder): + placeholder.data["nodes_init"] = nuke.allNodes() + + def collect_placeholders(self): + output = [] + scene_placeholders = self._collect_scene_placeholders() + for node_name, node in scene_placeholders.items(): + plugin_identifier_knob = node.knob("plugin_identifier") + if ( + plugin_identifier_knob is None + or plugin_identifier_knob.getValue() != self.identifier + ): + continue + + placeholder_data = self._parse_placeholder_node_data(node) + + output.append( + CreatePlaceholderItem(node_name, placeholder_data, self) + ) + + return output + + def populate_placeholder(self, placeholder): + self.populate_create_placeholder(placeholder) + + def repopulate_placeholder(self, placeholder): + self.populate_create_placeholder(placeholder) + + def get_placeholder_options(self, options=None): + return self.get_create_plugin_options(options) + + def post_placeholder_process(self, placeholder, failed): + """Cleanup placeholder after load of its corresponding representations. + + Args: + placeholder (PlaceholderItem): Item which was just used to load + representation. + failed (bool): Loading of representation failed. + """ + # deselect all selected nodes + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + # getting the latest nodes added + nodes_init = placeholder.data["nodes_init"] + nodes_created = list(set(nuke.allNodes()) - set(nodes_init)) + self.log.debug("Created nodes: {}".format(nodes_created)) + if not nodes_created: + return + + placeholder.data["delete"] = True + + nodes_created = self._move_to_placeholder_group( + placeholder, nodes_created + ) + placeholder.data["last_created"] = nodes_created + refresh_nodes(nodes_created) + + # positioning of the created nodes + min_x, min_y, _, _ = get_extreme_positions(nodes_created) + for node in nodes_created: + xpos = (node.xpos() - min_x) + placeholder_node.xpos() + ypos = (node.ypos() - min_y) + placeholder_node.ypos() + node.setXYpos(xpos, ypos) + refresh_nodes(nodes_created) + + # fix the problem of z_order for backdrops + self._fix_z_order(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) + + if placeholder.data["nb_children"] == 0: + # save initial nodes positions and dimensions, update them + # and set inputs and outputs of created nodes + + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_created) + + self._set_created_connections(placeholder) + + elif placeholder.data["siblings"]: + # create copies of placeholder siblings for the new created nodes, + # set their inputs and outputs and update all nodes positions and + # dimensions and siblings names + + siblings = get_nodes_by_names(placeholder.data["siblings"]) + refresh_nodes(siblings) + copies = self._create_sib_copies(placeholder) + new_nodes = list(copies.values()) # copies nodes + self._update_nodes(new_nodes, nodes_created) + placeholder_node.removeKnob(placeholder_node.knob("siblings")) + new_nodes_name = get_names_from_nodes(new_nodes) + imprint(placeholder_node, {"siblings": new_nodes_name}) + self._set_copies_connections(placeholder, copies) + + self._update_nodes( + nuke.allNodes(), + new_nodes + nodes_created, + 20 + ) + + new_siblings = get_names_from_nodes(new_nodes) + placeholder.data["siblings"] = new_siblings + + else: + # if the placeholder doesn't have siblings, the created + # nodes will be placed in a free space + + xpointer, ypointer = find_free_space_to_paste_nodes( + nodes_created, direction="bottom", offset=200 + ) + node = nuke.createNode("NoOp") + reset_selection() + nuke.delete(node) + for node in nodes_created: + xpos = (node.xpos() - min_x) + xpointer + ypos = (node.ypos() - min_y) + ypointer + node.setXYpos(xpos, ypos) + + placeholder.data["nb_children"] += 1 + reset_selection() + + # go back to root group + nuke.root().begin() + + def _move_to_placeholder_group(self, placeholder, nodes_created): + """ + opening the placeholder's group and copying created nodes in it. + + Returns : + nodes_created (list): the new list of pasted nodes + """ + groups_name = placeholder.data["group_name"] + reset_selection() + select_nodes(nodes_created) + if groups_name: + with node_tempfile() as filepath: + nuke.nodeCopy(filepath) + for node in nuke.selectedNodes(): + nuke.delete(node) + group = nuke.toNode(groups_name) + group.begin() + nuke.nodePaste(filepath) + nodes_created = nuke.selectedNodes() + return nodes_created + + def _fix_z_order(self, placeholder): + """Fix the problem of z_order when a backdrop is create.""" + + nodes_created = placeholder.data["last_created"] + created_backdrops = [] + bd_orders = set() + for node in nodes_created: + if isinstance(node, nuke.BackdropNode): + created_backdrops.append(node) + bd_orders.add(node.knob("z_order").getValue()) + + if not bd_orders: + return + + sib_orders = set() + for node_name in placeholder.data["siblings"]: + node = nuke.toNode(node_name) + if isinstance(node, nuke.BackdropNode): + sib_orders.add(node.knob("z_order").getValue()) + + if not sib_orders: + return + + min_order = min(bd_orders) + max_order = max(sib_orders) + for backdrop_node in created_backdrops: + z_order = backdrop_node.knob("z_order").getValue() + backdrop_node.knob("z_order").setValue( + z_order + max_order - min_order + 1) + + def _imprint_siblings(self, placeholder): + """ + - add siblings names to placeholder attributes (nodes created with it) + - add Id to the attributes of all the other nodes + """ + + created_nodes = placeholder.data["last_created"] + created_nodes_set = set(created_nodes) + + for node in created_nodes: + node_knobs = node.knobs() + + if ( + "is_placeholder" not in node_knobs + or ( + "is_placeholder" in node_knobs + and node.knob("is_placeholder").value() + ) + ): + siblings = list(created_nodes_set - {node}) + siblings_name = get_names_from_nodes(siblings) + siblings = {"siblings": siblings_name} + imprint(node, siblings) + + def _imprint_inits(self): + """Add initial positions and dimensions to the attributes""" + + for node in nuke.allNodes(): + refresh_node(node) + imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) + node.knob("x_init").setVisible(False) + node.knob("y_init").setVisible(False) + width = node.screenWidth() + height = node.screenHeight() + if "bdwidth" in node.knobs(): + imprint(node, {"w_init": width, "h_init": height}) + node.knob("w_init").setVisible(False) + node.knob("h_init").setVisible(False) + refresh_node(node) + + def _update_nodes( + self, placeholder, nodes, considered_nodes, offset_y=None + ): + """Adjust backdrop nodes dimensions and positions. + + Considering some nodes sizes. + + Args: + nodes (list): list of nodes to update + considered_nodes (list): list of nodes to consider while updating + positions and dimensions + offset (int): distance between copies + """ + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) + + diff_x = diff_y = 0 + contained_nodes = [] # for backdrops + + if offset_y is None: + width_ph = placeholder_node.screenWidth() + height_ph = placeholder_node.screenHeight() + diff_y = max_y - min_y - height_ph + diff_x = max_x - min_x - width_ph + contained_nodes = [placeholder_node] + min_x = placeholder_node.xpos() + min_y = placeholder_node.ypos() + else: + siblings = get_nodes_by_names(placeholder.data["siblings"]) + minX, _, maxX, _ = get_extreme_positions(siblings) + diff_y = max_y - min_y + 20 + diff_x = abs(max_x - min_x - maxX + minX) + contained_nodes = considered_nodes + + if diff_y <= 0 and diff_x <= 0: + return + + for node in nodes: + refresh_node(node) + + if ( + node == placeholder_node + or node in considered_nodes + ): + continue + + if ( + not isinstance(node, nuke.BackdropNode) + or ( + isinstance(node, nuke.BackdropNode) + and not set(contained_nodes) <= set(node.getNodes()) + ) + ): + if offset_y is None and node.xpos() >= min_x: + node.setXpos(node.xpos() + diff_x) + + if node.ypos() >= min_y: + node.setYpos(node.ypos() + diff_y) + + else: + width = node.screenWidth() + height = node.screenHeight() + node.knob("bdwidth").setValue(width + diff_x) + node.knob("bdheight").setValue(height + diff_y) + + refresh_node(node) + + def _set_created_connections(self, placeholder): + """ + set inputs and outputs of created nodes""" + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + input_node, output_node = get_group_io_nodes( + placeholder.data["last_created"] + ) + for node in placeholder_node.dependent(): + for idx in range(node.inputs()): + if node.input(idx) == placeholder_node and output_node: + node.setInput(idx, output_node) + + for node in placeholder_node.dependencies(): + for idx in range(placeholder_node.inputs()): + if placeholder_node.input(idx) == node and input_node: + input_node.setInput(0, node) + + def _create_sib_copies(self, placeholder): + """ creating copies of the palce_holder siblings (the ones who were + created with it) for the new nodes added + + Returns : + copies (dict) : with copied nodes names and their copies + """ + + copies = {} + siblings = get_nodes_by_names(placeholder.data["siblings"]) + for node in siblings: + new_node = duplicate_node(node) + + x_init = int(new_node.knob("x_init").getValue()) + y_init = int(new_node.knob("y_init").getValue()) + new_node.setXYpos(x_init, y_init) + if isinstance(new_node, nuke.BackdropNode): + w_init = new_node.knob("w_init").getValue() + h_init = new_node.knob("h_init").getValue() + new_node.knob("bdwidth").setValue(w_init) + new_node.knob("bdheight").setValue(h_init) + refresh_node(node) + + if "repre_id" in node.knobs().keys(): + node.removeKnob(node.knob("repre_id")) + copies[node.name()] = new_node + return copies + + def _set_copies_connections(self, placeholder, copies): + """Set inputs and outputs of the copies. + + Args: + copies (dict): Copied nodes by their names. + """ + + last_input, last_output = get_group_io_nodes( + placeholder.data["last_created"] + ) + siblings = get_nodes_by_names(placeholder.data["siblings"]) + siblings_input, siblings_output = get_group_io_nodes(siblings) + copy_input = copies[siblings_input.name()] + copy_output = copies[siblings_output.name()] + + for node_init in siblings: + if node_init == siblings_output: + continue + + node_copy = copies[node_init.name()] + for node in node_init.dependent(): + for idx in range(node.inputs()): + if node.input(idx) != node_init: + continue + + if node in siblings: + copies[node.name()].setInput(idx, node_copy) + else: + last_input.setInput(0, node_copy) + + for node in node_init.dependencies(): + for idx in range(node_init.inputs()): + if node_init.input(idx) != node: + continue + + if node_init == siblings_input: + copy_input.setInput(idx, node) + elif node in siblings: + node_copy.setInput(idx, copies[node.name()]) + else: + node_copy.setInput(idx, last_output) + + siblings_input.setInput(0, copy_output) diff --git a/client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py b/client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py new file mode 100644 index 0000000000..258f48c9d3 --- /dev/null +++ b/client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py @@ -0,0 +1,455 @@ +import nuke + +from ayon_core.pipeline.workfile.workfile_template_builder import ( + LoadPlaceholderItem, + PlaceholderLoadMixin, +) +from ayon_core.hosts.nuke.api.lib import ( + find_free_space_to_paste_nodes, + get_extreme_positions, + get_group_io_nodes, + imprint, + refresh_node, + refresh_nodes, + reset_selection, + get_names_from_nodes, + get_nodes_by_names, + select_nodes, + duplicate_node, + node_tempfile, +) +from ayon_core.hosts.nuke.api.workfile_template_builder import ( + NukePlaceholderPlugin +) + + +class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin): + identifier = "nuke.load" + label = "Nuke load" + + def _parse_placeholder_node_data(self, node): + placeholder_data = super( + NukePlaceholderLoadPlugin, self + )._parse_placeholder_node_data(node) + + node_knobs = node.knobs() + nb_children = 0 + if "nb_children" in node_knobs: + nb_children = int(node_knobs["nb_children"].getValue()) + placeholder_data["nb_children"] = nb_children + + siblings = [] + if "siblings" in node_knobs: + siblings = node_knobs["siblings"].values() + placeholder_data["siblings"] = siblings + + node_full_name = node.fullName() + placeholder_data["group_name"] = node_full_name.rpartition(".")[0] + placeholder_data["last_loaded"] = [] + placeholder_data["delete"] = False + return placeholder_data + + def _get_loaded_repre_ids(self): + loaded_representation_ids = self.builder.get_shared_populate_data( + "loaded_representation_ids" + ) + if loaded_representation_ids is None: + loaded_representation_ids = set() + for node in nuke.allNodes(): + if "repre_id" in node.knobs(): + loaded_representation_ids.add( + node.knob("repre_id").getValue() + ) + + self.builder.set_shared_populate_data( + "loaded_representation_ids", loaded_representation_ids + ) + return loaded_representation_ids + + def _before_placeholder_load(self, placeholder): + placeholder.data["nodes_init"] = nuke.allNodes() + + def _before_repre_load(self, placeholder, representation): + placeholder.data["last_repre_id"] = representation["id"] + + def collect_placeholders(self): + output = [] + scene_placeholders = self._collect_scene_placeholders() + for node_name, node in scene_placeholders.items(): + plugin_identifier_knob = node.knob("plugin_identifier") + if ( + plugin_identifier_knob is None + or plugin_identifier_knob.getValue() != self.identifier + ): + continue + + placeholder_data = self._parse_placeholder_node_data(node) + # TODO do data validations and maybe updgrades if are invalid + output.append( + LoadPlaceholderItem(node_name, placeholder_data, self) + ) + + return output + + def populate_placeholder(self, placeholder): + self.populate_load_placeholder(placeholder) + + def repopulate_placeholder(self, placeholder): + repre_ids = self._get_loaded_repre_ids() + self.populate_load_placeholder(placeholder, repre_ids) + + def get_placeholder_options(self, options=None): + return self.get_load_plugin_options(options) + + def post_placeholder_process(self, placeholder, failed): + """Cleanup placeholder after load of its corresponding representations. + + Args: + placeholder (PlaceholderItem): Item which was just used to load + representation. + failed (bool): Loading of representation failed. + """ + # deselect all selected nodes + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + # getting the latest nodes added + # TODO get from shared populate data! + nodes_init = placeholder.data["nodes_init"] + nodes_loaded = list(set(nuke.allNodes()) - set(nodes_init)) + self.log.debug("Loaded nodes: {}".format(nodes_loaded)) + if not nodes_loaded: + return + + placeholder.data["delete"] = True + + nodes_loaded = self._move_to_placeholder_group( + placeholder, nodes_loaded + ) + placeholder.data["last_loaded"] = nodes_loaded + refresh_nodes(nodes_loaded) + + # positioning of the loaded nodes + min_x, min_y, _, _ = get_extreme_positions(nodes_loaded) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + placeholder_node.xpos() + ypos = (node.ypos() - min_y) + placeholder_node.ypos() + node.setXYpos(xpos, ypos) + refresh_nodes(nodes_loaded) + + # fix the problem of z_order for backdrops + self._fix_z_order(placeholder) + + if placeholder.data.get("keep_placeholder"): + self._imprint_siblings(placeholder) + + if placeholder.data["nb_children"] == 0: + # save initial nodes positions and dimensions, update them + # and set inputs and outputs of loaded nodes + if placeholder.data.get("keep_placeholder"): + self._imprint_inits() + self._update_nodes(placeholder, nuke.allNodes(), nodes_loaded) + + self._set_loaded_connections(placeholder) + + elif placeholder.data["siblings"]: + # create copies of placeholder siblings for the new loaded nodes, + # set their inputs and outputs and update all nodes positions and + # dimensions and siblings names + + siblings = get_nodes_by_names(placeholder.data["siblings"]) + refresh_nodes(siblings) + copies = self._create_sib_copies(placeholder) + new_nodes = list(copies.values()) # copies nodes + self._update_nodes(new_nodes, nodes_loaded) + placeholder_node.removeKnob(placeholder_node.knob("siblings")) + new_nodes_name = get_names_from_nodes(new_nodes) + imprint(placeholder_node, {"siblings": new_nodes_name}) + self._set_copies_connections(placeholder, copies) + + self._update_nodes( + nuke.allNodes(), + new_nodes + nodes_loaded, + 20 + ) + + new_siblings = get_names_from_nodes(new_nodes) + placeholder.data["siblings"] = new_siblings + + else: + # if the placeholder doesn't have siblings, the loaded + # nodes will be placed in a free space + + xpointer, ypointer = find_free_space_to_paste_nodes( + nodes_loaded, direction="bottom", offset=200 + ) + node = nuke.createNode("NoOp") + reset_selection() + nuke.delete(node) + for node in nodes_loaded: + xpos = (node.xpos() - min_x) + xpointer + ypos = (node.ypos() - min_y) + ypointer + node.setXYpos(xpos, ypos) + + placeholder.data["nb_children"] += 1 + reset_selection() + + # go back to root group + nuke.root().begin() + + def _move_to_placeholder_group(self, placeholder, nodes_loaded): + """ + opening the placeholder's group and copying loaded nodes in it. + + Returns : + nodes_loaded (list): the new list of pasted nodes + """ + + groups_name = placeholder.data["group_name"] + reset_selection() + select_nodes(nodes_loaded) + if groups_name: + with node_tempfile() as filepath: + nuke.nodeCopy(filepath) + for node in nuke.selectedNodes(): + nuke.delete(node) + group = nuke.toNode(groups_name) + group.begin() + nuke.nodePaste(filepath) + nodes_loaded = nuke.selectedNodes() + return nodes_loaded + + def _fix_z_order(self, placeholder): + """Fix the problem of z_order when a backdrop is loaded.""" + + nodes_loaded = placeholder.data["last_loaded"] + loaded_backdrops = [] + bd_orders = set() + for node in nodes_loaded: + if isinstance(node, nuke.BackdropNode): + loaded_backdrops.append(node) + bd_orders.add(node.knob("z_order").getValue()) + + if not bd_orders: + return + + sib_orders = set() + for node_name in placeholder.data["siblings"]: + node = nuke.toNode(node_name) + if isinstance(node, nuke.BackdropNode): + sib_orders.add(node.knob("z_order").getValue()) + + if not sib_orders: + return + + min_order = min(bd_orders) + max_order = max(sib_orders) + for backdrop_node in loaded_backdrops: + z_order = backdrop_node.knob("z_order").getValue() + backdrop_node.knob("z_order").setValue( + z_order + max_order - min_order + 1) + + def _imprint_siblings(self, placeholder): + """ + - add siblings names to placeholder attributes (nodes loaded with it) + - add Id to the attributes of all the other nodes + """ + + loaded_nodes = placeholder.data["last_loaded"] + loaded_nodes_set = set(loaded_nodes) + data = {"repre_id": str(placeholder.data["last_repre_id"])} + + for node in loaded_nodes: + node_knobs = node.knobs() + if "builder_type" not in node_knobs: + # save the id of representation for all imported nodes + imprint(node, data) + node.knob("repre_id").setVisible(False) + refresh_node(node) + continue + + if ( + "is_placeholder" not in node_knobs + or ( + "is_placeholder" in node_knobs + and node.knob("is_placeholder").value() + ) + ): + siblings = list(loaded_nodes_set - {node}) + siblings_name = get_names_from_nodes(siblings) + siblings = {"siblings": siblings_name} + imprint(node, siblings) + + def _imprint_inits(self): + """Add initial positions and dimensions to the attributes""" + + for node in nuke.allNodes(): + refresh_node(node) + imprint(node, {"x_init": node.xpos(), "y_init": node.ypos()}) + node.knob("x_init").setVisible(False) + node.knob("y_init").setVisible(False) + width = node.screenWidth() + height = node.screenHeight() + if "bdwidth" in node.knobs(): + imprint(node, {"w_init": width, "h_init": height}) + node.knob("w_init").setVisible(False) + node.knob("h_init").setVisible(False) + refresh_node(node) + + def _update_nodes( + self, placeholder, nodes, considered_nodes, offset_y=None + ): + """Adjust backdrop nodes dimensions and positions. + + Considering some nodes sizes. + + Args: + nodes (list): list of nodes to update + considered_nodes (list): list of nodes to consider while updating + positions and dimensions + offset (int): distance between copies + """ + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + + min_x, min_y, max_x, max_y = get_extreme_positions(considered_nodes) + + diff_x = diff_y = 0 + contained_nodes = [] # for backdrops + + if offset_y is None: + width_ph = placeholder_node.screenWidth() + height_ph = placeholder_node.screenHeight() + diff_y = max_y - min_y - height_ph + diff_x = max_x - min_x - width_ph + contained_nodes = [placeholder_node] + min_x = placeholder_node.xpos() + min_y = placeholder_node.ypos() + else: + siblings = get_nodes_by_names(placeholder.data["siblings"]) + minX, _, maxX, _ = get_extreme_positions(siblings) + diff_y = max_y - min_y + 20 + diff_x = abs(max_x - min_x - maxX + minX) + contained_nodes = considered_nodes + + if diff_y <= 0 and diff_x <= 0: + return + + for node in nodes: + refresh_node(node) + + if ( + node == placeholder_node + or node in considered_nodes + ): + continue + + if ( + not isinstance(node, nuke.BackdropNode) + or ( + isinstance(node, nuke.BackdropNode) + and not set(contained_nodes) <= set(node.getNodes()) + ) + ): + if offset_y is None and node.xpos() >= min_x: + node.setXpos(node.xpos() + diff_x) + + if node.ypos() >= min_y: + node.setYpos(node.ypos() + diff_y) + + else: + width = node.screenWidth() + height = node.screenHeight() + node.knob("bdwidth").setValue(width + diff_x) + node.knob("bdheight").setValue(height + diff_y) + + refresh_node(node) + + def _set_loaded_connections(self, placeholder): + """ + set inputs and outputs of loaded nodes""" + + placeholder_node = nuke.toNode(placeholder.scene_identifier) + input_node, output_node = get_group_io_nodes( + placeholder.data["last_loaded"] + ) + for node in placeholder_node.dependent(): + for idx in range(node.inputs()): + if node.input(idx) == placeholder_node and output_node: + node.setInput(idx, output_node) + + for node in placeholder_node.dependencies(): + for idx in range(placeholder_node.inputs()): + if placeholder_node.input(idx) == node and input_node: + input_node.setInput(0, node) + + def _create_sib_copies(self, placeholder): + """ creating copies of the palce_holder siblings (the ones who were + loaded with it) for the new nodes added + + Returns : + copies (dict) : with copied nodes names and their copies + """ + + copies = {} + siblings = get_nodes_by_names(placeholder.data["siblings"]) + for node in siblings: + new_node = duplicate_node(node) + + x_init = int(new_node.knob("x_init").getValue()) + y_init = int(new_node.knob("y_init").getValue()) + new_node.setXYpos(x_init, y_init) + if isinstance(new_node, nuke.BackdropNode): + w_init = new_node.knob("w_init").getValue() + h_init = new_node.knob("h_init").getValue() + new_node.knob("bdwidth").setValue(w_init) + new_node.knob("bdheight").setValue(h_init) + refresh_node(node) + + if "repre_id" in node.knobs().keys(): + node.removeKnob(node.knob("repre_id")) + copies[node.name()] = new_node + return copies + + def _set_copies_connections(self, placeholder, copies): + """Set inputs and outputs of the copies. + + Args: + copies (dict): Copied nodes by their names. + """ + + last_input, last_output = get_group_io_nodes( + placeholder.data["last_loaded"] + ) + siblings = get_nodes_by_names(placeholder.data["siblings"]) + siblings_input, siblings_output = get_group_io_nodes(siblings) + copy_input = copies[siblings_input.name()] + copy_output = copies[siblings_output.name()] + + for node_init in siblings: + if node_init == siblings_output: + continue + + node_copy = copies[node_init.name()] + for node in node_init.dependent(): + for idx in range(node.inputs()): + if node.input(idx) != node_init: + continue + + if node in siblings: + copies[node.name()].setInput(idx, node_copy) + else: + last_input.setInput(0, node_copy) + + for node in node_init.dependencies(): + for idx in range(node_init.inputs()): + if node_init.input(idx) != node: + continue + + if node_init == siblings_input: + copy_input.setInput(idx, node) + elif node in siblings: + node_copy.setInput(idx, copies[node.name()]) + else: + node_copy.setInput(idx, last_output) + + siblings_input.setInput(0, copy_output) diff --git a/client/ayon_core/pipeline/__init__.py b/client/ayon_core/pipeline/__init__.py index d1a181a353..3102ce1da3 100644 --- a/client/ayon_core/pipeline/__init__.py +++ b/client/ayon_core/pipeline/__init__.py @@ -97,6 +97,15 @@ from .context_tools import ( get_current_folder_path, get_current_task_name ) + +from .workfile import ( + discover_template_placeholder_plugins, + register_template_placeholder_plugin, + deregister_template_placeholder_plugin, + register_template_placeholder_plugin_path, + deregister_template_placeholder_plugin_path, +) + install = install_host uninstall = uninstall_host @@ -198,6 +207,13 @@ __all__ = ( "get_current_folder_path", "get_current_task_name", + # Workfile templates + "discover_template_placeholder_plugins", + "register_template_placeholder_plugin", + "deregister_template_placeholder_plugin", + "register_template_placeholder_plugin_path", + "deregister_template_placeholder_plugin_path", + # Backwards compatible function names "install", "uninstall", diff --git a/client/ayon_core/pipeline/workfile/__init__.py b/client/ayon_core/pipeline/workfile/__init__.py index 36766e3a04..149036117a 100644 --- a/client/ayon_core/pipeline/workfile/__init__.py +++ b/client/ayon_core/pipeline/workfile/__init__.py @@ -21,6 +21,15 @@ from .utils import ( from .build_workfile import BuildWorkfile +from .workfile_template_builder import ( + discover_template_placeholder_plugins, + register_template_placeholder_plugin, + deregister_template_placeholder_plugin, + register_template_placeholder_plugin_path, + deregister_template_placeholder_plugin_path, +) + + __all__ = ( "get_workfile_template_key_from_context", "get_workfile_template_key", @@ -39,4 +48,10 @@ __all__ = ( "should_open_workfiles_tool_on_launch", "BuildWorkfile", + + "discover_template_placeholder_plugins", + "register_template_placeholder_plugin", + "deregister_template_placeholder_plugin", + "register_template_placeholder_plugin_path", + "deregister_template_placeholder_plugin_path", ) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..4dad7ae17f 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -43,6 +43,13 @@ from ayon_core.pipeline.load import ( get_representation_contexts, load_with_repre_context, ) +from ayon_core.pipeline.plugin_discover import ( + discover, + register_plugin, + register_plugin_path, + deregister_plugin, + deregister_plugin_path +) from ayon_core.pipeline.create import ( discover_legacy_creator_plugins, @@ -211,10 +218,14 @@ class AbstractTemplateBuilder(object): Returns: List[PlaceholderPlugin]: Plugin classes available for host. """ + plugins = [] + # Backwards compatibility if hasattr(self._host, "get_workfile_build_placeholder_plugins"): return self._host.get_workfile_build_placeholder_plugins() - return [] + + plugins.extend(discover(PlaceholderPlugin)) + return plugins @property def host(self): @@ -1918,3 +1929,23 @@ class CreatePlaceholderItem(PlaceholderItem): def create_failed(self, creator_data): self._failed_created_publish_instances.append(creator_data) + + +def discover_template_placeholder_plugins(*args, **kwargs): + return discover(PlaceholderPlugin, *args, **kwargs) + + +def register_template_placeholder_plugin(plugin: PlaceholderPlugin): + register_plugin(PlaceholderPlugin, plugin) + + +def deregister_template_placeholder_plugin(plugin: PlaceholderPlugin): + deregister_plugin(PlaceholderPlugin, plugin) + + +def register_template_placeholder_plugin_path(path: str): + register_plugin_path(PlaceholderPlugin, path) + + +def deregister_template_placeholder_plugin_path(path: str): + deregister_plugin_path(PlaceholderPlugin, path) From 269d395141306c3173f316b172d160f8d43d041a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 23:42:30 +0200 Subject: [PATCH 186/244] Fix ayon core refactor bug, similar to fix in #330 --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 4dad7ae17f..0434c44cb3 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1479,7 +1479,9 @@ class PlaceholderLoadMixin(object): product_name_regex = None if product_name_regex_value: product_name_regex = re.compile(product_name_regex_value) - product_type = placeholder.data["family"] + product_type = placeholder.data.get("product_type") + if product_type is None: + product_type = placeholder.data["family"] builder_type = placeholder.data["builder_type"] folder_ids = [] From d7b20dff37693492a16e6742d7e679808c4fe7a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 23:53:20 +0200 Subject: [PATCH 187/244] Workfile templates: add event system to Workfile Template Builder --- .../workfile/workfile_template_builder.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..f53aee6341 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -36,6 +36,7 @@ from ayon_core.lib import ( filter_profiles, attribute_definitions, ) +from ayon_core.lib.events import EventSystem from ayon_core.lib.attribute_definitions import get_attributes_keys from ayon_core.pipeline import Anatomy from ayon_core.pipeline.load import ( @@ -124,6 +125,8 @@ class AbstractTemplateBuilder(object): self._current_task_entity = _NOT_SET self._linked_folder_entities = _NOT_SET + self._event_system = EventSystem() + @property def project_name(self): if isinstance(self._host, HostBase): @@ -244,6 +247,14 @@ class AbstractTemplateBuilder(object): self._log = Logger.get_logger(repr(self)) return self._log + @property + def event_system(self): + """Event System of the Workfile templatee builder. + Returns: + EventSystem: The event system. + """ + return self._event_system + def refresh(self): """Reset cached data.""" @@ -257,6 +268,8 @@ class AbstractTemplateBuilder(object): self._project_settings = None + self._event_system = EventSystem() + self.clear_shared_data() self.clear_shared_populate_data() @@ -729,6 +742,16 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() + # Trigger on_depth_processed event + self.event_system.emit( + topic="template.depth_processed", + data={ + "depth": iter_counter, + "placeholders_by_scene_id": placeholder_by_scene_id + }, + source="builder" + ) + # Clear shared data before getting new placeholders self.clear_shared_populate_data() @@ -747,6 +770,16 @@ class AbstractTemplateBuilder(object): placeholder_by_scene_id[identifier] = placeholder placeholders.append(placeholder) + # Trigger on_finished event + self.event_system.emit( + topic="template.finished", + data={ + "depth": iter_counter, + "placeholders_by_scene_id": placeholder_by_scene_id, + }, + source="builder" + ) + self.refresh() def _get_build_profiles(self): @@ -1102,6 +1135,41 @@ class PlaceholderPlugin(object): plugin_data[key] = value self.builder.set_shared_populate_data(self.identifier, plugin_data) + def register_on_finished_callback( + self, placeholder, callback, order=None + ): + self.register_callback( + placeholder, + topic="template.finished", + callback=callback, + order=order + ) + + def register_on_depth_processed_callback( + self, placeholder, callback, order=0 + ): + self.register_callback( + placeholder, + topic="template.depth_processed", + callback=callback, + order=order + ) + + def register_callback(self, placeholder, topic, callback, order=None): + + if order is None: + # Match placeholder order by default + order = placeholder.order + + # We must persist the callback over time otherwise it will be removed + # by the event system as a valid function reference. We do that here + # always just so it's easier to develop plugins where callbacks might + # be partials or lambdas + placeholder.data.setdefault("callbacks", []).append(callback) + self.log.debug("Registering '%s' callback: %s", topic, callback) + self.builder.event_system.add_callback(topic, callback, order=order) + + class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. From edee279f15fa16b549db68e3adb6aba14fc2f541 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 12:04:24 +0200 Subject: [PATCH 188/244] Do not store in placeholder data - it's up to the registering code itself to persist or use the `weakref_partial` implementation from the event system --- .../pipeline/workfile/workfile_template_builder.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index f53aee6341..d08c951b36 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1161,16 +1161,10 @@ class PlaceholderPlugin(object): # Match placeholder order by default order = placeholder.order - # We must persist the callback over time otherwise it will be removed - # by the event system as a valid function reference. We do that here - # always just so it's easier to develop plugins where callbacks might - # be partials or lambdas - placeholder.data.setdefault("callbacks", []).append(callback) self.log.debug("Registering '%s' callback: %s", topic, callback) self.builder.event_system.add_callback(topic, callback, order=order) - class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. From 2befe843dc6fe9168b156ccf9501c99ddd48fe9e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 12:05:06 +0200 Subject: [PATCH 189/244] Do not force order of the placeholder, allow it to be `None` --- .../pipeline/workfile/workfile_template_builder.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index d08c951b36..b27b614579 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1146,7 +1146,7 @@ class PlaceholderPlugin(object): ) def register_on_depth_processed_callback( - self, placeholder, callback, order=0 + self, placeholder, callback, order=None ): self.register_callback( placeholder, @@ -1156,11 +1156,6 @@ class PlaceholderPlugin(object): ) def register_callback(self, placeholder, topic, callback, order=None): - - if order is None: - # Match placeholder order by default - order = placeholder.order - self.log.debug("Registering '%s' callback: %s", topic, callback) self.builder.event_system.add_callback(topic, callback, order=order) From 47c7e8634c9deafe307e25cfe4bb654851fbbb63 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 12:10:45 +0200 Subject: [PATCH 190/244] Do not expose the `event_system` on the builder directly - but expose the register and trigger event methods --- .../workfile/workfile_template_builder.py | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index b27b614579..22a4c984bc 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -247,14 +247,6 @@ class AbstractTemplateBuilder(object): self._log = Logger.get_logger(repr(self)) return self._log - @property - def event_system(self): - """Event System of the Workfile templatee builder. - Returns: - EventSystem: The event system. - """ - return self._event_system - def refresh(self): """Reset cached data.""" @@ -743,7 +735,7 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() # Trigger on_depth_processed event - self.event_system.emit( + self.trigger_event( topic="template.depth_processed", data={ "depth": iter_counter, @@ -771,7 +763,7 @@ class AbstractTemplateBuilder(object): placeholders.append(placeholder) # Trigger on_finished event - self.event_system.emit( + self.trigger_event( topic="template.finished", data={ "depth": iter_counter, @@ -905,6 +897,30 @@ class AbstractTemplateBuilder(object): "create_first_version": create_first_version } + def trigger_event(self, topic, data=None, source=None): + self._event_system.emit(topic, data, source) + + def register_event_callback(self, topic, callback, order=None): + self._event_system.add_callback(topic, callback, order=order) + + def register_on_finished_callback( + self, callback, order=None + ): + self.register_event_callback( + topic="template.finished", + callback=callback, + order=order + ) + + def register_on_depth_processed_callback( + self, callback, order=None + ): + self.register_event_callback( + topic="template.depth_processed", + callback=callback, + order=order + ) + @six.add_metaclass(ABCMeta) class PlaceholderPlugin(object): @@ -1135,30 +1151,6 @@ class PlaceholderPlugin(object): plugin_data[key] = value self.builder.set_shared_populate_data(self.identifier, plugin_data) - def register_on_finished_callback( - self, placeholder, callback, order=None - ): - self.register_callback( - placeholder, - topic="template.finished", - callback=callback, - order=order - ) - - def register_on_depth_processed_callback( - self, placeholder, callback, order=None - ): - self.register_callback( - placeholder, - topic="template.depth_processed", - callback=callback, - order=order - ) - - def register_callback(self, placeholder, topic, callback, order=None): - self.log.debug("Registering '%s' callback: %s", topic, callback) - self.builder.event_system.add_callback(topic, callback, order=order) - class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. From da5abf836773283ccb4d1296a35258cc9106e443 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:24:44 +0200 Subject: [PATCH 191/244] Update client/ayon_core/pipeline/workfile/workfile_template_builder.py --- .../pipeline/workfile/workfile_template_builder.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 22a4c984bc..a4fa2b4ddd 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -897,25 +897,25 @@ class AbstractTemplateBuilder(object): "create_first_version": create_first_version } - def trigger_event(self, topic, data=None, source=None): + def emit_event(self, topic, data=None, source=None): self._event_system.emit(topic, data, source) - def register_event_callback(self, topic, callback, order=None): + def add_event_callback(self, topic, callback, order=None): self._event_system.add_callback(topic, callback, order=order) - def register_on_finished_callback( + def add_on_finished_callback( self, callback, order=None ): - self.register_event_callback( + self.add_event_callback( topic="template.finished", callback=callback, order=order ) - def register_on_depth_processed_callback( + def add_on_depth_processed_callback( self, callback, order=None ): - self.register_event_callback( + self.add_event_callback( topic="template.depth_processed", callback=callback, order=order From 8194a7b07b1799ffafe960b25e0d86c2feaff258 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:26:54 +0200 Subject: [PATCH 192/244] Return the values of the called functions --- .../workfile/workfile_template_builder.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index a4fa2b4ddd..d189e3e2d6 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -36,7 +36,7 @@ from ayon_core.lib import ( filter_profiles, attribute_definitions, ) -from ayon_core.lib.events import EventSystem +from ayon_core.lib.events import EventSystem, EventCallback, Event from ayon_core.lib.attribute_definitions import get_attributes_keys from ayon_core.pipeline import Anatomy from ayon_core.pipeline.load import ( @@ -897,16 +897,16 @@ class AbstractTemplateBuilder(object): "create_first_version": create_first_version } - def emit_event(self, topic, data=None, source=None): - self._event_system.emit(topic, data, source) + def emit_event(self, topic, data=None, source=None) -> Event: + return self._event_system.emit(topic, data, source) def add_event_callback(self, topic, callback, order=None): - self._event_system.add_callback(topic, callback, order=order) + return self._event_system.add_callback(topic, callback, order=order) def add_on_finished_callback( self, callback, order=None - ): - self.add_event_callback( + ) -> EventCallback: + return self.add_event_callback( topic="template.finished", callback=callback, order=order @@ -914,8 +914,8 @@ class AbstractTemplateBuilder(object): def add_on_depth_processed_callback( self, callback, order=None - ): - self.add_event_callback( + ) -> EventCallback: + return self.add_event_callback( topic="template.depth_processed", callback=callback, order=order From c6e61028976c2fc1410cb3bac34e9f17ed51fe83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:30:18 +0200 Subject: [PATCH 193/244] Refactor `TEMPLATE_PLUGINS_PATH` -> `WORKFILE_BUILD_PLUGIN_PATH` Refactor plugin folders `template/` to `workfile_build/` --- client/ayon_core/hosts/aftereffects/api/pipeline.py | 4 ++-- .../{template => workfile_build}/create_placeholder.py | 0 .../{template => workfile_build}/load_placeholder.py | 0 client/ayon_core/hosts/maya/api/pipeline.py | 6 +++--- .../{template => workfile_build}/load_placeholder.py | 0 client/ayon_core/hosts/nuke/api/pipeline.py | 4 ++-- .../{template => workfile_build}/create_placeholder.py | 0 .../{template => workfile_build}/load_placeholder.py | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename client/ayon_core/hosts/aftereffects/plugins/{template => workfile_build}/create_placeholder.py (100%) rename client/ayon_core/hosts/aftereffects/plugins/{template => workfile_build}/load_placeholder.py (100%) rename client/ayon_core/hosts/maya/plugins/{template => workfile_build}/load_placeholder.py (100%) rename client/ayon_core/hosts/nuke/plugins/{template => workfile_build}/create_placeholder.py (100%) rename client/ayon_core/hosts/nuke/plugins/{template => workfile_build}/load_placeholder.py (100%) diff --git a/client/ayon_core/hosts/aftereffects/api/pipeline.py b/client/ayon_core/hosts/aftereffects/api/pipeline.py index 214986a2fc..754f952fb4 100644 --- a/client/ayon_core/hosts/aftereffects/api/pipeline.py +++ b/client/ayon_core/hosts/aftereffects/api/pipeline.py @@ -37,7 +37,7 @@ PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") +WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): @@ -74,7 +74,7 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) - register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) register_event_callback("application.launched", application_launch) diff --git a/client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py b/client/ayon_core/hosts/aftereffects/plugins/workfile_build/create_placeholder.py similarity index 100% rename from client/ayon_core/hosts/aftereffects/plugins/template/create_placeholder.py rename to client/ayon_core/hosts/aftereffects/plugins/workfile_build/create_placeholder.py diff --git a/client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py b/client/ayon_core/hosts/aftereffects/plugins/workfile_build/load_placeholder.py similarity index 100% rename from client/ayon_core/hosts/aftereffects/plugins/template/load_placeholder.py rename to client/ayon_core/hosts/aftereffects/plugins/workfile_build/load_placeholder.py diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index eca98fa306..4b993b6fbd 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -65,7 +65,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") +WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") AVALON_CONTAINERS = ":AVALON_CONTAINERS" @@ -95,7 +95,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) self.log.info("Installing callbacks ... ") register_event_callback("init", on_init) @@ -335,7 +335,7 @@ def uninstall(): deregister_loader_plugin_path(LOAD_PATH) deregister_creator_plugin_path(CREATE_PATH) deregister_inventory_action_path(INVENTORY_PATH) - deregister_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) + deregister_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) menu.uninstall() diff --git a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py similarity index 100% rename from client/ayon_core/hosts/maya/plugins/template/load_placeholder.py rename to client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index bdf601e30d..ccb0f12ff9 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -75,7 +75,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -TEMPLATE_PLUGINS_PATH = os.path.join(PLUGINS_DIR, "template") +WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") # registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): @@ -118,7 +118,7 @@ class NukeHost( register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(TEMPLATE_PLUGINS_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) # Register AYON event for workfiles loading. register_event_callback("workio.open_file", check_inventory_versions) diff --git a/client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py b/client/ayon_core/hosts/nuke/plugins/workfile_build/create_placeholder.py similarity index 100% rename from client/ayon_core/hosts/nuke/plugins/template/create_placeholder.py rename to client/ayon_core/hosts/nuke/plugins/workfile_build/create_placeholder.py diff --git a/client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py b/client/ayon_core/hosts/nuke/plugins/workfile_build/load_placeholder.py similarity index 100% rename from client/ayon_core/hosts/nuke/plugins/template/load_placeholder.py rename to client/ayon_core/hosts/nuke/plugins/workfile_build/load_placeholder.py From 57157f9a09dc62c8c6cecc9e5423b739f5310457 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:31:51 +0200 Subject: [PATCH 194/244] Refactor `WORKFILE_BUILD_PLUGIN_PATH` -> `WORKFILE_BUILD_PATH` to match other constants that do not contain `PLUGIN` --- client/ayon_core/hosts/aftereffects/api/pipeline.py | 4 ++-- client/ayon_core/hosts/maya/api/pipeline.py | 6 +++--- client/ayon_core/hosts/nuke/api/pipeline.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/api/pipeline.py b/client/ayon_core/hosts/aftereffects/api/pipeline.py index 754f952fb4..6b213822f3 100644 --- a/client/ayon_core/hosts/aftereffects/api/pipeline.py +++ b/client/ayon_core/hosts/aftereffects/api/pipeline.py @@ -37,7 +37,7 @@ PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") +WORKFILE_BUILD_PATH = os.path.join(PLUGINS_DIR, "workfile_build") class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): @@ -74,7 +74,7 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) register_event_callback("application.launched", application_launch) diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 4b993b6fbd..257c822e0b 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -65,7 +65,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") +WORKFILE_BUILD_PATH = os.path.join(PLUGINS_DIR, "workfile_build") AVALON_CONTAINERS = ":AVALON_CONTAINERS" @@ -95,7 +95,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) self.log.info("Installing callbacks ... ") register_event_callback("init", on_init) @@ -335,7 +335,7 @@ def uninstall(): deregister_loader_plugin_path(LOAD_PATH) deregister_creator_plugin_path(CREATE_PATH) deregister_inventory_action_path(INVENTORY_PATH) - deregister_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) + deregister_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) menu.uninstall() diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index ccb0f12ff9..f5e48eb375 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -75,7 +75,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -WORKFILE_BUILD_PLUGIN_PATH = os.path.join(PLUGINS_DIR, "workfile_build") +WORKFILE_BUILD_PATH = os.path.join(PLUGINS_DIR, "workfile_build") # registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): @@ -118,7 +118,7 @@ class NukeHost( register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PLUGIN_PATH) + register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) # Register AYON event for workfiles loading. register_event_callback("workio.open_file", check_inventory_versions) From 02f0994903f05429a54b7e24953a2710516433d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 14:57:19 +0200 Subject: [PATCH 195/244] :bug: fix support of PySide6 in Unreal 5.4 --- client/ayon_core/hosts/unreal/ue_workers.py | 32 +++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index e3f8729c2e..a987abcc83 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -260,11 +260,11 @@ class UEProjectGenerationWorker(UEWorker): self.failed.emit(msg, return_code) raise RuntimeError(msg) - # ensure we have PySide2 installed in engine + # ensure we have PySide2/6 installed in engine self.progress.emit(0) self.stage_begin.emit( - (f"Checking PySide2 installation... {stage_count} " + (f"Checking Qt bindings installation... {stage_count} " f" out of {stage_count}")) python_path = None if platform.system().lower() == "windows": @@ -287,11 +287,25 @@ class UEProjectGenerationWorker(UEWorker): msg = f"Unreal Python not found at {python_path}" self.failed.emit(msg, 1) raise RuntimeError(msg) - pyside_cmd = [python_path.as_posix(), - "-m", - "pip", - "install", - "pyside2"] + + pyside_version = "PySide2" + ue_version = self.ue_version.split(".") + if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: + pyside_version = "PySide6" + + site_packages_prefix = python_path.parent.as_posix() + + pyside_cmd = [ + python_path.as_posix(), + "-m", "pip", + "install", + "--ignore-installed", + pyside_version, + "--prefix", site_packages_prefix, + ] + + print(f"--- Installing {pyside_version} ...") + print(" ".join(pyside_cmd)) pyside_install = subprocess.Popen(pyside_cmd, stdout=subprocess.PIPE, @@ -306,8 +320,8 @@ class UEProjectGenerationWorker(UEWorker): return_code = pyside_install.wait() if return_code and return_code != 0: - msg = ("Failed to create the project! " - "The installation of PySide2 has failed!") + msg = (f"Failed to create the project! {return_code} " + f"The installation of {pyside_version} has failed!: {pyside_install}") self.failed.emit(msg, return_code) raise RuntimeError(msg) diff --git a/pyproject.toml b/pyproject.toml index c1f6ddfb0b..4726bef41a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput,parms,parm,hda,developpement" +ignore-words-list = "ayon,ynput,parms,parm,hda,developpement,ue" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true From 7c20ec0b564d5d3392dc558b48400144c56be8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 25 Apr 2024 15:31:15 +0200 Subject: [PATCH 196/244] :bug: downgrade PySide6 because of the bug --- client/ayon_core/hosts/unreal/ue_workers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index a987abcc83..cdac2c28af 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -291,7 +291,9 @@ class UEProjectGenerationWorker(UEWorker): pyside_version = "PySide2" ue_version = self.ue_version.split(".") if int(ue_version[0]) == 5 and int(ue_version[1]) >= 4: - pyside_version = "PySide6" + # Use PySide6 6.6.3 because 6.7.0 had a bug + # - 'QPushButton' can't be added to 'QBoxLayout' + pyside_version = "PySide6==6.6.3" site_packages_prefix = python_path.parent.as_posix() From 561021195d2e686a7a1f9667ce0244a0d5a9b969 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 Apr 2024 18:31:58 +0800 Subject: [PATCH 197/244] rename node callback added to detect the renaming of the asset --- client/ayon_core/hosts/max/api/lib.py | 27 ++++++++++++++++++++++ client/ayon_core/hosts/max/api/pipeline.py | 2 ++ 2 files changed, 29 insertions(+) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 02b099b3ff..974b483eac 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -519,6 +519,33 @@ def get_plugins() -> list: return plugin_info_list +def update_modifier_node_names(event, node): + """Update the name of the nodes after renaming + + Args: + event (pymxs.MXSWrapperBase): Event Name ( + Mandatory argument for rt.NodeEventCallback) + node (list): Event Number ( + Mandatory argument for rt.NodeEventCallback) + + """ + containers = [ + obj for obj in rt.Objects + if rt.ClassOf(obj) == rt.Container and + rt.getUserProp(obj, "id") == "pyblish.avalon.instance" + and rt.getUserProp( + obj, "productType") not in {"workfile", "tyflow"} + ] + if not containers: + return + for container in containers: + ayon_data = container.modifiers[0] + updated_node_names = [str(node.node) for node + in ayon_data.openPypeData.all_handles] + rt.setProperty( + ayon_data.openPypeData, "sel_list", updated_node_names) + + @contextlib.contextmanager def render_resolution(width, height): """Set render resolution option during context diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index 675f36c24f..dc13f47795 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -63,6 +63,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('postWorkspaceChange'), self._deferred_menu_creation) + rt.NodeEventCallback( + nameChanged=lib.update_modifier_node_names) def workfile_has_unsaved_changes(self): return rt.getSaveRequired() From 43d2a78170057f22284e67080f48f4553a19d360 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 Apr 2024 20:41:30 +0800 Subject: [PATCH 198/244] clean up the code --- client/ayon_core/hosts/max/api/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 974b483eac..a6f1c2d2de 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -539,11 +539,10 @@ def update_modifier_node_names(event, node): if not containers: return for container in containers: - ayon_data = container.modifiers[0] + ayon_data = container.modifiers[0].openPypeData updated_node_names = [str(node.node) for node - in ayon_data.openPypeData.all_handles] - rt.setProperty( - ayon_data.openPypeData, "sel_list", updated_node_names) + in ayon_data.all_handles] + rt.setProperty(ayon_data, "sel_list", updated_node_names) @contextlib.contextmanager From bff416ecad14863b4d04a01fb1cb7e4580010468 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 26 Apr 2024 17:13:45 +0300 Subject: [PATCH 199/244] Do nothing if workfile color settings don't exist - add a note about it inside the hook --- .../hosts/houdini/hooks/set_default_display_and_view.py | 7 ++++++- .../hosts/houdini/plugins/create/create_review.py | 4 ++-- .../houdini/plugins/publish/validate_review_colorspace.py | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py index 2e97c06bff..31bb5c1c5d 100644 --- a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py +++ b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py @@ -25,7 +25,12 @@ class SetDefaultDisplayView(PreLaunchHook): return houdini_color_settings = \ - self.data["project_settings"]["houdini"]["imageio"]["workfile"] + self.data["project_settings"]["houdini"]["imageio"].get("workfile", {}) + + if not houdini_color_settings: + self.log.info("Hook 'SetDefaultDisplayView' requires Houdini " + "addon version >= '0.2.13'") + return if not houdini_color_settings["enabled"]: self.log.info( diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_review.py b/client/ayon_core/hosts/houdini/plugins/create/create_review.py index 94dcf23181..4a00ed4d37 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_review.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_review.py @@ -18,8 +18,8 @@ class CreateReview(plugin.HoudiniCreator): def apply_settings(self, project_settings): super(CreateReview, self).apply_settings(project_settings) - color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: + color_settings = project_settings["houdini"]["imageio"].get("workfile", {}) + if color_settings and color_settings["enabled"]: self.review_color_space = color_settings.get("review_color_space") def create(self, product_name, instance_data, pre_create_data): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py index d3afa83b67..3b70aea894 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -46,8 +46,8 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, apply_plugin_settings_automatically(cls, settings, logger=cls.log) # Add review color settings - color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: + color_settings = project_settings["houdini"]["imageio"].get("workfile", {}) + if color_settings and color_settings["enabled"]: cls.review_color_space = color_settings.get("review_color_space") From 5b70bcd15955dfc8953e450612ff2d1b9b327e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Sat, 27 Apr 2024 00:03:08 +0200 Subject: [PATCH 200/244] Add `task` to skeleton instance passed to Deadline --- client/ayon_core/pipeline/farm/pyblish_functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index eb6f8569d9..72deee185e 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -225,6 +225,7 @@ def create_skeleton_instance( instance_skeleton_data = { "productType": product_type, "productName": data["productName"], + "task": data["task"], "families": families, "folderPath": data["folderPath"], "frameStart": time_data.start, From f8a8fa425e8166a5caca760e676a62f06005a8d0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 27 Apr 2024 11:11:04 +0100 Subject: [PATCH 201/244] Split ValidateAlembicOptionsDefaults --- .../validate_alembic_options_defaults.py | 30 ++++++++++++------- .../maya/server/settings/publishers.py | 15 ++++++++-- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py index 50dfbb5202..5197100406 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_alembic_options_defaults.py @@ -4,7 +4,7 @@ from ayon_core.pipeline import OptionalPyblishPluginMixin from ayon_core.pipeline.publish import RepairAction, PublishValidationError -class ValidateAlembicOptionsDefaults( +class ValidateAlembicDefaultsPointcache( pyblish.api.InstancePlugin, OptionalPyblishPluginMixin ): """Validate the attributes on the instance are defaults. @@ -13,17 +13,13 @@ class ValidateAlembicOptionsDefaults( """ order = pyblish.api.ValidatorOrder - families = ["pointcache", "animation"] + families = ["pointcache"] hosts = ["maya"] label = "Validate Alembic Options Defaults" actions = [RepairAction] optional = True - @classmethod - def _get_plugin_name(cls, publish_attributes): - for key in ["ExtractAnimation", "ExtractAlembic"]: - if key in publish_attributes: - return key + plugin_name = "ExtractAlembic" @classmethod def _get_settings(cls, context): @@ -34,7 +30,7 @@ class ValidateAlembicOptionsDefaults( @classmethod def _get_publish_attributes(cls, instance): attributes = instance.data["publish_attributes"][ - cls._get_plugin_name( + cls.plugin_name( instance.data["publish_attributes"] ) ] @@ -74,13 +70,13 @@ class ValidateAlembicOptionsDefaults( def repair(cls, instance): # Find create instance twin. create_context = instance.context.data["create_context"] - create_instance = create_context.get_instance_by_id( - instance.data["instance_id"]) + create_instance = create_context.get_instance_by_id( + instance.data["instance_id"] ) # Set the settings values on the create context then save to workfile. publish_attributes = instance.data["publish_attributes"] - plugin_name = cls._get_plugin_name(publish_attributes) + plugin_name = cls.plugin_name(publish_attributes) attributes = cls._get_publish_attributes(instance) settings = cls._get_settings(instance.context) create_publish_attributes = create_instance.data["publish_attributes"] @@ -88,3 +84,15 @@ class ValidateAlembicOptionsDefaults( create_publish_attributes[plugin_name][key] = settings[key] create_context.save_changes() + + +class ValidateAlembicDefaultsAnimation( + ValidateAlembicDefaultsPointcache +): + """Validate the attributes on the instance are defaults. + + The defaults are defined in the project settings. + """ + label = "Validate Alembic Options Defaults" + families = ["animation"] + plugin_name = "ExtractAnimation" diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 2061b97c24..8dcffbb59a 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -889,9 +889,13 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Alembic Visible Node", ) - ValidateAlembicOptionsDefaults: BasicValidateModel = SettingsField( + ValidateAlembicDefaultsPointcache: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, - title="Validate Alembic Options Defaults" + title="Validate Alembic Defaults Pointcache" + ) + ValidateAlembicDefaultsAnimation: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Alembic Defaults Animation" ) ExtractProxyAlembic: ExtractProxyAlembicModel = SettingsField( default_factory=ExtractProxyAlembicModel, @@ -1545,7 +1549,12 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "validate_shapes": True }, - "ValidateAlembicOptionsDefaults": { + "ValidateAlembicDefaultsPointcache": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateAlembicDefaultsAnimation": { "enabled": True, "optional": True, "active": True From 4cb9ee578e65a5aa133d975ff146a572488f4b29 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sat, 27 Apr 2024 11:12:30 +0100 Subject: [PATCH 202/244] Update client/ayon_core/hosts/maya/api/alembic.py Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/maya/api/alembic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index b67e0e0062..fb447d5546 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -14,7 +14,6 @@ ALEMBIC_ARGS = { "attrPrefix": (list, tuple), "autoSubd": bool, "dataFormat": str, - "dontSkipUnwrittenFrames": bool, "endFrame": float, "eulerFilter": bool, "frameRange": str, # "start end"; overrides startFrame & endFrame From d730e8138e53e92df381caa9d38b3060dcdb7845 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sat, 27 Apr 2024 11:13:26 +0100 Subject: [PATCH 203/244] Update client/ayon_core/hosts/maya/api/alembic.py Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/maya/api/alembic.py | 93 +++++++++++----------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index fb447d5546..3d84736847 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -80,46 +80,70 @@ def extract_alembic( the extracted content to solely what was Collected into the instance. Arguments: + file (str): The filepath to write the alembic file to. - startFrame (float): Start frame of output. Ignored if `frameRange` - provided. + attr (list of str, optional): A specific geometric attribute to write + out. Defaults to []. + + attrPrefix (list of str, optional): Prefix filter for determining which + geometric attributes to write out. Defaults to ["ABC_"]. + + dataFormat (str): The data format to use for the cache, + defaults to "ogawa" endFrame (float): End frame of output. Ignored if `frameRange` provided. - frameRange (tuple or str): Two-tuple with start and end frame or a - string formatted as: "startFrame endFrame". This argument - overrides `startFrame` and `endFrame` arguments. - eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with an Euler filter. Euler filtering helps resolve irregularities in rotations especially if X, Y, and Z rotations exceed 360 degrees. Defaults to True. + frameRange (tuple or str): Two-tuple with start and end frame or a + string formatted as: "startFrame endFrame". This argument + overrides `startFrame` and `endFrame` arguments. + noNormals (bool): When on, normal data from the original polygon objects is not included in the exported Alembic cache file. preRoll (bool): This frame range will not be sampled. Defaults to False. + preRollStartFrame (float): The frame to start scene + evaluation at. This is used to set the starting frame for time + dependent translations and can be used to evaluate run-up that + isn't actually translated. Defaults to 0. + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, such as hidden objects, are not included in the Alembic file. Defaults to False. + root (list of str): Maya dag path which will be parented to + the root of the Alembic file. Defaults to [], which means the + entire scene will be written out. + selection (bool): Write out all all selected nodes from the active selection list that are descendents of the roots specified with -root. Defaults to False. + startFrame (float): Start frame of output. Ignored if `frameRange` + provided. + + step (float): The time interval (expressed in frames) at + which the frame range is sampled. Additional samples around each + frame can be specified with -frs. Defaults to 1.0. + + stripNamespaces (bool): When on, any namespaces associated with the + exported objects are removed from the Alembic file. For example, an + object with the namespace taco:foo:bar appears as bar in the + Alembic file. + uvWrite (bool): When on, UV data from polygon meshes and subdivision objects are written to the Alembic file. Only the current UV map is included. - writeColorSets (bool): Write all color sets on MFnMeshes as - color 3 or color 4 indexed geometry parameters with face varying - scope. Defaults to False. - - writeFaceSets (bool): Write all Face sets on MFnMeshes. - Defaults to False. + verbose (bool): When on, outputs frame number information to the + Script Editor or output window during extraction. wholeFrameGeo (bool): Data for geometry will only be written out on whole frames. Defaults to False. @@ -128,13 +152,9 @@ def extract_alembic( stored as world space. By default, these nodes are stored as local space. Defaults to False. - writeVisibility (bool): Visibility state will be stored in - the Alembic file. Otherwise everything written out is treated as - visible. Defaults to False. - - writeUVSets (bool): Write all uv sets on MFnMeshes as vector - 2 indexed geometry parameters with face varying scope. Defaults to - False. + writeColorSets (bool): Write all color sets on MFnMeshes as + color 3 or color 4 indexed geometry parameters with face varying + scope. Defaults to False. writeCreases (bool): If the mesh has crease edges or crease vertices, the mesh (OPolyMesh) would now be written out as an OSubD @@ -143,35 +163,16 @@ def extract_alembic( Boolean attribute SubDivisionMesh has been added to mesh node and its value is true. Defaults to False. - dataFormat (str): The data format to use for the cache, - defaults to "ogawa" + writeFaceSets (bool): Write all Face sets on MFnMeshes. + Defaults to False. - step (float): The time interval (expressed in frames) at - which the frame range is sampled. Additional samples around each - frame can be specified with -frs. Defaults to 1.0. + writeUVSets (bool): Write all uv sets on MFnMeshes as vector + 2 indexed geometry parameters with face varying scope. Defaults to + False. - attr (list of str, optional): A specific geometric attribute to write - out. Defaults to []. - - attrPrefix (list of str, optional): Prefix filter for determining which - geometric attributes to write out. Defaults to ["ABC_"]. - - root (list of str): Maya dag path which will be parented to - the root of the Alembic file. Defaults to [], which means the - entire scene will be written out. - - stripNamespaces (bool): When on, any namespaces associated with the - exported objects are removed from the Alembic file. For example, an - object with the namespace taco:foo:bar appears as bar in the - Alembic file. - - verbose (bool): When on, outputs frame number information to the - Script Editor or output window during extraction. - - preRollStartFrame (float): The frame to start scene - evaluation at. This is used to set the starting frame for time - dependent translations and can be used to evaluate run-up that - isn't actually translated. Defaults to 0. + writeVisibility (bool): Visibility state will be stored in + the Alembic file. Otherwise everything written out is treated as + visible. Defaults to False. """ # Ensure alembic exporter is loaded From 838ae0cd945ad69e9655b6b21ad97029f926e073 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 27 Apr 2024 11:14:59 +0100 Subject: [PATCH 204/244] Remove writeNormals --- client/ayon_core/hosts/maya/api/alembic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index 3d84736847..bf887df4c7 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -69,7 +69,6 @@ def extract_alembic( worldSpace=False, writeColorSets=False, writeCreases=False, - writeNormals=False, writeFaceSets=False, writeUVSets=False, writeVisibility=False From 26791c97a57b7425d70178ec23212b637a88f658 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 27 Apr 2024 11:24:27 +0100 Subject: [PATCH 205/244] Fix instance defs. --- .../maya/plugins/create/create_animation_pointcache.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py index 5694936a57..08d50a1ab8 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_animation_pointcache.py @@ -100,8 +100,8 @@ class CreateAnimation(plugin.MayaHiddenCreator): return node_data def get_instance_attr_defs(self): - super(CreateAnimation, self).get_instance_attr_defs() - defs = _get_animation_attr_defs(self) + defs = super(CreateAnimation, self).get_instance_attr_defs() + defs += _get_animation_attr_defs(self) return defs @@ -124,7 +124,9 @@ class CreatePointCache(plugin.MayaCreator): return node_data def get_instance_attr_defs(self): - return _get_animation_attr_defs(self) + defs = super(CreatePointCache, self).get_instance_attr_defs() + defs += _get_animation_attr_defs(self) + return defs def create(self, product_name, instance_data, pre_create_data): instance = super(CreatePointCache, self).create( From ba5c62bf40b2f21b68fde8429e4c7f8bd70936aa Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Sat, 27 Apr 2024 11:25:59 +0100 Subject: [PATCH 206/244] Update client/ayon_core/hosts/maya/plugins/publish/collect_animation.py Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/maya/plugins/publish/collect_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py index 4604554aa0..391c80c84e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_animation.py @@ -17,7 +17,7 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 families = ["animation"] - label = "Collect Animation" + label = "Collect Animation Output Geometry" hosts = ["maya"] ignore_type = ["constraints"] From 636bebcc1e88808180986234bc778ceeaf04300b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Sat, 27 Apr 2024 11:29:20 +0100 Subject: [PATCH 207/244] Increment maya version --- server_addon/maya/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py index 00f28d901e..5c6ce923aa 100644 --- a/server_addon/maya/package.py +++ b/server_addon/maya/package.py @@ -1,3 +1,3 @@ name = "maya" title = "Maya" -version = "0.1.16" +version = "0.1.17" From 7a2b77ce9ae946bbae3f5edfa876322a24dfb855 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Apr 2024 08:16:05 +0100 Subject: [PATCH 208/244] Account for no placeholder set. --- client/ayon_core/hosts/maya/api/workfile_template_builder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index 75386d7e64..b8759e0740 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -263,6 +263,11 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): # Hide placeholder and add them to placeholder set node = placeholder.scene_identifier + # If we just populate the placeholders from current scene, the + # placeholder set will not be created so account for that. + if not cmds.objExists(PLACEHOLDER_SET): + cmds.sets(name=PLACEHOLDER_SET, empty=True) + cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) From 5018db2f084ad7995d4827d6b326859a4eafa132 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Apr 2024 17:54:40 +0800 Subject: [PATCH 209/244] cosmetic fix - Jakub'scomment --- client/ayon_core/hosts/max/api/lib.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index a6f1c2d2de..ea17d1df05 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -530,11 +530,15 @@ def update_modifier_node_names(event, node): """ containers = [ - obj for obj in rt.Objects - if rt.ClassOf(obj) == rt.Container and - rt.getUserProp(obj, "id") == "pyblish.avalon.instance" - and rt.getUserProp( - obj, "productType") not in {"workfile", "tyflow"} + obj + for obj in rt.Objects + if ( + rt.ClassOf(obj) == rt.Container + and rt.getUserProp(obj, "id") == "pyblish.avalon.instance" + and rt.getUserProp(obj, "productType") not in { + "workfile", "tyflow" + } + ) ] if not containers: return From 24b590d592914e312234f00937238dcfe5e1dfc9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:28:46 +0200 Subject: [PATCH 210/244] define compatibility of applications addon --- server_addon/applications/package.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index ce312ed662..43a301b7c2 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,10 @@ name = "applications" title = "Applications" version = "0.2.0" + +ayon_server_version = ">=1.0.7" +ayon_launcher_version = ">=1.0.2" +ayon_required_addons = { + "core": ">0.3.0", +} +ayon_compatible_addons = {} From 32c538d5c53ef3440d906822871fef8b67c0b8d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:00 +0200 Subject: [PATCH 211/244] copied cache items from tools to lib --- client/ayon_core/lib/__init__.py | 7 + client/ayon_core/lib/cache.py | 243 +++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 client/ayon_core/lib/cache.py diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 408262ca42..e436396c6c 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -27,6 +27,10 @@ from .local_settings import ( get_openpype_username, ) from .ayon_connection import initialize_ayon_connection +from .cache import ( + CacheItem, + NestedCacheItem, +) from .events import ( emit_event, register_event_callback @@ -157,6 +161,9 @@ __all__ = [ "initialize_ayon_connection", + "CacheItem", + "NestedCacheItem", + "emit_event", "register_event_callback", diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py new file mode 100644 index 0000000000..170b9853fe --- /dev/null +++ b/client/ayon_core/lib/cache.py @@ -0,0 +1,243 @@ +import time +import collections + +InitInfo = collections.namedtuple( + "InitInfo", + ["default_factory", "lifetime"] +) + + +def _default_factory_func(): + return None + + +class CacheItem: + """Simple cache item with lifetime and default factory for default value. + + + Default factory should return default value that is used on init + and on reset. + + Args: + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + """ + + def __init__(self, default_factory=None, lifetime=None): + if lifetime is None: + lifetime = 120 + self._lifetime = lifetime + self._last_update = None + if default_factory is None: + default_factory = _default_factory_func + self._default_factory = default_factory + self._data = default_factory() + + @property + def is_valid(self): + """Is cache valid to use. + + Return: + bool: True if cache is valid, False otherwise. + """ + + if self._last_update is None: + return False + + return (time.time() - self._last_update) < self._lifetime + + def set_lifetime(self, lifetime): + """Change lifetime of cache item. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._lifetime = lifetime + + def set_invalid(self): + """Set cache as invalid.""" + + self._last_update = None + + def reset(self): + """Set cache as invalid and reset data.""" + + self._last_update = None + self._data = self._default_factory() + + def get_data(self): + """Receive cached data. + + Returns: + Any: Any data that are cached. + """ + + return self._data + + def update_data(self, data): + self._data = data + self._last_update = time.time() + + +class NestedCacheItem: + """Helper for cached items stored in nested structure. + + Example: + >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) + >>> cache["a"]["b"].is_valid + False + >>> cache["a"]["b"].get_data() + 0 + >>> cache["a"]["b"] = 1 + >>> cache["a"]["b"].is_valid + True + >>> cache["a"]["b"].get_data() + 1 + >>> cache.reset() + >>> cache["a"]["b"].is_valid + False + + Args: + levels (int): Number of nested levels where read cache is stored. + default_factory (Optional[callable]): Function that returns default + value used on init and on reset. + lifetime (Optional[int]): Lifetime of the cache data in seconds. + _init_info (Optional[InitInfo]): Private argument. Init info for + nested cache where created from parent item. + """ + + def __init__( + self, levels=1, default_factory=None, lifetime=None, _init_info=None + ): + if levels < 1: + raise ValueError("Nested levels must be greater than 0") + self._data_by_key = {} + if _init_info is None: + _init_info = InitInfo(default_factory, lifetime) + self._init_info = _init_info + self._levels = levels + + def __getitem__(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + cache = self._data_by_key.get(key) + if cache is None: + if self._levels > 1: + cache = NestedCacheItem( + levels=self._levels - 1, + _init_info=self._init_info + ) + else: + cache = CacheItem( + self._init_info.default_factory, + self._init_info.lifetime + ) + self._data_by_key[key] = cache + return cache + + def __setitem__(self, key, value): + """Update cached data. + + Args: + key (str): Key of the cache item. + value (Any): Any data that are cached. + """ + + if self._levels > 1: + raise AttributeError(( + "{} does not support '__setitem__'. Lower nested level by {}" + ).format(self.__class__.__name__, self._levels - 1)) + cache = self[key] + cache.update_data(value) + + def get(self, key): + """Get cached data. + + Args: + key (str): Key of the cache item. + + Returns: + Union[NestedCacheItem, CacheItem]: Cache item. + """ + + return self[key] + + def cached_count(self): + """Amount of cached items. + + Returns: + int: Amount of cached items. + """ + + return len(self._data_by_key) + + def clear_key(self, key): + """Clear cached item by key. + + Args: + key (str): Key of the cache item. + """ + + self._data_by_key.pop(key, None) + + def clear_invalid(self): + """Clear all invalid cache items. + + Note: + To clear all cache items use 'reset'. + """ + + changed = {} + children_are_nested = self._levels > 1 + for key, cache in tuple(self._data_by_key.items()): + if children_are_nested: + output = cache.clear_invalid() + if output: + changed[key] = output + if not cache.cached_count(): + self._data_by_key.pop(key) + elif not cache.is_valid: + changed[key] = cache.get_data() + self._data_by_key.pop(key) + return changed + + def reset(self): + """Reset cache. + + Note: + To clear only invalid cache items use 'clear_invalid'. + """ + + self._data_by_key = {} + + def set_lifetime(self, lifetime): + """Change lifetime of all children cache items. + + Args: + lifetime (int): Lifetime of the cache data in seconds. + """ + + self._init_info.lifetime = lifetime + for cache in self._data_by_key.values(): + cache.set_lifetime(lifetime) + + @property + def is_valid(self): + """Raise reasonable error when called on wrong level. + + Raises: + AttributeError: If called on nested cache item. + """ + + raise AttributeError(( + "{} does not support 'is_valid'. Lower nested level by '{}'" + ).format(self.__class__.__name__, self._levels)) From 8f9ae0669d1b7b86b867a1ff685ebe3b2300b015 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:09:15 +0200 Subject: [PATCH 212/244] import cache items from new location --- client/ayon_core/tools/common_models/hierarchy.py | 3 +-- client/ayon_core/tools/common_models/projects.py | 3 +-- client/ayon_core/tools/common_models/thumbnails.py | 2 +- client/ayon_core/tools/loader/models/actions.py | 2 +- client/ayon_core/tools/loader/models/products.py | 2 +- client/ayon_core/tools/loader/models/sitesync.py | 3 +-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/common_models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py index d8b28f020d..78b8a7f492 100644 --- a/client/ayon_core/tools/common_models/hierarchy.py +++ b/client/ayon_core/tools/common_models/hierarchy.py @@ -6,8 +6,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem HIERARCHY_MODEL_SENDER = "hierarchy.model" diff --git a/client/ayon_core/tools/common_models/projects.py b/client/ayon_core/tools/common_models/projects.py index e30561000e..19a38bee21 100644 --- a/client/ayon_core/tools/common_models/projects.py +++ b/client/ayon_core/tools/common_models/projects.py @@ -5,8 +5,7 @@ import ayon_api import six from ayon_core.style import get_default_entity_icon_color - -from .cache import CacheItem +from ayon_core.lib import CacheItem PROJECTS_MODEL_SENDER = "projects.model" diff --git a/client/ayon_core/tools/common_models/thumbnails.py b/client/ayon_core/tools/common_models/thumbnails.py index 1c3aadc49f..6d14783b9a 100644 --- a/client/ayon_core/tools/common_models/thumbnails.py +++ b/client/ayon_core/tools/common_models/thumbnails.py @@ -5,7 +5,7 @@ import collections import ayon_api import appdirs -from .cache import NestedCacheItem +from ayon_core.lib import NestedCacheItem FileInfo = collections.namedtuple( "FileInfo", diff --git a/client/ayon_core/tools/loader/models/actions.py b/client/ayon_core/tools/loader/models/actions.py index ad2993af50..cfe91cadab 100644 --- a/client/ayon_core/tools/loader/models/actions.py +++ b/client/ayon_core/tools/loader/models/actions.py @@ -6,6 +6,7 @@ import uuid import ayon_api +from ayon_core.lib import NestedCacheItem from ayon_core.pipeline.load import ( discover_loader_plugins, ProductLoaderPlugin, @@ -17,7 +18,6 @@ from ayon_core.pipeline.load import ( LoadError, IncompatibleLoaderError, ) -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem ACTIONS_MODEL_SENDER = "actions.model" diff --git a/client/ayon_core/tools/loader/models/products.py b/client/ayon_core/tools/loader/models/products.py index 812446a012..a3bbc30a09 100644 --- a/client/ayon_core/tools/loader/models/products.py +++ b/client/ayon_core/tools/loader/models/products.py @@ -5,8 +5,8 @@ import arrow import ayon_api from ayon_api.operations import OperationsSession +from ayon_core.lib import NestedCacheItem from ayon_core.style import get_default_entity_icon_color -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ( ProductTypeItem, ProductItem, diff --git a/client/ayon_core/tools/loader/models/sitesync.py b/client/ayon_core/tools/loader/models/sitesync.py index 987510905b..02504c2ad3 100644 --- a/client/ayon_core/tools/loader/models/sitesync.py +++ b/client/ayon_core/tools/loader/models/sitesync.py @@ -2,9 +2,8 @@ import collections from ayon_api import get_representations, get_versions_links -from ayon_core.lib import Logger +from ayon_core.lib import Logger, NestedCacheItem from ayon_core.addon import AddonsManager -from ayon_core.tools.common_models import NestedCacheItem from ayon_core.tools.loader.abstract import ActionItem DOWNLOAD_IDENTIFIER = "sitesync.download" From 734ce367fa3177ac4fd4272a16a00c5e43104d3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:10:09 +0200 Subject: [PATCH 213/244] added deprecation warnings to classes on previous location --- client/ayon_core/tools/common_models/cache.py | 266 ++---------------- 1 file changed, 29 insertions(+), 237 deletions(-) diff --git a/client/ayon_core/tools/common_models/cache.py b/client/ayon_core/tools/common_models/cache.py index 221a14160c..59b727728f 100644 --- a/client/ayon_core/tools/common_models/cache.py +++ b/client/ayon_core/tools/common_models/cache.py @@ -1,239 +1,31 @@ -import time -import collections +import warnings -InitInfo = collections.namedtuple( - "InitInfo", - ["default_factory", "lifetime"] +from ayon_core.lib import CacheItem as _CacheItem +from ayon_core.lib import NestedCacheItem as _NestedCacheItem + + +# Cache classes were moved to `ayon_core.lib.cache` +class CacheItem(_CacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'CacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +class NestedCacheItem(_NestedCacheItem): + def __init__(self, *args, **kwargs): + warnings.warn( + "Used 'NestedCacheItem' from deprecated location " + "'ayon_core.tools.common_models', use 'ayon_core.lib' instead.", + DeprecationWarning, + ) + super().__init__(*args, **kwargs) + + +__all__ = ( + "CacheItem", + "NestedCacheItem", ) - - -def _default_factory_func(): - return None - - -class CacheItem: - """Simple cache item with lifetime and default value. - - Args: - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ - - def __init__(self, default_factory=None, lifetime=None): - if lifetime is None: - lifetime = 120 - self._lifetime = lifetime - self._last_update = None - if default_factory is None: - default_factory = _default_factory_func - self._default_factory = default_factory - self._data = default_factory() - - @property - def is_valid(self): - """Is cache valid to use. - - Return: - bool: True if cache is valid, False otherwise. - """ - - if self._last_update is None: - return False - - return (time.time() - self._last_update) < self._lifetime - - def set_lifetime(self, lifetime): - """Change lifetime of cache item. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._lifetime = lifetime - - def set_invalid(self): - """Set cache as invalid.""" - - self._last_update = None - - def reset(self): - """Set cache as invalid and reset data.""" - - self._last_update = None - self._data = self._default_factory() - - def get_data(self): - """Receive cached data. - - Returns: - Any: Any data that are cached. - """ - - return self._data - - def update_data(self, data): - self._data = data - self._last_update = time.time() - - -class NestedCacheItem: - """Helper for cached items stored in nested structure. - - Example: - >>> cache = NestedCacheItem(levels=2, default_factory=lambda: 0) - >>> cache["a"]["b"].is_valid - False - >>> cache["a"]["b"].get_data() - 0 - >>> cache["a"]["b"] = 1 - >>> cache["a"]["b"].is_valid - True - >>> cache["a"]["b"].get_data() - 1 - >>> cache.reset() - >>> cache["a"]["b"].is_valid - False - - Args: - levels (int): Number of nested levels where read cache is stored. - default_factory (Optional[callable]): Function that returns default - value used on init and on reset. - lifetime (Optional[int]): Lifetime of the cache data in seconds. - _init_info (Optional[InitInfo]): Private argument. Init info for - nested cache where created from parent item. - """ - - def __init__( - self, levels=1, default_factory=None, lifetime=None, _init_info=None - ): - if levels < 1: - raise ValueError("Nested levels must be greater than 0") - self._data_by_key = {} - if _init_info is None: - _init_info = InitInfo(default_factory, lifetime) - self._init_info = _init_info - self._levels = levels - - def __getitem__(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - cache = self._data_by_key.get(key) - if cache is None: - if self._levels > 1: - cache = NestedCacheItem( - levels=self._levels - 1, - _init_info=self._init_info - ) - else: - cache = CacheItem( - self._init_info.default_factory, - self._init_info.lifetime - ) - self._data_by_key[key] = cache - return cache - - def __setitem__(self, key, value): - """Update cached data. - - Args: - key (str): Key of the cache item. - value (Any): Any data that are cached. - """ - - if self._levels > 1: - raise AttributeError(( - "{} does not support '__setitem__'. Lower nested level by {}" - ).format(self.__class__.__name__, self._levels - 1)) - cache = self[key] - cache.update_data(value) - - def get(self, key): - """Get cached data. - - Args: - key (str): Key of the cache item. - - Returns: - Union[NestedCacheItem, CacheItem]: Cache item. - """ - - return self[key] - - def cached_count(self): - """Amount of cached items. - - Returns: - int: Amount of cached items. - """ - - return len(self._data_by_key) - - def clear_key(self, key): - """Clear cached item by key. - - Args: - key (str): Key of the cache item. - """ - - self._data_by_key.pop(key, None) - - def clear_invalid(self): - """Clear all invalid cache items. - - Note: - To clear all cache items use 'reset'. - """ - - changed = {} - children_are_nested = self._levels > 1 - for key, cache in tuple(self._data_by_key.items()): - if children_are_nested: - output = cache.clear_invalid() - if output: - changed[key] = output - if not cache.cached_count(): - self._data_by_key.pop(key) - elif not cache.is_valid: - changed[key] = cache.get_data() - self._data_by_key.pop(key) - return changed - - def reset(self): - """Reset cache. - - Note: - To clear only invalid cache items use 'clear_invalid'. - """ - - self._data_by_key = {} - - def set_lifetime(self, lifetime): - """Change lifetime of all children cache items. - - Args: - lifetime (int): Lifetime of the cache data in seconds. - """ - - self._init_info.lifetime = lifetime - for cache in self._data_by_key.values(): - cache.set_lifetime(lifetime) - - @property - def is_valid(self): - """Raise reasonable error when called on wront level. - - Raises: - AttributeError: If called on nested cache item. - """ - - raise AttributeError(( - "{} does not support 'is_valid'. Lower nested level by '{}'" - ).format(self.__class__.__name__, self._levels)) From 2059ffd74275b024a48021b3e3e5fdd7dfedee25 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Apr 2024 21:12:03 +0800 Subject: [PATCH 214/244] fix the malfunctioning issue in maxscene loader --- client/ayon_core/hosts/max/api/lib.py | 4 ++-- client/ayon_core/hosts/max/plugins/load/load_max_scene.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 02b099b3ff..4f365cb1c1 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -496,9 +496,9 @@ def object_transform_set(container_children): """ transform_set = {} for node in container_children: - name = f"{node.name}.transform" + name = f"{node}.transform" transform_set[name] = node.pos - name = f"{node.name}.scale" + name = f"{node}.scale" transform_set[name] = node.scale return transform_set diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 4f982dd5ba..97b8c6cd52 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -117,7 +117,7 @@ class MaxSceneLoader(load.LoaderPlugin): ) for max_obj, obj_name in zip(max_objects, max_object_names): max_obj.name = f"{namespace}:{obj_name}" - max_container.append(rt.getNodeByName(max_obj.name)) + max_container.append(max_obj) return containerise( name, max_container, context, namespace, loader=self.__class__.__name__) @@ -158,11 +158,11 @@ class MaxSceneLoader(load.LoaderPlugin): current_max_object_names): max_obj.name = f"{namespace}:{obj_name}" max_objects.append(max_obj) - max_transform = f"{max_obj.name}.transform" + max_transform = f"{max_obj}.transform" if max_transform in transform_data.keys(): max_obj.pos = transform_data[max_transform] or 0 max_obj.scale = transform_data[ - f"{max_obj.name}.scale"] or 0 + f"{max_obj}.scale"] or 0 update_custom_attribute_data(node, max_objects) lib.imprint(container["instance_node"], { From 86bbd24a9a7666d1475fbc22bf61597da3eef25d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:46 +0200 Subject: [PATCH 215/244] use cache items in anatomy --- client/ayon_core/pipeline/anatomy/anatomy.py | 76 ++++---------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 2aa8eeddbc..35599e85d6 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -3,11 +3,16 @@ import re import copy import platform import collections -import time import ayon_api -from ayon_core.lib import Logger, get_local_site_id, StringTemplate +from ayon_core.lib import ( + Logger, + get_local_site_id, + StringTemplate, + CacheItem, + NestedCacheItem, +) from ayon_core.addon import AddonsManager from .exceptions import RootCombinationError, ProjectNotSet @@ -397,62 +402,11 @@ class BaseAnatomy(object): ) -class CacheItem: - """Helper to cache data. - - Helper does not handle refresh of data and does not mark data as outdated. - Who uses the object should check of outdated state on his own will. - """ - - default_lifetime = 10 - - def __init__(self, lifetime=None): - self._data = None - self._cached = None - self._lifetime = lifetime or self.default_lifetime - - @property - def data(self): - """Cached data/object. - - Returns: - Any: Whatever was cached. - """ - - return self._data - - @property - def is_outdated(self): - """Item has outdated cache. - - Lifetime of cache item expired or was not yet set. - - Returns: - bool: Item is outdated. - """ - - if self._cached is None: - return True - return (time.time() - self._cached) > self._lifetime - - def update_data(self, data): - """Update cache of data. - - Args: - data (Any): Data to cache. - """ - - self._data = data - self._cached = time.time() - - class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem() - _project_cache = collections.defaultdict(CacheItem) - _default_site_id_cache = collections.defaultdict(CacheItem) - _root_overrides_cache = collections.defaultdict( - lambda: collections.defaultdict(CacheItem) - ) + _sitesync_addon_cache = CacheItem(lifetime=10) + _project_cache = NestedCacheItem(lifetime=10) + _default_site_id_cache = NestedCacheItem(lifetime=10) + _root_overrides_cache = NestedCacheItem(2, lifetime=10) def __init__( self, project_name=None, site_name=None, project_entity=None @@ -479,7 +433,7 @@ class Anatomy(BaseAnatomy): project_cache = cls._project_cache[project_name] if project_cache.is_outdated: project_cache.update_data(ayon_api.get_project(project_name)) - return copy.deepcopy(project_cache.data) + return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): @@ -488,7 +442,7 @@ class Anatomy(BaseAnatomy): cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") ) - return cls._sitesync_addon_cache.data + return cls._sitesync_addon_cache.get_data() @classmethod def _get_studio_roots_overrides(cls, project_name): @@ -537,7 +491,7 @@ class Anatomy(BaseAnatomy): project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) - site_name = project_cache.data + site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] if site_cache.is_outdated: @@ -553,4 +507,4 @@ class Anatomy(BaseAnatomy): project_name, site_name ) site_cache.update_data(roots_overrides) - return site_cache.data + return site_cache.get_data() From 27c3a7bcaa2e3df0e2075624bc99d483c770b0b9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:15:56 +0200 Subject: [PATCH 216/244] change defautlt timeouts --- client/ayon_core/pipeline/anatomy/anatomy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 35599e85d6..db8a00fa08 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -403,10 +403,10 @@ class BaseAnatomy(object): class Anatomy(BaseAnatomy): - _sitesync_addon_cache = CacheItem(lifetime=10) _project_cache = NestedCacheItem(lifetime=10) - _default_site_id_cache = NestedCacheItem(lifetime=10) - _root_overrides_cache = NestedCacheItem(2, lifetime=10) + _sitesync_addon_cache = CacheItem(lifetime=60) + _default_site_id_cache = NestedCacheItem(lifetime=60) + _root_overrides_cache = NestedCacheItem(2, lifetime=60) def __init__( self, project_name=None, site_name=None, project_entity=None From 72028abc2c70955fb25a63e6628cdaa656e97a50 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Apr 2024 15:55:36 +0200 Subject: [PATCH 217/244] Fix import --- .../ayon_core/hosts/maya/tools/mayalookassigner/vray_proxies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/tools/mayalookassigner/vray_proxies.py b/client/ayon_core/hosts/maya/tools/mayalookassigner/vray_proxies.py index 88ef4b201a..c1d9f019e4 100644 --- a/client/ayon_core/hosts/maya/tools/mayalookassigner/vray_proxies.py +++ b/client/ayon_core/hosts/maya/tools/mayalookassigner/vray_proxies.py @@ -7,7 +7,7 @@ from maya import cmds import ayon_api from ayon_core.pipeline import get_current_project_name -import ayon_core.hosts.maya.lib as maya_lib +import ayon_core.hosts.maya.api.lib as maya_lib from . import lib from .alembic import get_alembic_ids_cache From 3980f59211a389c7d1a11b4d9874d0f2147898c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:28 +0200 Subject: [PATCH 218/244] fix docstring lines --- client/ayon_core/lib/cache.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 170b9853fe..be6fd4f4d7 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -14,7 +14,6 @@ def _default_factory_func(): class CacheItem: """Simple cache item with lifetime and default factory for default value. - Default factory should return default value that is used on init and on reset. @@ -22,8 +21,8 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. - """ + """ def __init__(self, default_factory=None, lifetime=None): if lifetime is None: lifetime = 120 @@ -40,8 +39,8 @@ class CacheItem: Return: bool: True if cache is valid, False otherwise. - """ + """ if self._last_update is None: return False @@ -72,8 +71,8 @@ class CacheItem: Returns: Any: Any data that are cached. - """ + """ return self._data def update_data(self, data): @@ -106,8 +105,8 @@ class NestedCacheItem: lifetime (Optional[int]): Lifetime of the cache data in seconds. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. - """ + """ def __init__( self, levels=1, default_factory=None, lifetime=None, _init_info=None ): @@ -127,8 +126,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ cache = self._data_by_key.get(key) if cache is None: if self._levels > 1: @@ -150,8 +149,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. value (Any): Any data that are cached. - """ + """ if self._levels > 1: raise AttributeError(( "{} does not support '__setitem__'. Lower nested level by {}" @@ -167,8 +166,8 @@ class NestedCacheItem: Returns: Union[NestedCacheItem, CacheItem]: Cache item. - """ + """ return self[key] def cached_count(self): @@ -176,8 +175,8 @@ class NestedCacheItem: Returns: int: Amount of cached items. - """ + """ return len(self._data_by_key) def clear_key(self, key): @@ -185,8 +184,8 @@ class NestedCacheItem: Args: key (str): Key of the cache item. - """ + """ self._data_by_key.pop(key, None) def clear_invalid(self): @@ -194,8 +193,8 @@ class NestedCacheItem: Note: To clear all cache items use 'reset'. - """ + """ changed = {} children_are_nested = self._levels > 1 for key, cache in tuple(self._data_by_key.items()): @@ -215,8 +214,8 @@ class NestedCacheItem: Note: To clear only invalid cache items use 'clear_invalid'. - """ + """ self._data_by_key = {} def set_lifetime(self, lifetime): @@ -224,8 +223,8 @@ class NestedCacheItem: Args: lifetime (int): Lifetime of the cache data in seconds. - """ + """ self._init_info.lifetime = lifetime for cache in self._data_by_key.values(): cache.set_lifetime(lifetime) @@ -236,8 +235,8 @@ class NestedCacheItem: Raises: AttributeError: If called on nested cache item. - """ + """ raise AttributeError(( "{} does not support 'is_valid'. Lower nested level by '{}'" ).format(self.__class__.__name__, self._levels)) From f10d2660297224720dcc12aa57340d4cd648985d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:35 +0200 Subject: [PATCH 219/244] add missing docstring --- client/ayon_core/lib/cache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index be6fd4f4d7..c661a16f7b 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -76,6 +76,12 @@ class CacheItem: return self._data def update_data(self, data): + """Update cache data. + + Args: + data (Any): Any data that are cached. + + """ self._data = data self._last_update = time.time() From 2594fc82b061ceacee66765b402b1148057e0960 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:07:50 +0200 Subject: [PATCH 220/244] added default value of lifetime to docstring --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index c661a16f7b..005c900c9f 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -21,6 +21,7 @@ class CacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default lifetime is 120 seconds. """ def __init__(self, default_factory=None, lifetime=None): From 907fa5de934aeea15c9706bed1d3cb46f496a199 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:08:43 +0200 Subject: [PATCH 221/244] added default value to 'NestedCacheItem' --- client/ayon_core/lib/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/lib/cache.py b/client/ayon_core/lib/cache.py index 005c900c9f..dc83520f76 100644 --- a/client/ayon_core/lib/cache.py +++ b/client/ayon_core/lib/cache.py @@ -110,6 +110,7 @@ class NestedCacheItem: default_factory (Optional[callable]): Function that returns default value used on init and on reset. lifetime (Optional[int]): Lifetime of the cache data in seconds. + Default value is based on default value of 'CacheItem'. _init_info (Optional[InitInfo]): Private argument. Init info for nested cache where created from parent item. From 64ff24dbf12c9979d23f3558e7f619ba02fa8b54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:45:07 +0200 Subject: [PATCH 222/244] use 'is_valid' instead of 'is_outdated' --- client/ayon_core/pipeline/anatomy/anatomy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index db8a00fa08..98bbaa9bdc 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -431,13 +431,13 @@ class Anatomy(BaseAnatomy): @classmethod def get_project_entity_from_cache(cls, project_name): project_cache = cls._project_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data(ayon_api.get_project(project_name)) return copy.deepcopy(project_cache.get_data()) @classmethod def get_sitesync_addon(cls): - if cls._sitesync_addon_cache.is_outdated: + if not cls._sitesync_addon_cache.is_valid: manager = AddonsManager() cls._sitesync_addon_cache.update_data( manager.get_enabled_addon("sitesync") @@ -487,14 +487,14 @@ class Anatomy(BaseAnatomy): elif not site_name: # Use sync server to receive active site name project_cache = cls._default_site_id_cache[project_name] - if project_cache.is_outdated: + if not project_cache.is_valid: project_cache.update_data( sitesync_addon.get_active_site_type(project_name) ) site_name = project_cache.get_data() site_cache = cls._root_overrides_cache[project_name][site_name] - if site_cache.is_outdated: + if not site_cache.is_valid: if site_name == "studio": # Handle studio root overrides without sync server # - studio root overrides can be done even without sync server From 7db14f47c16c9e7b736b430d587bb7cd559aa757 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 29 Apr 2024 18:13:57 +0300 Subject: [PATCH 223/244] imporve code logic --- .../hosts/houdini/hooks/set_default_display_and_view.py | 3 ++- .../hosts/houdini/plugins/create/create_review.py | 7 +++++-- .../houdini/plugins/publish/validate_review_colorspace.py | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py index 31bb5c1c5d..7d41979600 100644 --- a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py +++ b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py @@ -24,8 +24,9 @@ class SetDefaultDisplayView(PreLaunchHook): if not OCIO: return + # workfile settings added in '0.2.13' houdini_color_settings = \ - self.data["project_settings"]["houdini"]["imageio"].get("workfile", {}) + self.data["project_settings"]["houdini"]["imageio"].get("workfile") if not houdini_color_settings: self.log.info("Hook 'SetDefaultDisplayView' requires Houdini " diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_review.py b/client/ayon_core/hosts/houdini/plugins/create/create_review.py index 4a00ed4d37..336a1c9318 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_review.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_review.py @@ -18,8 +18,11 @@ class CreateReview(plugin.HoudiniCreator): def apply_settings(self, project_settings): super(CreateReview, self).apply_settings(project_settings) - color_settings = project_settings["houdini"]["imageio"].get("workfile", {}) - if color_settings and color_settings["enabled"]: + # workfile settings added in '0.2.13' + color_settings = project_settings["houdini"]["imageio"].get( + "workfile", {} + ) + if not color_settings.get("enabled"): self.review_color_space = color_settings.get("review_color_space") def create(self, product_name, instance_data, pre_create_data): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py index 3b70aea894..cdbdba5361 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -45,9 +45,12 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, category="houdini") apply_plugin_settings_automatically(cls, settings, logger=cls.log) + # workfile settings added in '0.2.13' + color_settings = project_settings["houdini"]["imageio"].get( + "workfile", {} + ) # Add review color settings - color_settings = project_settings["houdini"]["imageio"].get("workfile", {}) - if color_settings and color_settings["enabled"]: + if not color_settings.get("enabled"): cls.review_color_space = color_settings.get("review_color_space") From 40cc4d2b98b6e7c59febea0e3bd040e05e8fb138 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 29 Apr 2024 21:32:54 +0300 Subject: [PATCH 224/244] fix color_settings condition --- client/ayon_core/hosts/houdini/plugins/create/create_review.py | 2 +- .../hosts/houdini/plugins/publish/validate_review_colorspace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_review.py b/client/ayon_core/hosts/houdini/plugins/create/create_review.py index 336a1c9318..f5e4d4ce64 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_review.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_review.py @@ -22,7 +22,7 @@ class CreateReview(plugin.HoudiniCreator): color_settings = project_settings["houdini"]["imageio"].get( "workfile", {} ) - if not color_settings.get("enabled"): + if color_settings.get("enabled"): self.review_color_space = color_settings.get("review_color_space") def create(self, product_name, instance_data, pre_create_data): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py index cdbdba5361..e7f528ba57 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -50,7 +50,7 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, "workfile", {} ) # Add review color settings - if not color_settings.get("enabled"): + if color_settings.get("enabled"): cls.review_color_space = color_settings.get("review_color_space") From 07bd2e21de2ad54316cf41b75396371cb2d2b4fe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Apr 2024 06:47:08 +0200 Subject: [PATCH 225/244] Resolve merge conflict --- .../hosts/maya/plugins/workfile_build/load_placeholder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py index 5bfaae6500..cf4a350c36 100644 --- a/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py @@ -255,7 +255,8 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if scene_parent: cmds.parent(node, scene_parent) else: - cmds.parent(node, world=True) + if cmds.listRelatives(node, parent=True): + cmds.parent(node, world=True) holding_sets = cmds.listSets(object=placeholder.scene_identifier) if not holding_sets: From 90ece1cdf98cabeec76cf52bc7854667818f8d3f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Apr 2024 06:48:50 +0200 Subject: [PATCH 226/244] Refactor `template_placeholder_plugin` -> `workfile_build_plugin` --- .../hosts/aftereffects/api/pipeline.py | 4 ++-- client/ayon_core/hosts/maya/api/pipeline.py | 8 ++++---- client/ayon_core/hosts/nuke/api/pipeline.py | 4 ++-- client/ayon_core/pipeline/__init__.py | 20 +++++++++---------- .../ayon_core/pipeline/workfile/__init__.py | 20 +++++++++---------- .../workfile/workfile_template_builder.py | 10 +++++----- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/api/pipeline.py b/client/ayon_core/hosts/aftereffects/api/pipeline.py index 6b213822f3..2239040f09 100644 --- a/client/ayon_core/hosts/aftereffects/api/pipeline.py +++ b/client/ayon_core/hosts/aftereffects/api/pipeline.py @@ -8,7 +8,7 @@ from ayon_core.lib import Logger, register_event_callback from ayon_core.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, - register_template_placeholder_plugin_path, + register_workfile_build_plugin_path, AVALON_CONTAINER_ID, AVALON_INSTANCE_ID, AYON_INSTANCE_ID, @@ -74,7 +74,7 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) + register_workfile_build_plugin_path(WORKFILE_BUILD_PATH) register_event_callback("application.launched", application_launch) diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 257c822e0b..74d73e5f95 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -30,11 +30,11 @@ from ayon_core.pipeline import ( register_loader_plugin_path, register_inventory_action_path, register_creator_plugin_path, - register_template_placeholder_plugin_path, + register_workfile_build_plugin_path, deregister_loader_plugin_path, deregister_inventory_action_path, deregister_creator_plugin_path, - deregister_template_placeholder_plugin_path, + deregister_workfile_build_plugin_path, AYON_CONTAINER_ID, AVALON_CONTAINER_ID, ) @@ -95,7 +95,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) + register_workfile_build_plugin_path(WORKFILE_BUILD_PATH) self.log.info("Installing callbacks ... ") register_event_callback("init", on_init) @@ -335,7 +335,7 @@ def uninstall(): deregister_loader_plugin_path(LOAD_PATH) deregister_creator_plugin_path(CREATE_PATH) deregister_inventory_action_path(INVENTORY_PATH) - deregister_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) + deregister_workfile_build_plugin_path(WORKFILE_BUILD_PATH) menu.uninstall() diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index f5e48eb375..d35a2e89e0 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -18,7 +18,7 @@ from ayon_core.pipeline import ( register_loader_plugin_path, register_creator_plugin_path, register_inventory_action_path, - register_template_placeholder_plugin_path, + register_workfile_build_plugin_path, AYON_INSTANCE_ID, AVALON_INSTANCE_ID, AVALON_CONTAINER_ID, @@ -118,7 +118,7 @@ class NukeHost( register_loader_plugin_path(LOAD_PATH) register_creator_plugin_path(CREATE_PATH) register_inventory_action_path(INVENTORY_PATH) - register_template_placeholder_plugin_path(WORKFILE_BUILD_PATH) + register_workfile_build_plugin_path(WORKFILE_BUILD_PATH) # Register AYON event for workfiles loading. register_event_callback("workio.open_file", check_inventory_versions) diff --git a/client/ayon_core/pipeline/__init__.py b/client/ayon_core/pipeline/__init__.py index 3102ce1da3..8fd00ee6b6 100644 --- a/client/ayon_core/pipeline/__init__.py +++ b/client/ayon_core/pipeline/__init__.py @@ -99,11 +99,11 @@ from .context_tools import ( ) from .workfile import ( - discover_template_placeholder_plugins, - register_template_placeholder_plugin, - deregister_template_placeholder_plugin, - register_template_placeholder_plugin_path, - deregister_template_placeholder_plugin_path, + discover_workfile_build_plugins, + register_workfile_build_plugin, + deregister_workfile_build_plugin, + register_workfile_build_plugin_path, + deregister_workfile_build_plugin_path, ) install = install_host @@ -208,11 +208,11 @@ __all__ = ( "get_current_task_name", # Workfile templates - "discover_template_placeholder_plugins", - "register_template_placeholder_plugin", - "deregister_template_placeholder_plugin", - "register_template_placeholder_plugin_path", - "deregister_template_placeholder_plugin_path", + "discover_workfile_build_plugins", + "register_workfile_build_plugin", + "deregister_workfile_build_plugin", + "register_workfile_build_plugin_path", + "deregister_workfile_build_plugin_path", # Backwards compatible function names "install", diff --git a/client/ayon_core/pipeline/workfile/__init__.py b/client/ayon_core/pipeline/workfile/__init__.py index 149036117a..05f939024c 100644 --- a/client/ayon_core/pipeline/workfile/__init__.py +++ b/client/ayon_core/pipeline/workfile/__init__.py @@ -22,11 +22,11 @@ from .build_workfile import BuildWorkfile from .workfile_template_builder import ( - discover_template_placeholder_plugins, - register_template_placeholder_plugin, - deregister_template_placeholder_plugin, - register_template_placeholder_plugin_path, - deregister_template_placeholder_plugin_path, + discover_workfile_build_plugins, + register_workfile_build_plugin, + deregister_workfile_build_plugin, + register_workfile_build_plugin_path, + deregister_workfile_build_plugin_path, ) @@ -49,9 +49,9 @@ __all__ = ( "BuildWorkfile", - "discover_template_placeholder_plugins", - "register_template_placeholder_plugin", - "deregister_template_placeholder_plugin", - "register_template_placeholder_plugin_path", - "deregister_template_placeholder_plugin_path", + "discover_workfile_build_plugins", + "register_workfile_build_plugin", + "deregister_workfile_build_plugin", + "register_workfile_build_plugin_path", + "deregister_workfile_build_plugin_path", ) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 642ccd1cbc..6d200cd7dd 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1925,21 +1925,21 @@ class CreatePlaceholderItem(PlaceholderItem): self._failed_created_publish_instances.append(creator_data) -def discover_template_placeholder_plugins(*args, **kwargs): +def discover_workfile_build_plugins(*args, **kwargs): return discover(PlaceholderPlugin, *args, **kwargs) -def register_template_placeholder_plugin(plugin: PlaceholderPlugin): +def register_workfile_build_plugin(plugin: PlaceholderPlugin): register_plugin(PlaceholderPlugin, plugin) -def deregister_template_placeholder_plugin(plugin: PlaceholderPlugin): +def deregister_workfile_build_plugin(plugin: PlaceholderPlugin): deregister_plugin(PlaceholderPlugin, plugin) -def register_template_placeholder_plugin_path(path: str): +def register_workfile_build_plugin_path(path: str): register_plugin_path(PlaceholderPlugin, path) -def deregister_template_placeholder_plugin_path(path: str): +def deregister_workfile_build_plugin_path(path: str): deregister_plugin_path(PlaceholderPlugin, path) From b5f4a843d516bf505a73455a956bb81b3e5dbe31 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 Apr 2024 13:31:30 +0800 Subject: [PATCH 227/244] add more fps value support & supports to reset fps value based on the task entity --- client/ayon_core/hosts/maya/api/lib.py | 35 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 321bcbc0b5..017d0cd2c4 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2520,7 +2520,17 @@ def set_scene_fps(fps, update=True): """ fps_mapping = { + # 2, 3, 4, 5, 6, 8, 10, 12, 16 + '2': '2fps', + '3': '3fps', + '4': '4fps', + '5': '5fps', + '6': '6fps', + '8': '8fps', + '10': '10fps', + '12': '12fps', '15': 'game', + '16': '16fps', '24': 'film', '25': 'pal', '30': 'ntsc', @@ -2612,21 +2622,24 @@ def get_fps_for_current_context(): Returns: Union[int, float]: FPS value. """ - - project_name = get_current_project_name() - folder_path = get_current_folder_path() - folder_entity = ayon_api.get_folder_by_path( - project_name, folder_path, fields={"attrib.fps"} - ) or {} - fps = folder_entity.get("attrib", {}).get("fps") + task_entity = get_current_task_entity(fields={"attrib"}) + fps = task_entity.get("attrib", {}).get("fps") if not fps: - project_entity = ayon_api.get_project( - project_name, fields=["attrib.fps"] + project_name = get_current_project_name() + folder_path = get_current_folder_path() + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"attrib.fps"} ) or {} - fps = project_entity.get("attrib", {}).get("fps") + fps = folder_entity.get("attrib", {}).get("fps") if not fps: - fps = 25 + project_entity = ayon_api.get_project( + project_name, fields=["attrib.fps"] + ) or {} + fps = project_entity.get("attrib", {}).get("fps") + + if not fps: + fps = 25 return convert_to_maya_fps(fps) From 444b0d8fc7bacf5ad4517b061cf05c55ce998643 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:38:32 +0800 Subject: [PATCH 228/244] Update client/ayon_core/hosts/maya/api/lib.py Co-authored-by: Toke Jepsen --- client/ayon_core/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 017d0cd2c4..b8c9bedc60 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2520,7 +2520,6 @@ def set_scene_fps(fps, update=True): """ fps_mapping = { - # 2, 3, 4, 5, 6, 8, 10, 12, 16 '2': '2fps', '3': '3fps', '4': '4fps', From e74a1d303e0d9cbed5bce2f1936301b2d65b15b7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 Apr 2024 16:15:17 +0800 Subject: [PATCH 229/244] fix the bug encountered when getting transform for the asset when updating it --- client/ayon_core/hosts/max/plugins/load/load_model_fbx.py | 4 ++-- client/ayon_core/hosts/max/plugins/load/load_model_obj.py | 4 ++-- client/ayon_core/hosts/max/plugins/load/load_model_usd.py | 4 ++-- .../hosts/max/plugins/load/load_pointcache_ornatrix.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py index 82cad71c3e..6f5de20ae0 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py @@ -76,11 +76,11 @@ class FbxModelLoader(load.LoaderPlugin): for fbx_object in current_fbx_objects: fbx_object.name = f"{namespace}:{fbx_object.name}" fbx_objects.append(fbx_object) - fbx_transform = f"{fbx_object.name}.transform" + fbx_transform = f"{fbx_object}.transform" if fbx_transform in transform_data.keys(): fbx_object.pos = transform_data[fbx_transform] or 0 fbx_object.scale = transform_data[ - f"{fbx_object.name}.scale"] or 0 + f"{fbx_object}.scale"] or 0 with maintained_selection(): rt.Select(node) diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py index 38f2cdf43c..a9119259df 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py @@ -67,11 +67,11 @@ class ObjLoader(load.LoaderPlugin): selections = rt.GetCurrentSelection() for selection in selections: selection.name = f"{namespace}:{selection.name}" - selection_transform = f"{selection.name}.transform" + selection_transform = f"{selection}.transform" if selection_transform in transform_data.keys(): selection.pos = transform_data[selection_transform] or 0 selection.scale = transform_data[ - f"{selection.name}.scale"] or 0 + f"{selection}.scale"] or 0 update_custom_attribute_data(node, selections) with maintained_selection(): rt.Select(node) diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py index 2b946eb2aa..2ed5d64a18 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py @@ -95,11 +95,11 @@ class ModelUSDLoader(load.LoaderPlugin): for children in asset.Children: children.name = f"{namespace}:{children.name}" usd_objects.append(children) - children_transform = f"{children.name}.transform" + children_transform = f"{children}.transform" if children_transform in transform_data.keys(): children.pos = transform_data[children_transform] or 0 children.scale = transform_data[ - f"{children.name}.scale"] or 0 + f"{children}.scale"] or 0 asset.name = f"{namespace}:{asset.name}" usd_objects.append(asset) diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py index 2efb7c7f62..47690f84e9 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -92,10 +92,10 @@ class OxAbcLoader(load.LoaderPlugin): abc.Parent = container abc.name = f"{namespace}:{abc.name}" ox_abc_objects.append(abc) - ox_transform = f"{abc.name}.transform" + ox_transform = f"{abc}.transform" if ox_transform in transform_data.keys(): abc.pos = transform_data[ox_transform] or 0 - abc.scale = transform_data[f"{abc.name}.scale"] or 0 + abc.scale = transform_data[f"{abc}.scale"] or 0 update_custom_attribute_data(node, ox_abc_objects) lib.imprint( container["instance_node"], From c50bd1498e820ed9af035578fbfe8f6c8ffb8854 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Apr 2024 10:34:31 +0200 Subject: [PATCH 230/244] Apply suggestions from code review Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../pipeline/workfile/workfile_template_builder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 64f2ad1c18..fc4ba3fe98 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -741,7 +741,7 @@ class AbstractTemplateBuilder(object): placeholder.set_finished() # Trigger on_depth_processed event - self.trigger_event( + self.emit_event( topic="template.depth_processed", data={ "depth": iter_counter, @@ -769,7 +769,7 @@ class AbstractTemplateBuilder(object): placeholders.append(placeholder) # Trigger on_finished event - self.trigger_event( + self.emit_event( topic="template.finished", data={ "depth": iter_counter, @@ -912,7 +912,7 @@ class AbstractTemplateBuilder(object): return self._event_system.add_callback(topic, callback, order=order) def add_on_finished_callback( - self, callback, order=None + self, callback, order=None ) -> EventCallback: return self.add_event_callback( topic="template.finished", @@ -921,7 +921,7 @@ class AbstractTemplateBuilder(object): ) def add_on_depth_processed_callback( - self, callback, order=None + self, callback, order=None ) -> EventCallback: return self.add_event_callback( topic="template.depth_processed", From 8dd30e33efca0755a7241a1ea95e7b91bad1d2f1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Apr 2024 12:06:26 +0100 Subject: [PATCH 231/244] Update fix --- .../hosts/maya/plugins/workfile_build/load_placeholder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py index cf4a350c36..5e73933722 100644 --- a/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/workfile_build/load_placeholder.py @@ -187,6 +187,11 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): # Hide placeholder and add them to placeholder set node = placeholder.scene_identifier + # If we just populate the placeholders from current scene, the + # placeholder set will not be created so account for that. + if not cmds.objExists(PLACEHOLDER_SET): + cmds.sets(name=PLACEHOLDER_SET, empty=True) + cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) From 9f4c7018a289a2bd060bd8ad1c25969952e93c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 16:36:08 +0200 Subject: [PATCH 232/244] :recycle: make validator optional --- .../maya/plugins/publish/validate_rendersettings.py | 9 +++++++-- server_addon/maya/server/settings/publishers.py | 9 ++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py index 78a247b3f2..987e9eec7c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py @@ -10,6 +10,7 @@ from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, PublishValidationError, + OptionalPyblishPluginMixin ) from ayon_core.hosts.maya.api import lib from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings @@ -37,7 +38,8 @@ def get_redshift_image_format_labels(): return mel.eval("{0}={0}".format(var)) -class ValidateRenderSettings(pyblish.api.InstancePlugin): +class ValidateRenderSettings(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Validates the global render settings * File Name Prefix must start with: `` @@ -55,7 +57,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): * Frame Padding must be: * default: 4 - * Animation must be toggle on, in Render Settings - Common tab: + * Animation must be toggled on, in Render Settings - Common tab: * vray: Animation on standard of specific * arnold: Frame / Animation ext: Any choice without "(Single Frame)" * redshift: Animation toggled on @@ -71,6 +73,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["renderlayer"] actions = [RepairAction] + optional = True ImagePrefixes = { 'mentalray': 'defaultRenderGlobals.imageFilePrefix', @@ -112,6 +115,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): DEFAULT_PREFIX = "//_" def process(self, instance): + if not self.is_active(instance.data): + return invalid = self.get_invalid(instance) if invalid: diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 27288053a2..c29d52f95e 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -184,7 +184,7 @@ class ValidateAttributesModel(BaseSettingsModel): if not success: raise BadRequestException( - "The attibutes can't be parsed as json object" + "The attributes can't be parsed as json object" ) return value @@ -220,7 +220,7 @@ class ValidateUnrealStaticMeshNameModel(BaseSettingsModel): enabled: bool = SettingsField(title="ValidateUnrealStaticMeshName") optional: bool = SettingsField(title="Optional") validate_mesh: bool = SettingsField(title="Validate mesh names") - validate_collision: bool = SettingsField(title="Validate collison names") + validate_collision: bool = SettingsField(title="Validate collision names") class ValidateCycleErrorModel(BaseSettingsModel): @@ -265,6 +265,7 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): + optional: bool = SettingsField(title="Optional") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") vray_render_attributes: list[RendererAttributesModel] = SettingsField( @@ -392,7 +393,7 @@ class ExtractGPUCacheModel(BaseSettingsModel): title="Optimize Animations For Motion Blur" ) writeMaterials: bool = SettingsField(title="Write Materials") - useBaseTessellation: bool = SettingsField(title="User Base Tesselation") + useBaseTessellation: bool = SettingsField(title="User Based Tessellation") class PublishersModel(BaseSettingsModel): @@ -942,6 +943,8 @@ DEFAULT_PUBLISH_SETTINGS = { ] }, "ValidateRenderSettings": { + "enabled": True, + "optional": False, "arnold_render_attributes": [], "vray_render_attributes": [], "redshift_render_attributes": [], From f943cb98e831476afbfe75f3a41a8b1b6ecda833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 17:27:03 +0200 Subject: [PATCH 233/244] :recycle: support enabled state --- server_addon/maya/server/settings/publishers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index c29d52f95e..30f23904e2 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -265,6 +265,7 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): + enabled: bool = SettingsField(title="ValidateRenderSettings") optional: bool = SettingsField(title="Optional") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") From 8e4fe3bddec8adb29e8be4f079d6ea735482f56d Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 30 Apr 2024 16:28:15 +0100 Subject: [PATCH 234/244] Update client/ayon_core/hosts/hiero/api/workio.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- client/ayon_core/hosts/hiero/api/workio.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 9d8b1777e7..c7b749a9dd 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -56,8 +56,11 @@ def open_file(filepath): # Close previous project if its different to the current project. if project.path() != filepath: + # open project file + hiero.core.openProject(filepath.replace(os.path.sep, "/")) project.close() + return True From e3619120fff489b36d527f5e195a0827e674e3c8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Apr 2024 16:30:11 +0100 Subject: [PATCH 235/244] Remove redundant openProject --- client/ayon_core/hosts/hiero/api/workio.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index c7b749a9dd..de6f1f4a37 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -51,9 +51,6 @@ def open_file(filepath): project = hiero.core.projects()[-1] - # open project file - hiero.core.openProject(filepath.replace(os.path.sep, "/")) - # Close previous project if its different to the current project. if project.path() != filepath: # open project file From 84dc455bbe6aadce32361d154c3014a95951f998 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 01:36:15 +0200 Subject: [PATCH 236/244] Fix `extract_alembic` imports --- client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py | 2 +- .../ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py | 2 +- .../maya/plugins/publish/extract_unreal_skeletalmesh_abc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py index 2c23f9b752..5f51dc38cb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_assembly.py @@ -2,7 +2,7 @@ import os import json from ayon_core.pipeline import publish -from ayon_core.hosts.maya.api.lib import extract_alembic +from ayon_core.hosts.maya.api.alembic import extract_alembic from maya import cmds diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py index 3637a58614..5aefdfc33a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_proxy_abc.py @@ -3,8 +3,8 @@ import os from maya import cmds from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection, iter_visible_nodes_in_range diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py index 1a389f3d33..b5cc7745a1 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_unreal_skeletalmesh_abc.py @@ -5,8 +5,8 @@ import os from maya import cmds # noqa from ayon_core.pipeline import publish +from ayon_core.hosts.maya.api.alembic import extract_alembic from ayon_core.hosts.maya.api.lib import ( - extract_alembic, suspended_refresh, maintained_selection ) From 5643cbb9ca5dd2dfcbaee3ff2fb2c7dc23954cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 May 2024 11:25:42 +0200 Subject: [PATCH 237/244] :bug: fix invalid enabled flag unrelated to the original PR, sorry --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 30f23904e2..c46aa59453 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -243,7 +243,7 @@ class ValidatePluginPathAttributesModel(BaseSettingsModel): and the node attribute is abc_file """ - enabled: bool = True + enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") attribute: list[ValidatePluginPathAttributesAttrModel] = SettingsField( From 2c5a43dc931737c2e1aed2dfe6614545bb6c275f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 2 May 2024 11:42:46 +0200 Subject: [PATCH 238/244] :recycle: limit the site prefix only to windows --- client/ayon_core/hosts/unreal/ue_workers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/unreal/ue_workers.py b/client/ayon_core/hosts/unreal/ue_workers.py index cdac2c28af..256c0557be 100644 --- a/client/ayon_core/hosts/unreal/ue_workers.py +++ b/client/ayon_core/hosts/unreal/ue_workers.py @@ -303,9 +303,12 @@ class UEProjectGenerationWorker(UEWorker): "install", "--ignore-installed", pyside_version, - "--prefix", site_packages_prefix, + ] + if platform.system().lower() == "windows": + pyside_cmd += ["--target", site_packages_prefix] + print(f"--- Installing {pyside_version} ...") print(" ".join(pyside_cmd)) From 94ff9d92c33bc06fd39e4c6bb99fc11e8c0a871e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 May 2024 11:49:06 +0200 Subject: [PATCH 239/244] Fix validate_unique_names call validate_unique_names cannot be used on fields that are just regular `list[str]`, they must be `list[SomethingSomething]`. --- server_addon/deadline/server/settings/publish_plugins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 9f69143e37..784ad2560b 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -191,7 +191,6 @@ class NukeSubmitDeadlineModel(BaseSettingsModel): @validator( "limit_groups", - "env_allowed_keys", "env_search_replace_values") def validate_unique_names(cls, value): ensure_unique_names(value) From 396c24d9d7f99460167a30c7729fbb8d4480dffe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 2 May 2024 11:52:21 +0200 Subject: [PATCH 240/244] Bump up version of deadline --- server_addon/deadline/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py index 944797fea6..25ba1c1166 100644 --- a/server_addon/deadline/package.py +++ b/server_addon/deadline/package.py @@ -1,3 +1,3 @@ name = "deadline" title = "Deadline" -version = "0.1.10" +version = "0.1.11" From 3fef14e2e738cf0b86e52a5462a6b9e0b496b905 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 2 May 2024 12:28:27 +0100 Subject: [PATCH 241/244] Update client/ayon_core/hosts/hiero/api/workio.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- client/ayon_core/hosts/hiero/api/workio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index de6f1f4a37..2d0874bd07 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -52,9 +52,11 @@ def open_file(filepath): project = hiero.core.projects()[-1] # Close previous project if its different to the current project. + # Close previous project if its different to the current project. + filepath = filepath.replace(os.path.sep, "/") if project.path() != filepath: # open project file - hiero.core.openProject(filepath.replace(os.path.sep, "/")) + hiero.core.openProject(filepath) project.close() From 65529f3662b1fa3cc2876e65d6ba49fd66e90e4a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 May 2024 12:41:00 +0100 Subject: [PATCH 242/244] Fix workio --- client/ayon_core/hosts/hiero/api/workio.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 2d0874bd07..6e8fc20172 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -51,15 +51,13 @@ def open_file(filepath): project = hiero.core.projects()[-1] - # Close previous project if its different to the current project. # Close previous project if its different to the current project. filepath = filepath.replace(os.path.sep, "/") - if project.path() != filepath: + if project.path().replace(os.path.sep, "/") != filepath: # open project file hiero.core.openProject(filepath) project.close() - return True From f00acb5dd220b81a1f9989deaf3952c9882af5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 12:21:10 +0200 Subject: [PATCH 243/244] :recycle: add active flag, change label --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 2 +- server_addon/maya/server/settings/publishers.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py index 987e9eec7c..7badfdc027 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py @@ -69,7 +69,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder - label = "Render Settings" + label = "Validate Render Settings" hosts = ["maya"] families = ["renderlayer"] actions = [RepairAction] diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 4ad5c9b6d0..460df803f2 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -310,8 +310,9 @@ class RendererAttributesModel(BaseSettingsModel): class ValidateRenderSettingsModel(BaseSettingsModel): - enabled: bool = SettingsField(title="ValidateRenderSettings") + enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") vray_render_attributes: list[RendererAttributesModel] = SettingsField( From c5471e409a993758855c7a147e2159c0a16e769e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 12:57:21 +0200 Subject: [PATCH 244/244] :art: add default value --- server_addon/maya/server/settings/publishers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 460df803f2..bc38d5f746 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1175,6 +1175,7 @@ DEFAULT_PUBLISH_SETTINGS = { }, "ValidateRenderSettings": { "enabled": True, + "active": True, "optional": False, "arnold_render_attributes": [], "vray_render_attributes": [],