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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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/423] 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 c2f2fdb38b7d6f08c7edf06e5ff1efef81c091e6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 11:20:42 +0200 Subject: [PATCH 026/423] add workfile colorspace to Houdini settings --- .../defaults/project_settings/houdini.json | 6 ++++ .../schema_project_houdini.json | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 87983620ec..93fa7e2c6f 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -21,6 +21,12 @@ "file_rules": { "activate_host_rules": false, "rules": {} + }, + "workfile":{ + "enabled": false, + "default_display": "ACES", + "default_view": "sRGB", + "review_color_space": "Output - sRGB" } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index d4d0565ec9..4e7f1aa4c9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -19,6 +19,40 @@ { "type": "template", "name": "template_host_color_management_ocio" + }, + { + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Render space in Houdini is always set to 'scene_linear' Role." + }, + { + "type": "text", + "key": "default_display", + "label": "Display" + }, + { + "type": "text", + "key": "default_view", + "label": "View" + }, + { + "type": "text", + "key": "review_color_space", + "label": "Review colorspace" + } + + ] } ] }, From bf32236e9477205c3cb65ffaa60e5f0d5d21c357 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:24:40 +0200 Subject: [PATCH 027/423] add Houdini workfile colorspace to Ayon settings --- .../houdini/server/settings/imageio.py | 34 ++++++++++++++++++- server_addon/houdini/server/settings/main.py | 6 +++- server_addon/houdini/server/version.py | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 88aa40ecd6..6a61171b66 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -34,9 +34,18 @@ class ImageIOFileRulesModel(BaseSettingsModel): return value +class WorkfileImageIOModel(BaseSettingsModel): + """Render space in Houdini is always set to 'scene_linear' Role.""" + + enabled: bool = Field(False, title="Enabled") + default_display: str = Field(title="Display") + default_view: str = Field(title="View") + review_color_space: str = Field(title="Review colorspace") + + class HoudiniImageIOModel(BaseSettingsModel): activate_host_color_management: bool = Field( - True, title="Enable Color Management" + False, title="Enable Color Management" ) ocio_config: ImageIOConfigModel = Field( default_factory=ImageIOConfigModel, @@ -46,3 +55,26 @@ class HoudiniImageIOModel(BaseSettingsModel): default_factory=ImageIOFileRulesModel, title="File Rules" ) + workfile: WorkfileImageIOModel = Field( + default_factory=WorkfileImageIOModel, + title="Workfile" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "activate_host_color_management": False, + "ocio_config": { + "override_global_config": False, + "filepath": [] + }, + "file_rules": { + "activate_host_rules": False, + "rules": [] + }, + "workfile": { + "enabled": False, + "default_display": "ACES", + "default_view": "sRGB", + "review_color_space": "Output - sRGB" + } +} diff --git a/server_addon/houdini/server/settings/main.py b/server_addon/houdini/server/settings/main.py index 9cfec54f22..250e654afd 100644 --- a/server_addon/houdini/server/settings/main.py +++ b/server_addon/houdini/server/settings/main.py @@ -4,7 +4,10 @@ from .general import ( GeneralSettingsModel, DEFAULT_GENERAL_SETTINGS ) -from .imageio import HoudiniImageIOModel +from .imageio import ( + HoudiniImageIOModel, + DEFAULT_IMAGEIO_SETTINGS +) from .shelves import ShelvesModel from .create import ( CreatePluginsModel, @@ -41,6 +44,7 @@ class HoudiniSettings(BaseSettingsModel): DEFAULT_VALUES = { "general": DEFAULT_GENERAL_SETTINGS, + "imageio": DEFAULT_IMAGEIO_SETTINGS, "shelves": [], "create": DEFAULT_HOUDINI_CREATE_SETTINGS, "publish": DEFAULT_HOUDINI_PUBLISH_SETTINGS diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 01ef12070d..6cd38b7465 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.6" +__version__ = "0.2.7" From fca6abde553beb286268299803c17f4d22e63d3f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:26:11 +0200 Subject: [PATCH 028/423] add Houdini SetDefaultDislayView prelaunch hook --- .../hooks/set_default_display_and_view.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 openpype/hosts/houdini/hooks/set_default_display_and_view.py diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py new file mode 100644 index 0000000000..33278d7fee --- /dev/null +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -0,0 +1,52 @@ +from openpype.lib.applications import PreLaunchHook, LaunchTypes +from openpype.settings import get_project_settings + + +class SetDefaultDislayView(PreLaunchHook): + """Set default view and default display for houdini hosts that use OpenColorIO. + + Houdini's defaultDisplay and defaultView are set by + setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' + environment variables respectively. + + More info: https://www.sidefx.com/docs/houdini/io/ocio.html#set-up + """ + + app_groups = {"houdini"} + launch_types = {LaunchTypes.local} + + def execute(self): + + OCIO = self.launch_context.env.get("OCIO") + + # This is a cheap way to skip this hook if either + # global color management or houdini color management was disabled. + if not OCIO: + return + + project_settings = get_project_settings( + project_name=self.data["project_name"] + ) + + houdini_color_Settings = project_settings["houdini"]["imageio"]["workfile"] + + if not houdini_color_Settings["enabled"] : + self.log.info( + "Houdini's workefile color settings are disabled." + ) + return + + default_display = houdini_color_Settings["default_display"] + default_view = houdini_color_Settings["default_view"] + + self.log.info( + "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" + .format(default_display) + ) + self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + + self.log.info( + "Setting OCIO_ACTIVE_VIEWS environment to config path: {}" + .format(default_view) + ) + self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From 10cf562f359a39948b6db6245fd56533190d824d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:44:38 +0200 Subject: [PATCH 029/423] resolve hound --- .../hosts/houdini/hooks/set_default_display_and_view.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 33278d7fee..2c98247b8c 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -3,7 +3,7 @@ from openpype.settings import get_project_settings class SetDefaultDislayView(PreLaunchHook): - """Set default view and default display for houdini hosts that use OpenColorIO. + """Set default view and default display for houdini host that use OpenColorIO. Houdini's defaultDisplay and defaultView are set by setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' @@ -28,9 +28,10 @@ class SetDefaultDislayView(PreLaunchHook): project_name=self.data["project_name"] ) - houdini_color_Settings = project_settings["houdini"]["imageio"]["workfile"] + houdini_color_Settings = \ + project_settings["houdini"]["imageio"]["workfile"] - if not houdini_color_Settings["enabled"] : + if not houdini_color_Settings["enabled"]: self.log.info( "Houdini's workefile color settings are disabled." ) From 537d1acac854c46dbe20eac95977886f81619253 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 15:38:11 +0200 Subject: [PATCH 030/423] BigRoy's comments - Better logging & remove unnecessary logic --- .../houdini/hooks/set_default_display_and_view.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 2c98247b8c..cb5dba51cf 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,4 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -from openpype.settings import get_project_settings class SetDefaultDislayView(PreLaunchHook): @@ -19,21 +18,18 @@ class SetDefaultDislayView(PreLaunchHook): OCIO = self.launch_context.env.get("OCIO") - # This is a cheap way to skip this hook if either - # global color management or houdini color management was disabled. + # This is a cheap way to skip this hook if either global color + # management or houdini color management was disabled because the + # OCIO var would be set by the global OCIOEnvHook if not OCIO: return - project_settings = get_project_settings( - project_name=self.data["project_name"] - ) - houdini_color_Settings = \ - project_settings["houdini"]["imageio"]["workfile"] + self.data["project_settings"]["houdini"]["imageio"]["workfile"] if not houdini_color_Settings["enabled"]: self.log.info( - "Houdini's workefile color settings are disabled." + "Houdini workfile color management is disabled." ) return From 3ffbf736517db28aef379ec1281c71753325e7f7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 15:45:22 +0200 Subject: [PATCH 031/423] resolve hound --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index cb5dba51cf..8c91ef7d06 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -25,7 +25,7 @@ class SetDefaultDislayView(PreLaunchHook): return houdini_color_Settings = \ - self.data["project_settings"]["houdini"]["imageio"]["workfile"] + self.data["project_settings"]["houdini"]["imageio"]["workfile"] if not houdini_color_Settings["enabled"]: self.log.info( From 1a1e48e4a5b44d1e0498952f78ad06a77f68e6f1 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 2 Nov 2023 16:23:27 +0200 Subject: [PATCH 032/423] BigRoy's comment - update variable name Co-authored-by: Roy Nieterau --- .../hosts/houdini/hooks/set_default_display_and_view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 8c91ef7d06..85e605b37d 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -24,17 +24,17 @@ class SetDefaultDislayView(PreLaunchHook): if not OCIO: return - houdini_color_Settings = \ + houdini_color_settings = \ self.data["project_settings"]["houdini"]["imageio"]["workfile"] - if not houdini_color_Settings["enabled"]: + if not houdini_color_settings["enabled"]: self.log.info( "Houdini workfile color management is disabled." ) return - default_display = houdini_color_Settings["default_display"] - default_view = houdini_color_Settings["default_view"] + default_display = houdini_color_settings["default_display"] + default_view = houdini_color_settings["default_view"] self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" From 7d80da84e21e615e32119c42e87b6e741356b7fa Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 2 Nov 2023 17:18:18 +0200 Subject: [PATCH 033/423] BigRoy's Comment - fix typo Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 85e605b37d..edff73117f 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,7 +1,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -class SetDefaultDislayView(PreLaunchHook): +class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini host that use OpenColorIO. Houdini's defaultDisplay and defaultView are set by From cd4d8bd2b1c9eddf4cbe7204999ead31d2308a4a Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Fri, 3 Nov 2023 09:17:00 +0200 Subject: [PATCH 034/423] BigRoy's comment - better doc string Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index edff73117f..ff55e6275d 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -2,7 +2,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes class SetDefaultDisplayView(PreLaunchHook): - """Set default view and default display for houdini host that use OpenColorIO. + """Set default view and default display for houdini via OpenColorIO. Houdini's defaultDisplay and defaultView are set by setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' From 0a8e293e6e6157dd21efd439af874e095c488c5d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 3 Nov 2023 09:25:07 +0200 Subject: [PATCH 035/423] bump patch version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 6cd38b7465..c49a95c357 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.7" +__version__ = "0.2.8" From a32f0a62934a031ce25f9be24c83c2b7462699ed Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 09:20:41 +0200 Subject: [PATCH 036/423] use 'review color space' setting in review creator --- .../houdini/plugins/create/create_review.py | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 60c34a358b..a7a56cce03 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,6 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef +from openpype.settings import get_current_project_settings import os import hou @@ -87,7 +88,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_colorcorrect_to_default_view_space(instance_node) + self.set_review_color_space(instance_node) to_lock = ["id", "family"] @@ -131,22 +132,40 @@ class CreateReview(plugin.HoudiniCreator): decimals=3) ] - def set_colorcorrect_to_default_view_space(self, - instance_node): - """Set ociocolorspace to the default output space.""" - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + def set_review_color_space(self, instance_node): + """Set ociocolorspace parameter. - # set Color Correction parameter to OpenColorIO + This function will use the value exposed in settings + if workfile settings were enabled. + + Otherwise, it will use the default colorspace corresponding + to the display & view of the current Houdini session. + """ + + # Set Color Correction parameter to OpenColorIO instance_node.setParms({"colorcorrect": 2}) - # Get default view space for ociocolorspace parm. - default_view_space = get_default_display_view_colorspace() + # Get view space for ociocolorspace parm. + view_space = self.get_review_colorspace_from_Settings() + + if not view_space: + from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + view_space = get_default_display_view_colorspace() + instance_node.setParms( - {"ociocolorspace": default_view_space} + {"ociocolorspace": view_space} ) self.log.debug( "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(instance_node, default_view_space) + "the view color space '{}'" + .format(instance_node, view_space) ) + + def get_review_colorspace_from_Settings(self): + project_settings = get_current_project_settings() + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + return color_settings.get("review_color_space") + + return "" From 0eacab216675438d65c697428916d055d57631de Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:51:01 +0200 Subject: [PATCH 037/423] update houdini workfile color settings --- .../settings/defaults/project_settings/houdini.json | 3 ++- .../projects_schema/schema_project_houdini.json | 10 +++++++++- server_addon/houdini/server/settings/imageio.py | 10 +++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 93fa7e2c6f..826a4e12f1 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -26,7 +26,8 @@ "enabled": false, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB" + "review_color_space": "Output - sRGB", + "override_review_color": false } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 4e7f1aa4c9..70b6d63ee8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -50,8 +50,16 @@ "type": "text", "key": "review_color_space", "label": "Review colorspace" + }, + { + "type": "label", + "label": "Allow artists to override review colorspace, therefore review color space validator\nwon't error if artists used another review colorspace." + }, + { + "type": "boolean", + "key": "override_review_color", + "label": "Override review colorspace" } - ] } ] diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 6a61171b66..6404942d5b 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -41,6 +41,13 @@ class WorkfileImageIOModel(BaseSettingsModel): default_display: str = Field(title="Display") default_view: str = Field(title="View") review_color_space: str = Field(title="Review colorspace") + override_review_color: bool = Field( + False, + title="Override review colorspace", + description=("Allow artists to override review colorspace, " + "therefore review color space validator won't error " + "if artists used another review colorspace.") + ) class HoudiniImageIOModel(BaseSettingsModel): @@ -76,5 +83,6 @@ DEFAULT_IMAGEIO_SETTINGS = { "default_display": "ACES", "default_view": "sRGB", "review_color_space": "Output - sRGB" - } + }, + "override_review_color": False } From c4cd10c63740243e97bf270d1b9de7c26f82ae98 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:51:51 +0200 Subject: [PATCH 038/423] use houdini color settings while review creation and validation --- openpype/hosts/houdini/api/lib.py | 62 +++++++++++++++++++ .../houdini/plugins/create/create_review.py | 43 +------------ .../publish/validate_review_colorspace.py | 54 +++++++++------- 3 files changed, 94 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index ac375c56d6..f4db492978 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -844,6 +844,68 @@ def get_current_context_template_data_with_asset_data(): return template_data +def get_houdini_color_settings(): + """Get Houdini working file color settings. + + Returns: + Dict: The dictionary contains the Houdini working file color settings + if the settings are enabled, otherwise it is an empty dictionary. + """ + + project_settings = get_current_project_settings() + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + color_settings.pop("enabled") + # Remove leading, and trailing whitespaces + view_Space = color_settings["review_color_space"] + color_settings["review_color_space"] = view_Space.strip() + return color_settings + + return {} + +def set_review_color_space(opengl_node, log=None): + """Set ociocolorspace parameter for the given OpenGL node. + + This function will use the value exposed in settings + if workfile settings were enabled. + + Otherwise, it will use the default colorspace corresponding + to the display & view of the current Houdini session. + + Args: + OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. + log (logging.Logger): Logger to log to. + """ + + if log is None: + log = self.log + + # Set Color Correction parameter to OpenColorIO + if opengl_node.evalParm("colorcorrect") != 2: + opengl_node.setParms({"colorcorrect": 2}) + log.debug( + "'Color Correction' parm on '{}' has been set to" + " 'OpenColorIO'".format(opengl_node.path()) + ) + + # Get view space for ociocolorspace parm. + view_space = get_houdini_color_settings().get("review_color_space") + + if not view_space: + from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + view_space = get_default_display_view_colorspace() + + opengl_node.setParms( + {"ociocolorspace": view_space} + ) + + self.log.debug( + "'OCIO Colorspace' parm on '{}' has been set to " + "the view color space '{}'" + .format(opengl_node, view_space) + ) + + def get_context_var_changes(): """get context var changes.""" diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index a7a56cce03..775babbab6 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,8 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef -from openpype.settings import get_current_project_settings - +from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -88,7 +87,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_review_color_space(instance_node) + set_review_color_space(instance_node, log=self.log) to_lock = ["id", "family"] @@ -131,41 +130,3 @@ class CreateReview(plugin.HoudiniCreator): minimum=0.0001, decimals=3) ] - - def set_review_color_space(self, instance_node): - """Set ociocolorspace parameter. - - This function will use the value exposed in settings - if workfile settings were enabled. - - Otherwise, it will use the default colorspace corresponding - to the display & view of the current Houdini session. - """ - - # Set Color Correction parameter to OpenColorIO - instance_node.setParms({"colorcorrect": 2}) - - # Get view space for ociocolorspace parm. - view_space = self.get_review_colorspace_from_Settings() - - if not view_space: - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - view_space = get_default_display_view_colorspace() - - instance_node.setParms( - {"ociocolorspace": view_space} - ) - - self.log.debug( - "'OCIO Colorspace' parm on '{}' has been set to " - "the view color space '{}'" - .format(instance_node, view_space) - ) - - def get_review_colorspace_from_Settings(self): - project_settings = get_current_project_settings() - color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: - return color_settings.get("review_color_space") - - return "" diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 03ecd1b052..2bc62516d5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -6,13 +6,17 @@ from openpype.pipeline import ( ) from openpype.pipeline.publish import RepairAction from openpype.hosts.houdini.api.action import SelectROPAction +from openpype.hosts.houdini.api.lib import ( + get_houdini_color_settings, + set_review_color_space +) import os import hou -class SetDefaultViewSpaceAction(RepairAction): - label = "Set default view colorspace" +class SetReviewColorSpaceAction(RepairAction): + label = "Set Review Color Space" icon = "mdi.monitor" @@ -27,7 +31,7 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, families = ["review"] hosts = ["houdini"] label = "Validate Review Colorspace" - actions = [SetDefaultViewSpaceAction, SelectROPAction] + actions = [SetReviewColorSpaceAction, SelectROPAction] optional = True @@ -61,30 +65,32 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, .format(rop_node.path()) ) + color_settings = get_houdini_color_settings() + if color_settings.get("override_review_color"): + return + + if rop_node.evalParm("ociocolorspace") != \ + color_settings["review_color_space"]: + + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match studio settings.\n" + "Check 'OCIO Colorspace' parameter on '{}' ROP" + .format(rop_node.path()) + ) + @classmethod def repair(cls, instance): """Set Default View Space Action. - It is a helper action more than a repair action, - used to set colorspace on opengl node to the default view. + It sets ociocolorspace parameter. + + if workfile settings are enabled, it will use the value + exposed in the settings. + + if workfile settings are disabled, it will use the default + colorspace corresponding to the display & view of + the current Houdini session. """ - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - rop_node = hou.node(instance.data["instance_node"]) - - if rop_node.evalParm("colorcorrect") != 2: - rop_node.setParms({"colorcorrect": 2}) - cls.log.debug( - "'Color Correction' parm on '{}' has been set to" - " 'OpenColorIO'".format(rop_node.path()) - ) - - # Get default view colorspace name - default_view_space = get_default_display_view_colorspace() - - rop_node.setParms({"ociocolorspace": default_view_space}) - cls.log.info( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(rop_node, default_view_space) - ) + opengl_node = hou.node(instance.data["instance_node"]) + set_review_color_space(opengl_node, log=cls.log) From 2caac26ac7c3c0dc8d0262488840162d5bd5e09e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:55:46 +0200 Subject: [PATCH 039/423] Resolve Hound --- openpype/hosts/houdini/api/lib.py | 1 + .../houdini/plugins/publish/validate_review_colorspace.py | 4 ++-- server_addon/houdini/server/settings/imageio.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index ae65cadf1b..bfbfcb586e 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -855,6 +855,7 @@ def get_houdini_color_settings(): return {} + def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 2bc62516d5..783e67014c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -73,8 +73,8 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, color_settings["review_color_space"]: raise PublishValidationError( - "Invalid value: Colorspace name doesn't match studio settings.\n" - "Check 'OCIO Colorspace' parameter on '{}' ROP" + "Invalid value: Colorspace name doesn't match studio " + "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" .format(rop_node.path()) ) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 6404942d5b..ab98152232 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -45,8 +45,8 @@ class WorkfileImageIOModel(BaseSettingsModel): False, title="Override review colorspace", description=("Allow artists to override review colorspace, " - "therefore review color space validator won't error " - "if artists used another review colorspace.") + "therefore review color space validator won't error " + "if artists used another review colorspace.") ) From 8de9dccbfb4734452286fc6dc55d0d9cbcfef4ed Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 12:07:10 +0200 Subject: [PATCH 040/423] update doc string --- openpype/hosts/houdini/api/lib.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index bfbfcb586e..b40f8b2fcf 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -859,11 +859,12 @@ def get_houdini_color_settings(): def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. - This function will use the value exposed in settings - if workfile settings were enabled. + if workfile settings are enabled, it will use the value + exposed in the settings. - Otherwise, it will use the default colorspace corresponding - to the display & view of the current Houdini session. + if workfile settings are disabled, it will use the default + colorspace corresponding to the display & view of + the current Houdini session. Args: OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. From 96849785936881a79ea1a325a643a957a6612f4d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 12:27:17 +0200 Subject: [PATCH 041/423] bump Houdini addon patch version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index c49a95c357..75cf7831c4 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.8" +__version__ = "0.2.9" From dadddfb00e9e8c9ee52ffc19960c2db4333a65ab Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 08:18:10 +0200 Subject: [PATCH 042/423] Minkiu comment - remove unnecessary variable --- openpype/hosts/houdini/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index b40f8b2fcf..27e70ce152 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -849,8 +849,8 @@ def get_houdini_color_settings(): if color_settings["enabled"]: color_settings.pop("enabled") # Remove leading, and trailing whitespaces - view_Space = color_settings["review_color_space"] - color_settings["review_color_space"] = view_Space.strip() + color_settings["review_color_space"] = \ + color_settings["review_color_space"].strip() return color_settings return {} From a1d1c49e3866c4cc5f9fad235f2822e308423daa Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 08:36:01 +0200 Subject: [PATCH 043/423] fabia's comment - Better conditional --- openpype/hosts/houdini/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 27e70ce152..bdcc368d7f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -875,8 +875,9 @@ def set_review_color_space(opengl_node, log=None): log = self.log # Set Color Correction parameter to OpenColorIO - if opengl_node.evalParm("colorcorrect") != 2: - opengl_node.setParms({"colorcorrect": 2}) + colorcorrect_parm = opengl_node.parm("colorcorrect") + if colorcorrect_parm.eval() != 2: + colorcorrect_parm.set(2) log.debug( "'Color Correction' parm on '{}' has been set to" " 'OpenColorIO'".format(opengl_node.path()) From 83089da81a93c62c65cf8a768f9891e63637898a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 10:43:53 +0200 Subject: [PATCH 044/423] update set_review_color_space() and get_houdini_color_settings() logic --- openpype/hosts/houdini/api/lib.py | 23 +++++++-------- .../publish/validate_review_colorspace.py | 29 ++++++++++--------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index bdcc368d7f..77ae929857 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -846,25 +846,22 @@ def get_houdini_color_settings(): project_settings = get_current_project_settings() color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: - color_settings.pop("enabled") - # Remove leading, and trailing whitespaces - color_settings["review_color_space"] = \ - color_settings["review_color_space"].strip() - return color_settings - return {} + # Remove leading, and trailing whitespaces + color_settings["review_color_space"] = \ + color_settings["review_color_space"].strip() + return color_settings def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. - if workfile settings are enabled, it will use the value + If workfile settings are enabled, it will use the value exposed in the settings. - if workfile settings are disabled, it will use the default - colorspace corresponding to the display & view of - the current Houdini session. + If the value exposed in the settings is empty, + it will use the default colorspace corresponding to + the display & view of the current Houdini session. Args: OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. @@ -884,8 +881,10 @@ def set_review_color_space(opengl_node, log=None): ) # Get view space for ociocolorspace parm. - view_space = get_houdini_color_settings().get("review_color_space") + color_settings = get_houdini_color_settings() + view_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa + # fall to default review color space if the setting is empty. if not view_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa view_space = get_default_display_view_colorspace() diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 783e67014c..e4356a741c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -66,17 +66,18 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, ) color_settings = get_houdini_color_settings() - if color_settings.get("override_review_color"): - return + # skip if houdini color settings are disabled + if color_settings["enabled"]: + view_space = color_settings["review_color_space"] + # skip if review color space setting is empty. + if view_space and \ + rop_node.evalParm("ociocolorspace") != view_space: - if rop_node.evalParm("ociocolorspace") != \ - color_settings["review_color_space"]: - - raise PublishValidationError( - "Invalid value: Colorspace name doesn't match studio " - "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" - .format(rop_node.path()) - ) + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match studio " + "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" + .format(rop_node.path()) + ) @classmethod def repair(cls, instance): @@ -84,12 +85,12 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, It sets ociocolorspace parameter. - if workfile settings are enabled, it will use the value + If workfile settings are enabled, it will use the value exposed in the settings. - if workfile settings are disabled, it will use the default - colorspace corresponding to the display & view of - the current Houdini session. + If the value exposed in the settings is empty, + it will use the default colorspace corresponding to + the display & view of the current Houdini session. """ opengl_node = hou.node(instance.data["instance_node"]) From f349df03407e67ac91e19292b250c00f58c9ddb7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 10:52:50 +0200 Subject: [PATCH 045/423] undo adding unnecessary setting --- .../defaults/project_settings/houdini.json | 3 +-- .../projects_schema/schema_project_houdini.json | 7 +------ server_addon/houdini/server/settings/imageio.py | 16 ++++++---------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 017682782d..7473e83275 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -26,8 +26,7 @@ "enabled": false, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB", - "override_review_color": false + "review_color_space": "" } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 70b6d63ee8..af87f4ff35 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -53,12 +53,7 @@ }, { "type": "label", - "label": "Allow artists to override review colorspace, therefore review color space validator\nwon't error if artists used another review colorspace." - }, - { - "type": "boolean", - "key": "override_review_color", - "label": "Override review colorspace" + "label": "Leave Review colorspace empty to use the default colorspace." } ] } diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index ab98152232..9e9c2a6092 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -40,13 +40,10 @@ class WorkfileImageIOModel(BaseSettingsModel): enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") default_view: str = Field(title="View") - review_color_space: str = Field(title="Review colorspace") - override_review_color: bool = Field( - False, - title="Override review colorspace", - description=("Allow artists to override review colorspace, " - "therefore review color space validator won't error " - "if artists used another review colorspace.") + review_color_space: str = Field( + title="Review colorspace", + description=("Leave Review colorspace empty to use the" + " default colorspace.") ) @@ -82,7 +79,6 @@ DEFAULT_IMAGEIO_SETTINGS = { "enabled": False, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB" - }, - "override_review_color": False + "review_color_space": "" + } } From fd648509626edafdc4f545e5b4ac7c9b50482d73 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 8 Nov 2023 17:43:38 +0200 Subject: [PATCH 046/423] add colorspace data to houdini review extractor --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 38808089ac..25203f444e 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -8,7 +8,8 @@ from openpype.hosts.houdini.api.lib import render_rop import hou -class ExtractOpenGL(publish.Extractor): +class ExtractOpenGL(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): order = pyblish.api.ExtractorOrder - 0.01 label = "Extract OpenGL" @@ -46,6 +47,12 @@ class ExtractOpenGL(publish.Extractor): "camera_name": instance.data.get("review_camera") } + colorspace = ropnode.evalParm("ociocolorspace") + # inject colorspace data + self.set_representation_colorspace( + representation, instance.context, + colorspace=colorspace + ) if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) From 205c0642d52ee9cf4f035b3418be75eaabc4848e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 8 Nov 2023 18:00:02 +0200 Subject: [PATCH 047/423] BigRoy's comment - use a conditional --- .../houdini/plugins/publish/extract_opengl.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 25203f444e..e6579e03a3 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -47,12 +47,14 @@ class ExtractOpenGL(publish.Extractor, "camera_name": instance.data.get("review_camera") } - colorspace = ropnode.evalParm("ociocolorspace") - # inject colorspace data - self.set_representation_colorspace( - representation, instance.context, - colorspace=colorspace - ) + if ropnode.evalParm("colorcorrect") == 2: # OpenColorIO enabled + colorspace = ropnode.evalParm("ociocolorspace") + # inject colorspace data + self.set_representation_colorspace( + representation, instance.context, + colorspace=colorspace + ) + if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) From 7a62d5d9b866b162a2f9d6f18ea947d171147ca1 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 9 Nov 2023 17:18:27 +0200 Subject: [PATCH 048/423] add colorspace data to houdini image sequence extractor --- .../houdini/plugins/publish/extract_composite.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_composite.py b/openpype/hosts/houdini/plugins/publish/extract_composite.py index 11cf83a46d..5047b719c9 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_composite.py +++ b/openpype/hosts/houdini/plugins/publish/extract_composite.py @@ -7,7 +7,8 @@ from openpype.hosts.houdini.api.lib import render_rop, splitext import hou -class ExtractComposite(publish.Extractor): +class ExtractComposite(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): order = pyblish.api.ExtractorOrder label = "Extract Composite (Image Sequence)" @@ -45,8 +46,11 @@ class ExtractComposite(publish.Extractor): "frameEnd": instance.data["frameEndHandle"], } - from pprint import pformat - - self.log.info(pformat(representation)) + # inject colorspace data + # It's always scene_linear (Houdini's default) + self.set_representation_colorspace( + representation, instance.context, + colorspace="scene_linear" + ) instance.data["representations"].append(representation) From 251291aa38e0cccc560ae9d460f4b0f4cc7453f4 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 12 Nov 2023 11:37:38 +0200 Subject: [PATCH 049/423] append existent view and display env vars --- .../houdini/hooks/set_default_display_and_view.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index ff55e6275d..54a1e39a87 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,5 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes - +import os class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini via OpenColorIO. @@ -33,8 +33,18 @@ class SetDefaultDisplayView(PreLaunchHook): ) return + # This is a way to get values specified by admins if they already + # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually + # using Ayon global env vars or Ayon app env vars or Ayon houdini tool + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get("OCIO_ACTIVE_DISPLAYS", "") + OCIO_ACTIVE_VIEWS = self.launch_context.env.get("OCIO_ACTIVE_VIEWS", "") + + # default_display and default_view default_display = houdini_color_settings["default_display"] + default_display = ":".join([default_display, OCIO_ACTIVE_DISPLAYS]) + default_view = houdini_color_settings["default_view"] + default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" From 38bf9c17c4c2c992d92d3a4adf511e9bee5ec0f2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 12 Nov 2023 11:40:05 +0200 Subject: [PATCH 050/423] resolve hound --- .../houdini/hooks/set_default_display_and_view.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 54a1e39a87..14f4fc5829 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,5 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -import os + class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini via OpenColorIO. @@ -35,9 +35,14 @@ class SetDefaultDisplayView(PreLaunchHook): # This is a way to get values specified by admins if they already # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually - # using Ayon global env vars or Ayon app env vars or Ayon houdini tool - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get("OCIO_ACTIVE_DISPLAYS", "") - OCIO_ACTIVE_VIEWS = self.launch_context.env.get("OCIO_ACTIVE_VIEWS", "") + # using Ayon global env vars or Ayon app env vars + # or Ayon houdini tool + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( + "OCIO_ACTIVE_DISPLAYS", "" + ) + OCIO_ACTIVE_VIEWS = self.launch_context.env.get( + "OCIO_ACTIVE_VIEWS", "" + ) # default_display and default_view default_display = houdini_color_settings["default_display"] From 87dc8ca57984a3e1fc5ea2505ee227677d9fd4d6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 11:52:13 +0200 Subject: [PATCH 051/423] fix a typo --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 14f4fc5829..82d402a86b 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -52,13 +52,13 @@ class SetDefaultDisplayView(PreLaunchHook): default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" + "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" .format(default_display) ) self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to config path: {}" + "Setting OCIO_ACTIVE_VIEWS environment to: {}" .format(default_view) ) self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From a7e2267eef6abeab2edbd876619deee377ff7e30 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:28:51 +0200 Subject: [PATCH 052/423] BigRoy's comment - Cleaner env var values --- .../hooks/set_default_display_and_view.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 82d402a86b..243f32fcf6 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -46,19 +46,23 @@ class SetDefaultDisplayView(PreLaunchHook): # default_display and default_view default_display = houdini_color_settings["default_display"] - default_display = ":".join([default_display, OCIO_ACTIVE_DISPLAYS]) - - default_view = houdini_color_settings["default_view"] - default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) - - self.log.info( + if default_display: + default_display = ":".join( + key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key + ) + self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" .format(default_display) - ) - self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + ) + self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display - self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to: {}" - .format(default_view) - ) - self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view + default_view = houdini_color_settings["default_view"] + if default_view: + default_view = ":".join( + key for key in [default_view, OCIO_ACTIVE_VIEWS] if key + ) + self.log.info( + "Setting OCIO_ACTIVE_VIEWS environment to: {}" + .format(default_view) + ) + self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From a82367603f95ee554aefd673f9fae30018ee434d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:37:00 +0200 Subject: [PATCH 053/423] update houdini workfile colorsettings help --- .../schemas/projects_schema/schema_project_houdini.json | 4 ---- server_addon/houdini/server/settings/imageio.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index af87f4ff35..45ed6024db 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -50,10 +50,6 @@ "type": "text", "key": "review_color_space", "label": "Review colorspace" - }, - { - "type": "label", - "label": "Leave Review colorspace empty to use the default colorspace." } ] } diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 9e9c2a6092..cc12a15c0d 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -40,11 +40,7 @@ class WorkfileImageIOModel(BaseSettingsModel): enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") default_view: str = Field(title="View") - review_color_space: str = Field( - title="Review colorspace", - description=("Leave Review colorspace empty to use the" - " default colorspace.") - ) + review_color_space: str = Field(title="Review colorspace") class HoudiniImageIOModel(BaseSettingsModel): From cb9e22ea6a33b319b8cd9a363fab3055cf2e3512 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:38:29 +0200 Subject: [PATCH 054/423] resolve hound --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 243f32fcf6..0cfc23c930 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -51,8 +51,8 @@ class SetDefaultDisplayView(PreLaunchHook): key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key ) self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" - .format(default_display) + "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" + .format(default_display) ) self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display From ebc3d95773212d00076374f0808ab14173a741ce Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Mon, 13 Nov 2023 15:16:01 +0200 Subject: [PATCH 055/423] BigRoy's suggestion - Better help text Co-authored-by: Roy Nieterau --- .../schemas/projects_schema/schema_project_houdini.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 45ed6024db..275fc54053 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -34,7 +34,7 @@ }, { "type": "label", - "label": "Render space in Houdini is always set to 'scene_linear' Role." + "label": "Empty values will be skipped, allowing any existing env vars to pass through as defined.\nNote: The render space in Houdini is always set to the 'scene_linear' role." }, { "type": "text", From be6d015b45fa67710dd5d7acb41d252f2ed2d97f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 15:32:52 +0200 Subject: [PATCH 056/423] BigRoy's suggestion - Better help text, Ayon settings --- server_addon/houdini/server/settings/imageio.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index cc12a15c0d..4fa5cac82d 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -35,7 +35,13 @@ class ImageIOFileRulesModel(BaseSettingsModel): class WorkfileImageIOModel(BaseSettingsModel): - """Render space in Houdini is always set to 'scene_linear' Role.""" + """Workfile settings help. + + Empty values will be skipped, allowing any existing env vars to + pass through as defined. + + Note: The render space in Houdini is + always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") From d3362eb192a5f4b8c0eaf5fe54eca660a5a8d79a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 15:33:49 +0200 Subject: [PATCH 057/423] bump houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 75cf7831c4..6232f7ab18 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.9" +__version__ = "0.2.10" From bb8b4c1881056d0cf052bea89757de83dbccb432 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 13 Nov 2023 17:55:26 +0000 Subject: [PATCH 058/423] `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 059/423] `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 060/423] `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 061/423] `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 062/423] `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 063/423] `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 064/423] `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 065/423] `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 066/423] `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 067/423] `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 068/423] `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 0c3ff0f4918884eb90b87f17f43c549fcdb56363 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 17 Nov 2023 21:38:10 +0200 Subject: [PATCH 069/423] Fabia's Comments --- openpype/hosts/houdini/api/lib.py | 12 +++++------ .../hooks/set_default_display_and_view.py | 21 +++++++++---------- .../plugins/publish/extract_composite.py | 15 +++++++------ .../publish/validate_review_colorspace.py | 10 ++++----- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 77ae929857..d506df429b 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -880,23 +880,23 @@ def set_review_color_space(opengl_node, log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # Get view space for ociocolorspace parm. + # Get review color space color_settings = get_houdini_color_settings() - view_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa + review_color_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa # fall to default review color space if the setting is empty. - if not view_space: + if not review_color_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - view_space = get_default_display_view_colorspace() + review_color_space = get_default_display_view_colorspace() opengl_node.setParms( - {"ociocolorspace": view_space} + {"ociocolorspace": review_color_space} ) self.log.debug( "'OCIO Colorspace' parm on '{}' has been set to " "the view color space '{}'" - .format(opengl_node, view_space) + .format(opengl_node, review_color_space) ) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 0cfc23c930..bb5f8bf71a 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -33,20 +33,15 @@ class SetDefaultDisplayView(PreLaunchHook): ) return - # This is a way to get values specified by admins if they already - # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually - # using Ayon global env vars or Ayon app env vars - # or Ayon houdini tool - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( - "OCIO_ACTIVE_DISPLAYS", "" - ) - OCIO_ACTIVE_VIEWS = self.launch_context.env.get( - "OCIO_ACTIVE_VIEWS", "" - ) + # 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' are checked + # as Admins can add them in Ayon env vars or Ayon tools. - # default_display and default_view default_display = houdini_color_settings["default_display"] if default_display: + # get 'OCIO_ACTIVE_DISPLAYS' value if exists. + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( + "OCIO_ACTIVE_DISPLAYS", "" + ) default_display = ":".join( key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key ) @@ -58,6 +53,10 @@ class SetDefaultDisplayView(PreLaunchHook): default_view = houdini_color_settings["default_view"] if default_view: + # get 'OCIO_ACTIVE_VIEWS' value if exists. + OCIO_ACTIVE_VIEWS = self.launch_context.env.get( + "OCIO_ACTIVE_VIEWS", "" + ) default_view = ":".join( key for key in [default_view, OCIO_ACTIVE_VIEWS] if key ) diff --git a/openpype/hosts/houdini/plugins/publish/extract_composite.py b/openpype/hosts/houdini/plugins/publish/extract_composite.py index 5047b719c9..a3194026bf 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_composite.py +++ b/openpype/hosts/houdini/plugins/publish/extract_composite.py @@ -46,11 +46,14 @@ class ExtractComposite(publish.Extractor, "frameEnd": instance.data["frameEndHandle"], } - # inject colorspace data - # It's always scene_linear (Houdini's default) - self.set_representation_colorspace( - representation, instance.context, - colorspace="scene_linear" - ) + if ext == "exr": + # Inject colorspace with 'scene_linear' as that's the + # default Houdini working colorspace and all extracted + # OpenEXR images should be in that colorspace. + # https://www.sidefx.com/docs/houdini/render/linear.html#image-formats + self.set_representation_colorspace( + representation, instance.context, + colorspace="scene_linear" + ) instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index e4356a741c..db6533d4ec 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -16,7 +16,7 @@ import hou class SetReviewColorSpaceAction(RepairAction): - label = "Set Review Color Space" + label = "Set Default Review Color Space" icon = "mdi.monitor" @@ -68,10 +68,10 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, color_settings = get_houdini_color_settings() # skip if houdini color settings are disabled if color_settings["enabled"]: - view_space = color_settings["review_color_space"] + review_color_space = color_settings["review_color_space"] # skip if review color space setting is empty. - if view_space and \ - rop_node.evalParm("ociocolorspace") != view_space: + if review_color_space and \ + rop_node.evalParm("ociocolorspace") != review_color_space: raise PublishValidationError( "Invalid value: Colorspace name doesn't match studio " @@ -81,7 +81,7 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - """Set Default View Space Action. + """Set Default Review Space Action. It sets ociocolorspace parameter. From e700f2878d543ea2564a578e8260374ebf951790 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Mon, 20 Nov 2023 17:34:55 +0000 Subject: [PATCH 070/423] `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 071/423] `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 072/423] `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 073/423] `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 e45df04e5c63a998167863832d73838d879bb98e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 23 Nov 2023 09:24:15 +0200 Subject: [PATCH 074/423] Jeza Comment - Better Labels --- .../schemas/projects_schema/schema_project_houdini.json | 4 ++-- server_addon/houdini/server/settings/imageio.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 275fc54053..9aef7c3525 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -39,12 +39,12 @@ { "type": "text", "key": "default_display", - "label": "Display" + "label": "Default active displays" }, { "type": "text", "key": "default_view", - "label": "View" + "label": "Default active views" }, { "type": "text", diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 4fa5cac82d..694d653720 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -44,8 +44,8 @@ class WorkfileImageIOModel(BaseSettingsModel): always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") - default_display: str = Field(title="Display") - default_view: str = Field(title="View") + default_display: str = Field(title="Default active displays") + default_view: str = Field(title="Default active views") review_color_space: str = Field(title="Review colorspace") From ab04429f5d6424ceacfc23705ec5008b157fbe53 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 24 Nov 2023 16:23:09 +0200 Subject: [PATCH 075/423] BigRoy's comment - add settings tip --- .../houdini/server/settings/imageio.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 694d653720..18ebc2c0a1 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -44,9 +44,22 @@ class WorkfileImageIOModel(BaseSettingsModel): always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") - default_display: str = Field(title="Default active displays") - default_view: str = Field(title="Default active views") - review_color_space: str = Field(title="Review colorspace") + default_display: str = Field( + title="Default active displays", + description="It behaves like the 'OCIO_ACTIVE_DISPLAYS' env var," + " Colon-separated list of displays, e.g ACES:P3" + ) + default_view: str = Field( + title="Default active views", + description="It behaves like the 'OCIO_ACTIVE_VIEWS' env var," + " Colon-separated list of views, e.g sRGB:DCDM" + ) + review_color_space: str = Field( + title="Review colorspace", + description="It exposes OCIO Colorspace parameter in opengl nodes." + "if left empty, Ayon will figure out the default " + "colorspace using your default display and default view." + ) class HoudiniImageIOModel(BaseSettingsModel): From 0e905f373f34f8767e73575ef2368d1dad035550 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Tue, 12 Dec 2023 10:00:30 +0000 Subject: [PATCH 076/423] `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 c40b7139d07861ee869f67d209810d867a37d0e6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 14 Dec 2023 14:49:05 +0200 Subject: [PATCH 077/423] bump houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 6232f7ab18..5635676f6b 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.10" +__version__ = "0.2.11" From b8f89e141c71327d032e1463ab8d3f523c5ceeb2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 14:51:35 +0200 Subject: [PATCH 078/423] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Minkiu=20Commen?= =?UTF-8?q?t=20-=20refactor=20repeated=20code=20into=20a=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/set_default_display_and_view.py | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index bb5f8bf71a..3462ab1647 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -39,29 +39,20 @@ class SetDefaultDisplayView(PreLaunchHook): default_display = houdini_color_settings["default_display"] if default_display: # get 'OCIO_ACTIVE_DISPLAYS' value if exists. - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( - "OCIO_ACTIVE_DISPLAYS", "" - ) - default_display = ":".join( - key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key - ) - self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" - .format(default_display) - ) - self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + self._set_context_env("OCIO_ACTIVE_DISPLAYS", default_display) default_view = houdini_color_settings["default_view"] if default_view: # get 'OCIO_ACTIVE_VIEWS' value if exists. - OCIO_ACTIVE_VIEWS = self.launch_context.env.get( - "OCIO_ACTIVE_VIEWS", "" + self._set_context_env("OCIO_ACTIVE_VIEWS", default_view) + + def _set_context_env(self, env_var, default_value): + env_value = self.launch_context.env.get(env_var, "") + new_value = ":".join( + key for key in [default_value, env_value] if key ) - default_view = ":".join( - key for key in [default_view, OCIO_ACTIVE_VIEWS] if key + self.log.info( + "Setting {} environment to: {}" + .format(env_var, new_value) ) - self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to: {}" - .format(default_view) - ) - self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view + self.launch_context.env[env_var] = new_value From 71fe47dc411fa3a0a50fc951b807f868986d7e5f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 14:54:14 +0200 Subject: [PATCH 079/423] =?UTF-8?q?=F0=9F=A6=B4=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../houdini/hooks/set_default_display_and_view.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 3462ab1647..4f19dcfcd6 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -49,10 +49,10 @@ class SetDefaultDisplayView(PreLaunchHook): def _set_context_env(self, env_var, default_value): env_value = self.launch_context.env.get(env_var, "") new_value = ":".join( - key for key in [default_value, env_value] if key - ) + key for key in [default_value, env_value] if key + ) self.log.info( - "Setting {} environment to: {}" - .format(env_var, new_value) - ) + "Setting {} environment to: {}" + .format(env_var, new_value) + ) self.launch_context.env[env_var] = new_value From f2f83454b56c755567526c64618c43808cf5660b Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 4 Jan 2024 17:07:51 +0000 Subject: [PATCH 080/423] `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 ec6a8b2a96eae0813e9a853495d4dfc813562254 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:21:21 +0200 Subject: [PATCH 081/423] =?UTF-8?q?=E2=9E=96=20Remove=20unnecessary=20func?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 36 ++++--------------- .../houdini/plugins/create/create_review.py | 16 ++++++++- .../publish/validate_review_colorspace.py | 24 ++++++++----- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 4f39677afc..89e5309f29 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -866,35 +866,17 @@ def get_current_context_template_data_with_asset_data(): return template_data -def get_houdini_color_settings(): - """Get Houdini working file color settings. - - Returns: - Dict: The dictionary contains the Houdini working file color settings - if the settings are enabled, otherwise it is an empty dictionary. - """ - - project_settings = get_current_project_settings() - color_settings = project_settings["houdini"]["imageio"]["workfile"] - - # Remove leading, and trailing whitespaces - color_settings["review_color_space"] = \ - color_settings["review_color_space"].strip() - return color_settings - - -def set_review_color_space(opengl_node, log=None): +def set_review_color_space(opengl_node, review_color_space="", log=None): """Set ociocolorspace parameter for the given OpenGL node. - If workfile settings are enabled, it will use the value - exposed in the settings. - - If the value exposed in the settings is empty, - it will use the default colorspace corresponding to - the display & view of the current Houdini session. + Set `ociocolorspace` parameter of the given OpenGl node + to to the given review_color_space value. + If review_color_space is empty, a default colorspace corresponding to + the display & view of the current Houdini session will be used. Args: - OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. + opengl_node (hou.Node): ROP node to set its ociocolorspace parameter. + review_color_space (str): Colorspace value for ociocolorspace parameter. log (logging.Logger): Logger to log to. """ @@ -910,10 +892,6 @@ def set_review_color_space(opengl_node, log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # Get review color space - color_settings = get_houdini_color_settings() - review_color_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa - # fall to default review color space if the setting is empty. if not review_color_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 775babbab6..22a5dbd817 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,6 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef +from openpype.settings import get_current_project_settings from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -14,6 +15,11 @@ class CreateReview(plugin.HoudiniCreator): label = "Review" family = "review" icon = "video-camera" + workfile_color_settings = {} + + def apply_settings(self, project_settings): + super(CreateReview, self).apply_settings(project_settings) + self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] def create(self, subset_name, instance_data, pre_create_data): @@ -87,7 +93,15 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - set_review_color_space(instance_node, log=self.log) + workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] + review_color_space = workfile_color_settings["enabled"] and \ + workfile_color_settings["review_color_space"] + + set_review_color_space( + instance_node, + review_color_space, + log=self.log) + to_lock = ["id", "family"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index db6533d4ec..528f4ae242 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -6,10 +6,7 @@ from openpype.pipeline import ( ) from openpype.pipeline.publish import RepairAction from openpype.hosts.houdini.api.action import SelectROPAction -from openpype.hosts.houdini.api.lib import ( - get_houdini_color_settings, - set_review_color_space -) +from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -65,10 +62,11 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, .format(rop_node.path()) ) - color_settings = get_houdini_color_settings() - # skip if houdini color settings are disabled - if color_settings["enabled"]: - review_color_space = color_settings["review_color_space"] + workfile_color_settings = instance.context.data["project_settings"]["houdini"]["imageio"]["workfile"] # noqa + # skip if houdini workfile color settings are disabled + if workfile_color_settings["enabled"]: + review_color_space = workfile_color_settings["review_color_space"] + # skip if review color space setting is empty. if review_color_space and \ rop_node.evalParm("ociocolorspace") != review_color_space: @@ -94,4 +92,12 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, """ opengl_node = hou.node(instance.data["instance_node"]) - set_review_color_space(opengl_node, log=cls.log) + + workfile_color_settings = instance.context.data["project_settings"]["houdini"]["imageio"]["workfile"] # noqa + review_color_space = workfile_color_settings["enabled"] and \ + workfile_color_settings["review_color_space"] + + set_review_color_space( + opengl_node, + review_color_space, + log=cls.log) From 10cdd11270aa837d45e404d60ffc4ae8da3e5fe6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:24:34 +0200 Subject: [PATCH 082/423] =?UTF-8?q?=F0=9F=90=BE=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 4 ++-- openpype/hosts/houdini/plugins/create/create_review.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 89e5309f29..51d490adc9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -875,8 +875,8 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): the display & view of the current Houdini session will be used. Args: - opengl_node (hou.Node): ROP node to set its ociocolorspace parameter. - review_color_space (str): Colorspace value for ociocolorspace parameter. + opengl_node (hou.Node): ROP node to set its ociocolorspace parm. + review_color_space (str): Colorspace value for ociocolorspace parm. log (logging.Logger): Logger to log to. """ diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 22a5dbd817..8e08c76c9a 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -93,7 +93,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] + workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] # noqa review_color_space = workfile_color_settings["enabled"] and \ workfile_color_settings["review_color_space"] From eb123e1fc028a5facca643df685bd6da12f8e81a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:25:16 +0200 Subject: [PATCH 083/423] =?UTF-8?q?=F0=9F=90=BE=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/plugins/create/create_review.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 8e08c76c9a..f758b8529f 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,7 +2,6 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef -from openpype.settings import get_current_project_settings from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou From 05948a968a70db8f81774af62a6ba3f306309a70 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:26:30 +0200 Subject: [PATCH 084/423] =?UTF-8?q?=F0=9F=90=BE=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index f758b8529f..6c82afc4e5 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -18,7 +18,7 @@ class CreateReview(plugin.HoudiniCreator): def apply_settings(self, project_settings): super(CreateReview, self).apply_settings(project_settings) - self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] + self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] # noqa def create(self, subset_name, instance_data, pre_create_data): From e0e2cb06dd8d85ec73482285a75606bcf26b99ea Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:20:19 +0100 Subject: [PATCH 085/423] 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 086/423] 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 087/423] 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 088/423] 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 089/423] `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 090/423] `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 75b6dfe5efc606ed123531faf08d0300c6f838c2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Jan 2024 12:08:44 +0200 Subject: [PATCH 091/423] bump Houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 5635676f6b..b5c9b6cb71 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.11" +__version__ = "0.2.12" From 97e8ccd5c5209f11e9c822fd573e758abd094d39 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 31 Jan 2024 15:29:48 +0000 Subject: [PATCH 092/423] `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 093/423] `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 094/423] `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 095/423] `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 096/423] `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 097/423] `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 098/423] `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 099/423] 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 100/423] 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 101/423] 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 102/423] 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 103/423] `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 104/423] 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 105/423] 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 106/423] 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 107/423] 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 108/423] 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 109/423] 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 110/423] 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 111/423] 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 112/423] 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 113/423] 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 114/423] [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 115/423] 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 116/423] 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 117/423] 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 118/423] [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 119/423] 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 120/423] 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 121/423] 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 122/423] 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 123/423] 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 124/423] 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 125/423] [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 126/423] 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 127/423] 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 128/423] 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 129/423] [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 130/423] 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 131/423] 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 132/423] 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 133/423] 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 134/423] [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 135/423] 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 52e88951f4ced27620c6f34a220bc5ce0c622d17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 21 Feb 2024 11:53:41 +0100 Subject: [PATCH 136/423] Traypublisher CSV ingest ayon conversion kickof Traypublisher CSV ingest ayon conversion kickoff Added functionality for ingesting CSV files into projects. Includes commands to ingest CSV data, publish the content, and create instances based on the CSV data. --- client/ayon_core/hosts/traypublisher/addon.py | 61 +- .../hosts/traypublisher/csv_publish.py | 76 ++ .../plugins/create/create_csv_ingest.py | 686 ++++++++++++++++++ .../collect_csv_ingest_instance_data.py | 36 + .../plugins/publish/extract_csv_file.py | 31 + .../publish/validate_existing_version.py | 1 + .../plugins/publish/validate_frame_ranges.py | 2 + client/ayon_core/plugins/publish/integrate.py | 3 +- 8 files changed, 894 insertions(+), 2 deletions(-) create mode 100644 client/ayon_core/hosts/traypublisher/csv_publish.py create mode 100644 client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py create mode 100644 client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py create mode 100644 client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py diff --git a/client/ayon_core/hosts/traypublisher/addon.py b/client/ayon_core/hosts/traypublisher/addon.py index 70bdfe9a64..f3884aedfe 100644 --- a/client/ayon_core/hosts/traypublisher/addon.py +++ b/client/ayon_core/hosts/traypublisher/addon.py @@ -1,6 +1,6 @@ import os -from ayon_core.lib import get_ayon_launcher_args +from pathlib import Path from ayon_core.lib.execute import run_detached_process from ayon_core.addon import ( click_wrap, @@ -57,3 +57,62 @@ def launch(): from ayon_core.tools import traypublisher traypublisher.main() + + +@cli_main.command() +@click_wrap.option( + "--csv-filepath", + help="Full path to CSV file with data", + type=str, + required=True +) +@click_wrap.option( + "--project-name", + help="Project name in which the context will be used", + type=str, + required=True +) +@click_wrap.option( + "--asset-name", + help="Asset name in which the context will be used", + type=str, + required=True +) +@click_wrap.option( + "--task-name", + help="Task name under Asset in which the context will be used", + type=str, + required=False +) +@click_wrap.option( + "--ignore-validators", + help="Option to ignore validators", + type=bool, + is_flag=True, + required=False +) +def ingestcsv( + csv_filepath, + project_name, + asset_name, + task_name, + ignore_validators +): + """Ingest CSV file into project. + + This command will ingest CSV file into project. CSV file must be in + specific format. See documentation for more information. + """ + from .csv_publish import csvpublish + + # use Path to check if csv_filepath exists + if not Path(csv_filepath).exists(): + raise FileNotFoundError(f"File {csv_filepath} does not exist.") + + csvpublish( + csv_filepath, + project_name, + asset_name, + task_name, + ignore_validators + ) diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py new file mode 100644 index 0000000000..f8eed2f2c5 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -0,0 +1,76 @@ +import os + +import pyblish.api +import pyblish.util + +from ayon_core.client import get_asset_by_name +from ayon_core.lib.attribute_definitions import FileDefItem +from ayon_core.pipeline import install_host +from ayon_core.pipeline.create import CreateContext + +from ayon_core.hosts.traypublisher.api import TrayPublisherHost + + +def csvpublish( + csv_filepath, + project_name, + asset_name, + task_name=None, + ignore_validators=False +): + """Publish CSV file. + + Args: + csv_filepath (str): Path to CSV file. + project_name (str): Project name. + asset_name (str): Asset name. + task_name (Optional[str]): Task name. + ignore_validators (Optional[bool]): Option to ignore validators. + """ + + # initialization of host + host = TrayPublisherHost() + install_host(host) + + # setting host context into project + host.set_project_name(project_name) + + # add asset context to environment + # TODO: perhaps this can be done in a better way? + os.environ.update({ + "AVALON_PROJECT": project_name, + "AVALON_ASSET": asset_name, + "AVALON_TASK": task_name or "" + }) + + # form precreate data with field values + file_field = FileDefItem.from_paths([csv_filepath], False).pop().to_dict() + precreate_data = { + "csv_filepath_data": file_field, + } + + # create context initialization + create_context = CreateContext(host, headless=True) + asset_doc = get_asset_by_name( + project_name, + asset_name + ) + + create_context.create( + "io.openpype.creators.traypublisher.csv_ingest", + "Main", + asset_doc=asset_doc, + task_name=task_name, + pre_create_data=precreate_data, + ) + + # publishing context initialization + pyblish_context = pyblish.api.Context() + pyblish_context.data["create_context"] = create_context + + # redefine targets (skip 'local' to disable validators) + if ignore_validators: + targets = ["default", "ingest"] + + # publishing + pyblish.util.publish(context=pyblish_context, targets=targets) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py new file mode 100644 index 0000000000..aa986657dc --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -0,0 +1,686 @@ +import os +import re +import csv +import clique +from copy import deepcopy, copy + +from ayon_core.client import get_asset_by_name +from ayon_core.pipeline.create import get_subset_name +from ayon_core.pipeline import CreatedInstance +from ayon_core.lib import FileDef, BoolDef +from ayon_core.lib.transcoding import ( + VIDEO_EXTENSIONS, IMAGE_EXTENSIONS +) + +from ayon_core.hosts.traypublisher.api.plugin import ( + TrayPublishCreator +) + + +class IngestCSV(TrayPublishCreator): + """CSV ingest creator class""" + + icon = "fa.file" + + label = "CSV Ingest" + family = "csv_ingest_file" + identifier = "io.ayon_core.creators.traypublisher.csv_ingest" + + default_variants = ["Main"] + + description = "Ingest products' data from CSV file" + detailed_description = """ +Ingest products' data from CSV file following column and representation +configuration in project settings. +""" + + # Position in the list of creators. + order = 10 + + # settings for this creator + columns_config = {} + representations_config = {} + + + def create(self, subset_name, instance_data, pre_create_data): + """Create an product from each row found in the CSV. + + Args: + subset_name (str): The subset name. + instance_data (dict): The instance data. + pre_create_data (dict): + """ + + csv_filepath_data = pre_create_data.get("csv_filepath_data", {}) + + folder = csv_filepath_data.get("directory", "") + if not os.path.exists(folder): + raise FileNotFoundError( + f"Directory '{folder}' does not exist." + ) + filename = csv_filepath_data.get("filenames", []) + self._process_csv_file(subset_name, instance_data, folder, filename[0]) + + def _process_csv_file( + self, subset_name, instance_data, staging_dir, filename): + """Process CSV file. + + Args: + subset_name (str): The subset name. + instance_data (dict): The instance data. + staging_dir (str): The staging directory. + filename (str): The filename. + """ + + # create new instance from the csv file via self function + self._pass_data_to_csv_instance( + instance_data, + staging_dir, + filename + ) + + csv_instance = CreatedInstance( + self.family, subset_name, instance_data, self + ) + self._store_new_instance(csv_instance) + + csv_instance["csvFileData"] = { + "filename": filename, + "staging_dir": staging_dir, + } + + # from special function get all data from csv file and convert them + # to new instances + csv_data_for_instances = self._get_data_from_csv( + staging_dir, filename) + + # create instances from csv data via self function + self._create_instances_from_csv_data( + csv_data_for_instances, staging_dir + ) + + def _create_instances_from_csv_data( + self, + csv_data_for_instances, + staging_dir + ): + """Create instances from csv data""" + + for asset_name, _data in csv_data_for_instances.items(): + asset_doc = _data["asset_doc"] + products = _data["products"] + + for instance_name, product_data in products.items(): + # get important instance variables + task_name = product_data["task_name"] + variant = product_data["variant"] + product_type = product_data["product_type"] + version = product_data["version"] + + # create subset/product name + product_name = get_subset_name( + product_type, + variant, + task_name, + asset_doc, + ) + + # make sure frame start/end is inherited from csv columns + # expected frame range data are handles excluded + for _, repre_data in product_data["representations"].items(): # noqa: E501 + frame_start = repre_data["frameStart"] + frame_end = repre_data["frameEnd"] + handle_start = repre_data["handleStart"] + handle_end = repre_data["handleEnd"] + fps = repre_data["fps"] + break + + # try to find any version comment in representation data + version_comment = next( + iter( + repre_data["comment"] + for _, repre_data in product_data["representations"].items() # noqa: E501 + if repre_data["comment"] + ), + None + ) + + # try to find any slate switch in representation data + slate_exists = any( + repre_data["slate"] + for _, repre_data in product_data["representations"].items() # noqa: E501 + ) + + # get representations from product data + representations = product_data["representations"] + label = f"{asset_name}_{product_name}_v{version:>03}" + + families = ["csv_ingest"] + if slate_exists: + # adding slate to families mainly for loaders to be able + # to filter out slates + families.append("slate") + + # make product data + product_data = { + "name": instance_name, + "asset": asset_name, + "families": families, + "label": label, + "task": task_name, + "variant": variant, + "source": "csv", + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end, + "fps": fps, + "version": version, + "comment": version_comment, + } + + # create new instance + new_instance = CreatedInstance( + product_type, product_name, product_data, self + ) + self._store_new_instance(new_instance) + + if not new_instance.get("prepared_data_for_repres"): + new_instance["prepared_data_for_repres"] = [] + + base_thumbnail_repre_data = { + "name": "thumbnail", + "ext": None, + "files": None, + "stagingDir": None, + "stagingDir_persistent": True, + "tags": ["thumbnail", "delete"], + } + # need to populate all thumbnails for all representations + # so we can check if unique thumbnail per representation + # is needed + thumbnails = [ + repre_data["thumbnailPath"] + for repre_data in representations.values() + if repre_data["thumbnailPath"] + ] + multiple_thumbnails = len(set(thumbnails)) > 1 + explicit_output_name = None + thumbnails_processed = False + for filepath, repre_data in representations.items(): + # check if any review derivate tag is present + reviewable = any( + tag for tag in repre_data.get("tags", []) + # tag can be `ftrackreview` or `review` + if "review" in tag + ) + # since we need to populate multiple thumbnails as + # representation with outputName for (Ftrack instance + # integrator) pairing with reviewable video representations + if ( + thumbnails + and multiple_thumbnails + and reviewable + ): + # multiple unique thumbnails per representation needs + # grouping by outputName + # mainly used in Ftrack instance integrator + explicit_output_name = repre_data["representationName"] + relative_thumbnail_path = repre_data["thumbnailPath"] + # representation might not have thumbnail path + # so ignore this one + if not relative_thumbnail_path: + continue + thumb_dir, thumb_file = \ + self._get_refactor_thumbnail_path( + staging_dir, relative_thumbnail_path) + filename, ext = os.path.splitext(thumb_file) + thumbnail_repr_data = deepcopy( + base_thumbnail_repre_data) + thumbnail_repr_data.update({ + "name": "thumbnail_{}".format(filename), + "ext": ext[1:], + "files": thumb_file, + "stagingDir": thumb_dir, + "outputName": explicit_output_name, + }) + new_instance["prepared_data_for_repres"].append( + ("_thumbnail_", thumbnail_repr_data) + ) + elif ( + thumbnails + and not multiple_thumbnails + and not thumbnails_processed + or not reviewable + ): + if not thumbnails: + continue + # here we will use only one thumbnail for + # all representations + relative_thumbnail_path = repre_data["thumbnailPath"] + if not relative_thumbnail_path: + relative_thumbnail_path = thumbnails.pop() + thumb_dir, thumb_file = \ + self._get_refactor_thumbnail_path( + staging_dir, relative_thumbnail_path) + _, ext = os.path.splitext(thumb_file) + thumbnail_repr_data = deepcopy( + base_thumbnail_repre_data) + thumbnail_repr_data.update({ + "ext": ext[1:], + "files": thumb_file, + "stagingDir": thumb_dir + }) + new_instance["prepared_data_for_repres"].append( + ("_thumbnail_", thumbnail_repr_data) + ) + thumbnails_processed = True + + # get representation data + representation_data = self._get_representation_data( + filepath, repre_data, staging_dir, + explicit_output_name + ) + + new_instance["prepared_data_for_repres"].append( + (repre_data["colorspace"], representation_data) + ) + + def _get_refactor_thumbnail_path( + self, staging_dir, relative_thumbnail_path): + thumbnail_abs_path = os.path.join( + staging_dir, relative_thumbnail_path) + return os.path.split( + thumbnail_abs_path) + + def _get_representation_data( + self, filepath, repre_data, staging_dir, explicit_output_name=None + ): + """Get representation data + + Args: + filepath (str): Filepath to representation file. + repre_data (dict): Representation data from CSV file. + staging_dir (str): Staging directory. + explicit_output_name (Optional[str]): Explicit output name. + For grouping purposes with reviewable components. + Defaults to None. + """ + + # get extension of file + basename = os.path.basename(filepath) + _, extension = os.path.splitext(filepath) + + # validate filepath is having correct extension based on output + config_repre_data = self.representations_config["representations"] + repre_name = repre_data["representationName"] + if repre_name not in config_repre_data: + raise KeyError( + f"Representation '{repre_name}' not found " + "in config representation data." + ) + validate_extensions = config_repre_data[repre_name]["extensions"] + if extension not in validate_extensions: + raise TypeError( + f"File extension '{extension}' not valid for " + f"output '{validate_extensions}'." + ) + + is_sequence = (extension in IMAGE_EXTENSIONS) + # convert ### string in file name to %03d + # this is for correct frame range validation + # example: file.###.exr -> file.%03d.exr + if "#" in basename: + padding = len(basename.split("#")) - 1 + basename = basename.replace("#" * padding, f"%0{padding}d") + is_sequence = True + + # make absolute path to file + absfilepath = os.path.normpath(os.path.join(staging_dir, filepath)) + dirname = os.path.dirname(absfilepath) + + # check if dirname exists + if not os.path.isdir(dirname): + raise NotADirectoryError( + f"Directory '{dirname}' does not exist." + ) + + # collect all data from dirname + paths_for_collection = [] + for file in os.listdir(dirname): + filepath = os.path.join(dirname, file) + paths_for_collection.append(filepath) + + collections, _ = clique.assemble(paths_for_collection) + + if collections: + collections = collections[0] + else: + if is_sequence: + raise ValueError( + f"No collections found in directory '{dirname}'." + ) + + frame_start = None + frame_end = None + if is_sequence: + files = [os.path.basename(file) for file in collections] + frame_start = list(collections.indexes)[0] + frame_end = list(collections.indexes)[-1] + else: + files = basename + + tags = deepcopy(repre_data["tags"]) + # if slate in repre_data is True then remove one frame from start + if repre_data["slate"]: + tags.append("has_slate") + + # get representation data + representation_data = { + "name": repre_name, + "ext": extension[1:], + "files": files, + "stagingDir": dirname, + "stagingDir_persistent": True, + "tags": tags, + } + if extension in VIDEO_EXTENSIONS: + representation_data.update({ + "fps": repre_data["fps"], + "outputName": repre_name, + }) + + if explicit_output_name: + representation_data["outputName"] = explicit_output_name + + if frame_start: + representation_data["frameStart"] = frame_start + if frame_end: + representation_data["frameEnd"] = frame_end + + return representation_data + + def _get_data_from_csv( + self, package_dir, filename + ): + """Generate instances from the csv file""" + # get current project name and code from context.data + project_name = self.create_context.get_current_project_name() + + csv_file_path = os.path.join( + package_dir, filename + ) + + # make sure csv file contains columns from following list + required_columns = [ + name for name, value in self.columns_config["columns"].items() + if value["required"] + ] + # get data from csv file + with open(csv_file_path, "r") as csv_file: + csv_reader = csv.DictReader( + csv_file, delimiter=self.columns_config["csv_delimiter"]) + + # fix fieldnames + # sometimes someone can keep extra space at the start or end of + # the column name + all_columns = [ + " ".join(column.rsplit()) for column in csv_reader.fieldnames] + # return back fixed fieldnames + csv_reader.fieldnames = all_columns + + # check if csv file contains all required columns + if any(column not in all_columns for column in required_columns): + raise KeyError( + f"Missing required columns: {required_columns}" + ) + + csv_data = {} + # get data from csv file + for row in csv_reader: + # Get required columns first + context_asset_name = self._get_row_value_with_validation( + "Folder Context", row) + task_name = self._get_row_value_with_validation( + "Task Name", row) + version = self._get_row_value_with_validation( + "Version", row) + + # Get optional columns + variant = self._get_row_value_with_validation( + "Variant", row) + product_type = self._get_row_value_with_validation( + "Product Type", row) + + pre_product_name = ( + f"{task_name}{variant}{product_type}" + f"{version}".replace(" ", "").lower() + ) + + # get representation data + filename, representation_data = \ + self._get_representation_row_data(row) + + # get all csv data into one dict and make sure there are no + # duplicates data are already validated and sorted under + # correct existing asset also check if asset exists and if + # task name is valid task in asset doc and representations + # are distributed under products following variants + if context_asset_name not in csv_data: + asset_doc = get_asset_by_name( + project_name, context_asset_name) + + # make sure asset exists + if not asset_doc: + raise ValueError( + f"Asset '{context_asset_name}' not found." + ) + # check if task name is valid task in asset doc + if task_name not in asset_doc["data"]["tasks"]: + raise ValueError( + f"Task '{task_name}' not found in asset doc." + ) + + csv_data[context_asset_name] = { + "asset_doc": asset_doc, + "products": { + pre_product_name: { + "task_name": task_name, + "variant": variant, + "product_type": product_type, + "version": version, + "representations": { + filename: representation_data, + }, + } + } + } + else: + asset_doc = csv_data[context_asset_name]["asset_doc"] + csv_products = csv_data[context_asset_name]["products"] + if pre_product_name not in csv_products: + csv_products[pre_product_name] = { + "task_name": task_name, + "variant": variant, + "product_type": product_type, + "version": version, + "representations": { + filename: representation_data, + }, + } + else: + csv_representations = \ + csv_products[pre_product_name]["representations"] + if filename in csv_representations: + raise ValueError( + f"Duplicate filename '{filename}' in csv file." + ) + csv_representations[filename] = representation_data + + return csv_data + + def _get_representation_row_data(self, row_data): + """Get representation row data""" + # Get required columns first + file_path = self._get_row_value_with_validation( + "File Path", row_data) + frame_start = self._get_row_value_with_validation( + "Frame Start", row_data) + frame_end = self._get_row_value_with_validation( + "Frame End", row_data) + handle_start = self._get_row_value_with_validation( + "Handle Start", row_data) + handle_end = self._get_row_value_with_validation( + "Handle End", row_data) + fps = self._get_row_value_with_validation( + "FPS", row_data) + + # Get optional columns + thumbnail_path = self._get_row_value_with_validation( + "Thumbnail", row_data) + colorspace = self._get_row_value_with_validation( + "Colorspace", row_data) + comment = self._get_row_value_with_validation( + "Version Comment", row_data) + repre = self._get_row_value_with_validation( + "Representation", row_data) + slate_exists = self._get_row_value_with_validation( + "Slate Exists", row_data) + repre_tags = self._get_row_value_with_validation( + "Representation Tags", row_data) + + # convert tags value to list + tags_list = copy(self.representations_config["default_tags"]) + if repre_tags: + tags_list = [] + tags_delimiter = self.representations_config["tags_delimiter"] + # strip spaces from repre_tags + if tags_delimiter in repre_tags: + tags = repre_tags.split(tags_delimiter) + for _tag in tags: + tags_list.append(("".join(_tag.strip())).lower()) + else: + tags_list.append(repre_tags) + + representation_data = { + "colorspace": colorspace, + "comment": comment, + "representationName": repre, + "slate": slate_exists, + "tags": tags_list, + "thumbnailPath": thumbnail_path, + "frameStart": int(frame_start), + "frameEnd": int(frame_end), + "handleStart": int(handle_start), + "handleEnd": int(handle_end), + "fps": float(fps), + } + + return file_path, representation_data + + def _get_row_value_with_validation( + self, column_name, row_data, default_value=None + ): + """Get row value with validation""" + columns_config = self.columns_config["columns"] + # get column data from column config + column_data = columns_config.get(column_name) + if not column_data: + raise KeyError( + f"Column '{column_name}' not found in column config." + ) + + # get column value from row + column_value = row_data.get(column_name) + column_required = column_data["required"] + + # check if column value is not empty string and column is required + if column_value == "" and column_required: + raise ValueError( + f"Value in column '{column_name}' is required." + ) + + # get column type + column_type = column_data["type"] + # get column validation regex + column_validation = column_data["validate"] + # get column default value + column_default = default_value or column_data["default"] + + if column_type in ["number", "decimal"] and column_default == 0: + column_default = None + + # check if column value is not empty string + if column_value == "": + # set default value if column value is empty string + column_value = column_default + + # set column value to correct type following column type + if column_type == "number" and column_value is not None: + column_value = int(column_value) + elif column_type == "decimal" and column_value is not None: + column_value = float(column_value) + elif column_type == "bool": + column_value = column_value in ["true", "True"] + + # check if column value matches validation regex + if ( + column_value is not None and + not re.match(str(column_validation), str(column_value)) + ): + raise ValueError( + f"Column '{column_name}' value '{column_value}' " + f"does not match validation regex '{column_validation}' \n" + f"Row data: {row_data} \n" + f"Column data: {column_data}" + ) + + return column_value + + def _pass_data_to_csv_instance( + self, instance_data, staging_dir, filename + ): + """Pass CSV representation file to instance data""" + + representation = { + "name": "csv", + "ext": "csv", + "files": filename, + "stagingDir": staging_dir, + "stagingDir_persistent": True, + } + + instance_data.update({ + "label": f"CSV: {filename}", + "representations": [representation], + "stagingDir": staging_dir, + "stagingDir_persistent": True, + }) + + def get_instance_attr_defs(self): + return [ + BoolDef( + "add_review_family", + default=True, + label="Review" + ) + ] + + def get_pre_create_attr_defs(self): + """Creating pre-create attributes at creator plugin. + + Returns: + list: list of attribute object instances + """ + # Use same attributes as for instance attrobites + attr_defs = [ + FileDef( + "csv_filepath_data", + folders=False, + extensions=[".csv"], + allow_sequences=False, + single_item=True, + label="CSV File", + ), + ] + return attr_defs diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py new file mode 100644 index 0000000000..0da3ebed81 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py @@ -0,0 +1,36 @@ +from pprint import pformat +import pyblish.api +from ayon_core.pipeline import publish + + +class CollectCSVIngestInstancesData( + pyblish.api.InstancePlugin, + publish.AYONPyblishPluginMixin, + publish.ColormanagedPyblishPluginMixin +): + """Collect CSV Ingest data from instance. + """ + + label = "Collect CSV Ingest instances data" + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["traypublisher"] + families = ["csv_ingest"] + + def process(self, instance): + self.log.info(f"Collecting {instance.name}") + + # expecting [(colorspace, repre_data), ...] + prepared_repres_data_items = instance.data[ + "prepared_data_for_repres"] + + for colorspace, repre_data in prepared_repres_data_items: + # only apply colorspace to those which are not marked as thumbnail + if colorspace != "_thumbnail_": + # colorspace name is passed from CSV column + self.set_representation_colorspace( + repre_data, instance.context, colorspace + ) + + instance.data["representations"].append(repre_data) + + self.log.debug(pformat(instance.data)) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py new file mode 100644 index 0000000000..4bdf7c0493 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_csv_file.py @@ -0,0 +1,31 @@ +import pyblish.api + +from ayon_core.pipeline import publish + + +class ExtractCSVFile(publish.Extractor): + """ + Extractor export CSV file + """ + + label = "Extract CSV file" + order = pyblish.api.ExtractorOrder - 0.45 + families = ["csv_ingest_file"] + hosts = ["traypublisher"] + + def process(self, instance): + + csv_file_data = instance.data["csvFileData"] + + representation_csv = { + 'name': "csv_data", + 'ext': "csv", + 'files': csv_file_data["filename"], + "stagingDir": csv_file_data["staging_dir"], + "stagingDir_persistent": True + } + + instance.data["representations"].append(representation_csv) + + self.log.info("Added CSV file representation: {}".format( + representation_csv)) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py index 6a85f92ce1..7a35a19a85 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_existing_version.py @@ -16,6 +16,7 @@ class ValidateExistingVersion( order = ValidateContentsOrder hosts = ["traypublisher"] + targets = ["local"] actions = [RepairAction] diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py index cd4a98b84d..ca53a2c8ef 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_frame_ranges.py @@ -16,6 +16,8 @@ class ValidateFrameRange(OptionalPyblishPluginMixin, label = "Validate Frame Range" hosts = ["traypublisher"] families = ["render", "plate"] + targets = ["local"] + order = ValidateContentsOrder optional = True diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index a502031595..2788875c23 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -140,7 +140,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "uasset", "blendScene", "yeticacheUE", - "tycache" + "tycache", + "csv_ingest_file", ] default_template_name = "publish" From 64b366ca3d703fcd1cde53c917be3ab0a0b79d0f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 21 Feb 2024 12:08:44 +0000 Subject: [PATCH 137/423] 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 138/423] 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 139/423] 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 140/423] 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 141/423] 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 6274e5259a8b1a0daa31fe96847ead0a6e1cea74 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Feb 2024 17:15:20 +0100 Subject: [PATCH 142/423] Settings models and validators for batch movie creation and CSV ingestion plugins. Update addon version to 0.1.4. --- .../server/settings/creator_plugins.py | 295 ++++++++++++++++++ server_addon/traypublisher/server/version.py | 2 +- 2 files changed, 296 insertions(+), 1 deletion(-) diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index bf66d9a088..7ce241faa6 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -1,4 +1,7 @@ +from pydantic import validator from ayon_server.settings import BaseSettingsModel, SettingsField +from ayon_server.settings.validators import ensure_unique_names +from ayon_server.exceptions import BadRequestException class BatchMovieCreatorPlugin(BaseSettingsModel): @@ -22,11 +25,137 @@ class BatchMovieCreatorPlugin(BaseSettingsModel): ) +class ColumnItemModel(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + name: str = SettingsField( + title="Name", + default="" + ) + + type: str = SettingsField( + title="Type", + default="" + ) + + default: str = SettingsField( + title="Default", + default="" + ) + + required: bool = SettingsField( + title="Required", + default=False + ) + + validate: str = SettingsField( + title="Validate", + default="" + ) + + +class ColumnConfigModel(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + csv_delimiter: str = SettingsField( + title="CSV delimiter", + default="," + ) + + columns: list[ColumnItemModel] = SettingsField( + title="Columns", + default_factory=list + ) + + @validator("columns") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class RepresentationItemModel(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + name: str = SettingsField( + title="Name", + default="" + ) + + extensions: list[str] = SettingsField( + title="Extensions", + default_factory=list + ) + + @validator("extensions") + def validate_extension(cls, value): + for ext in value: + if not ext.startswith("."): + raise BadRequestException("Extension must start with '.'") + return value + + +class RepresentationConfigModel(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + tags_delimiter: str = SettingsField( + title="Tags delimiter", + default=";" + ) + + default_tags: list[str] = SettingsField( + title="Default tags", + default_factory=list + ) + + representation: list[RepresentationItemModel] = SettingsField( + title="Representation", + default_factory=list + ) + + @validator("representation") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class IngestCSVPluginModel(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + enabled: bool = SettingsField( + title="Enabled", + default=False + ) + + columns_config: ColumnConfigModel = SettingsField( + title="Columns config", + default_factory=ColumnConfigModel + ) + + representations_config: dict = SettingsField( + title="Representations config", + default_factory=dict + ) + + class TrayPublisherCreatePluginsModel(BaseSettingsModel): BatchMovieCreator: BatchMovieCreatorPlugin = SettingsField( title="Batch Movie Creator", default_factory=BatchMovieCreatorPlugin ) + IngestCSV: IngestCSVPluginModel = SettingsField( + title="Ingest CSV", + default_factory=IngestCSVPluginModel + ) DEFAULT_CREATORS = { @@ -41,4 +170,170 @@ DEFAULT_CREATORS = { ".mov" ] }, + "IngestCSV": { + "enabled": True, + "columns_config": { + "csv_delimiter": ",", + "columns": [ + { + "name": "File Path", + "type": "text", + "default": "", + "required": True, + "validate": "^([a-z0-9#._\\/]*)$" + }, + { + "name": "Folder Context", + "type": "text", + "default": "", + "required": True, + "validate": "^([a-zA-Z0-9_]*)$" + }, + { + "name": "Task Name", + "type": "text", + "default": "", + "required": True, + "validate": "^(.*)$" + }, + { + "name": "Version", + "type": "number", + "default": 1, + "required": True, + "validate": "^(\\d{1,3})$" + }, + { + "name": "Frame Start", + "type": "number", + "default": 0, + "required": True, + "validate": "^(\\d{1,8})$" + }, + { + "name": "Frame End", + "type": "number", + "default": 0, + "required": True, + "validate": "^(\\d{1,8})$" + }, + { + "name": "Handle Start", + "type": "number", + "default": 0, + "required": True, + "validate": "^(\\d)$" + }, + { + "name": "Handle End", + "type": "number", + "default": 0, + "required": True, + "validate": "^(\\d)$" + }, + { + "name": "FPS", + "type": "decimal", + "default": 0.0, + "required": True, + "validate": "^[0-9]*\\.[0-9]+$|^[0-9]+$" + }, + { + "name": "Thumbnail", + "type": "text", + "default": "", + "required": False, + "validate": "^([a-z0-9#._\\/]*)$" + }, + { + "name": "Colorspace", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + }, + { + "name": "Version Comment", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + }, + { + "name": "Representation", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + }, + { + "name": "Product Type", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + }, + { + "name": "Variant", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + }, + { + "name": "Slate Exists", + "type": "bool", + "default": True, + "required": False, + "validate": "(True|False)" + }, + { + "name": "Representation Tags", + "type": "text", + "default": "", + "required": False, + "validate": "^(.*)$" + } + ] + }, + "representations_config": { + "tags_delimiter": ";", + "default_tags": [ + "review" + ], + "representations": [ + { + "name": "preview", + "extensions": [ + ".mp4", + ".mov" + ] + }, + { + "name": "exr", + "extensions": [ + ".exr" + ] + }, + { + "name": "edit", + "extensions": [ + ".mov" + ] + }, + { + "name": "review", + "extensions": [ + ".mov" + ] + }, + { + "name": "nuke", + "extensions": [ + ".nk" + ] + } + ] + } + } } diff --git a/server_addon/traypublisher/server/version.py b/server_addon/traypublisher/server/version.py index e57ad00718..de699158fd 100644 --- a/server_addon/traypublisher/server/version.py +++ b/server_addon/traypublisher/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.3" +__version__ = "0.1.4" From fe0ccb2a4126bb7f632b3ace39b9f592d1ae0fe7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Feb 2024 17:15:36 +0100 Subject: [PATCH 143/423] Refactor column validation and retrieval logic. - Refactored how columns are validated and retrieved for better clarity. --- .../plugins/create/create_csv_ingest.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index aa986657dc..a11810f902 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -312,14 +312,20 @@ configuration in project settings. _, extension = os.path.splitext(filepath) # validate filepath is having correct extension based on output - config_repre_data = self.representations_config["representations"] repre_name = repre_data["representationName"] - if repre_name not in config_repre_data: + repre_config_data = None + for repre in self.representations_config["representations"]: + if repre["name"] == repre_name: + repre_config_data = repre + break + + if not repre_config_data: raise KeyError( f"Representation '{repre_name}' not found " "in config representation data." ) - validate_extensions = config_repre_data[repre_name]["extensions"] + + validate_extensions = repre_config_data["extensions"] if extension not in validate_extensions: raise TypeError( f"File extension '{extension}' not valid for " @@ -413,8 +419,8 @@ configuration in project settings. # make sure csv file contains columns from following list required_columns = [ - name for name, value in self.columns_config["columns"].items() - if value["required"] + column["name"] for column in self.columns_config["columns"] + if column["required"] ] # get data from csv file with open(csv_file_path, "r") as csv_file: @@ -582,9 +588,14 @@ configuration in project settings. self, column_name, row_data, default_value=None ): """Get row value with validation""" - columns_config = self.columns_config["columns"] + # get column data from column config - column_data = columns_config.get(column_name) + column_data = None + for column in self.columns_config["columns"]: + if column["name"] == column_name: + column_data = column + break + if not column_data: raise KeyError( f"Column '{column_name}' not found in column config." From 5595c021e244470d936059b98e44552a3b078e29 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Feb 2024 22:15:56 +0100 Subject: [PATCH 144/423] Update column attribute names and validation patterns in creator plugins settings. Improve error message for file extension validation. --- .../plugins/create/create_csv_ingest.py | 4 +- .../server/settings/creator_plugins.py | 80 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index a11810f902..7c379784f7 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -603,7 +603,7 @@ configuration in project settings. # get column value from row column_value = row_data.get(column_name) - column_required = column_data["required"] + column_required = column_data["required_column"] # check if column value is not empty string and column is required if column_value == "" and column_required: @@ -614,7 +614,7 @@ configuration in project settings. # get column type column_type = column_data["type"] # get column validation regex - column_validation = column_data["validate"] + column_validation = column_data["validation_pattern"] # get column default value column_default = default_value or column_data["default"] diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index 7ce241faa6..9f9c31da98 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -45,14 +45,14 @@ class ColumnItemModel(BaseSettingsModel): default="" ) - required: bool = SettingsField( - title="Required", + required_column: bool = SettingsField( + title="Required Column", default=False ) - validate: str = SettingsField( - title="Validate", - default="" + validation_pattern: str = SettingsField( + title="Validation Regex Pattern", + default="^(.*)$" ) @@ -96,7 +96,7 @@ class RepresentationItemModel(BaseSettingsModel): def validate_extension(cls, value): for ext in value: if not ext.startswith("."): - raise BadRequestException("Extension must start with '.'") + raise BadRequestException(f"Extension must start with '.': {ext}") return value @@ -179,120 +179,120 @@ DEFAULT_CREATORS = { "name": "File Path", "type": "text", "default": "", - "required": True, - "validate": "^([a-z0-9#._\\/]*)$" + "required_column": True, + "validation_pattern": "^([a-z0-9#._\\/]*)$" }, { "name": "Folder Context", "type": "text", "default": "", - "required": True, - "validate": "^([a-zA-Z0-9_]*)$" + "required_column": True, + "validation_pattern": "^([a-zA-Z0-9_]*)$" }, { "name": "Task Name", "type": "text", "default": "", - "required": True, - "validate": "^(.*)$" + "required_column": True, + "validation_pattern": "^(.*)$" }, { "name": "Version", "type": "number", "default": 1, - "required": True, - "validate": "^(\\d{1,3})$" + "required_column": True, + "validation_pattern": "^(\\d{1,3})$" }, { "name": "Frame Start", "type": "number", "default": 0, - "required": True, - "validate": "^(\\d{1,8})$" + "required_column": True, + "validation_pattern": "^(\\d{1,8})$" }, { "name": "Frame End", "type": "number", "default": 0, - "required": True, - "validate": "^(\\d{1,8})$" + "required_column": True, + "validation_pattern": "^(\\d{1,8})$" }, { "name": "Handle Start", "type": "number", "default": 0, - "required": True, - "validate": "^(\\d)$" + "required_column": True, + "validation_pattern": "^(\\d)$" }, { "name": "Handle End", "type": "number", "default": 0, - "required": True, - "validate": "^(\\d)$" + "required_column": True, + "validation_pattern": "^(\\d)$" }, { "name": "FPS", "type": "decimal", "default": 0.0, - "required": True, - "validate": "^[0-9]*\\.[0-9]+$|^[0-9]+$" + "required_column": True, + "validation_pattern": "^[0-9]*\\.[0-9]+$|^[0-9]+$" }, { "name": "Thumbnail", "type": "text", "default": "", - "required": False, - "validate": "^([a-z0-9#._\\/]*)$" + "required_column": False, + "validation_pattern": "^([a-z0-9#._\\/]*)$" }, { "name": "Colorspace", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" }, { "name": "Version Comment", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" }, { "name": "Representation", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" }, { "name": "Product Type", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" }, { "name": "Variant", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" }, { "name": "Slate Exists", "type": "bool", "default": True, - "required": False, - "validate": "(True|False)" + "required_column": False, + "validation_pattern": "(True|False)" }, { "name": "Representation Tags", "type": "text", "default": "", - "required": False, - "validate": "^(.*)$" + "required_column": False, + "validation_pattern": "^(.*)$" } ] }, From 81c657227ef19487c03a91d92bebca1705cbb830 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Feb 2024 17:12:51 +0000 Subject: [PATCH 145/423] 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 146/423] 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 147/423] 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 b7bd29389240a518838fbb2124aae26da6cadea8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Mar 2024 17:19:51 +0100 Subject: [PATCH 148/423] Update addon and plugin files for Ayon Core hosts traypublisher. - Added import statement for `get_ayon_launcher_args`. - Renamed function call from `get_subset_name` to `get_product_name`. - Changed attribute name from `family` to `product_type` in class IngestCSV. - Updated comments and docstrings in the code. --- client/ayon_core/hosts/traypublisher/addon.py | 1 + .../plugins/create/create_csv_ingest.py | 7 +++---- .../server/settings/creator_plugins.py | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/addon.py b/client/ayon_core/hosts/traypublisher/addon.py index f3884aedfe..ae0705cee2 100644 --- a/client/ayon_core/hosts/traypublisher/addon.py +++ b/client/ayon_core/hosts/traypublisher/addon.py @@ -1,6 +1,7 @@ import os from pathlib import Path +from ayon_core.lib import get_ayon_launcher_args from ayon_core.lib.execute import run_detached_process from ayon_core.addon import ( click_wrap, diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 7c379784f7..193e439581 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -5,7 +5,7 @@ import clique from copy import deepcopy, copy from ayon_core.client import get_asset_by_name -from ayon_core.pipeline.create import get_subset_name +from ayon_core.pipeline.create import get_product_name from ayon_core.pipeline import CreatedInstance from ayon_core.lib import FileDef, BoolDef from ayon_core.lib.transcoding import ( @@ -23,7 +23,7 @@ class IngestCSV(TrayPublishCreator): icon = "fa.file" label = "CSV Ingest" - family = "csv_ingest_file" + product_type = "csv_ingest_file" identifier = "io.ayon_core.creators.traypublisher.csv_ingest" default_variants = ["Main"] @@ -41,7 +41,6 @@ configuration in project settings. columns_config = {} representations_config = {} - def create(self, subset_name, instance_data, pre_create_data): """Create an product from each row found in the CSV. @@ -118,7 +117,7 @@ configuration in project settings. version = product_data["version"] # create subset/product name - product_name = get_subset_name( + product_name = get_product_name( product_type, variant, task_name, diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index 9f9c31da98..82c4d37739 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -78,9 +78,11 @@ class ColumnConfigModel(BaseSettingsModel): class RepresentationItemModel(BaseSettingsModel): - """Allows to publish multiple video files in one go.
Name of matching - asset is parsed from file names ('asset.mov', 'asset_v001.mov', - 'my_asset_to_publish.mov')""" + """Allows to publish multiple video files in one go. + + Name of matching asset is parsed from file names + ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov') + """ name: str = SettingsField( title="Name", @@ -115,12 +117,12 @@ class RepresentationConfigModel(BaseSettingsModel): default_factory=list ) - representation: list[RepresentationItemModel] = SettingsField( - title="Representation", + representations: list[RepresentationItemModel] = SettingsField( + title="Representations", default_factory=list ) - @validator("representation") + @validator("representations") def validate_unique_outputs(cls, value): ensure_unique_names(value) return value @@ -141,9 +143,9 @@ class IngestCSVPluginModel(BaseSettingsModel): default_factory=ColumnConfigModel ) - representations_config: dict = SettingsField( + representations_config: RepresentationConfigModel = SettingsField( title="Representations config", - default_factory=dict + default_factory=RepresentationConfigModel ) From 3a3b535001460d4e20cbccc3e05ca265872c4037 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Mar 2024 11:01:59 +0000 Subject: [PATCH 149/423] 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 102866da849ecb6e36d0acbbd1743fd6af2e5973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 5 Mar 2024 13:48:33 +0100 Subject: [PATCH 150/423] Update client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py Co-authored-by: Roy Nieterau --- .../plugins/publish/collect_csv_ingest_instance_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py index 0da3ebed81..1840dbb445 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py @@ -17,7 +17,6 @@ class CollectCSVIngestInstancesData( families = ["csv_ingest"] def process(self, instance): - self.log.info(f"Collecting {instance.name}") # expecting [(colorspace, repre_data), ...] prepared_repres_data_items = instance.data[ From 4cbeb1033aab65ecc2882e1f95e8ae909a504246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 5 Mar 2024 13:48:41 +0100 Subject: [PATCH 151/423] Update client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py Co-authored-by: Roy Nieterau --- .../plugins/publish/collect_csv_ingest_instance_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py index 1840dbb445..f76ef93c95 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py @@ -32,4 +32,3 @@ class CollectCSVIngestInstancesData( instance.data["representations"].append(repre_data) - self.log.debug(pformat(instance.data)) From ac4e3157bce395f953fd870317cdf426f9de85d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Mar 2024 14:43:48 +0100 Subject: [PATCH 152/423] ayon conversion fixes --- client/ayon_core/hosts/traypublisher/csv_publish.py | 10 +--------- .../plugins/create/create_csv_ingest.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py index f8eed2f2c5..6f2e335f89 100644 --- a/client/ayon_core/hosts/traypublisher/csv_publish.py +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -35,14 +35,6 @@ def csvpublish( # setting host context into project host.set_project_name(project_name) - # add asset context to environment - # TODO: perhaps this can be done in a better way? - os.environ.update({ - "AVALON_PROJECT": project_name, - "AVALON_ASSET": asset_name, - "AVALON_TASK": task_name or "" - }) - # form precreate data with field values file_field = FileDefItem.from_paths([csv_filepath], False).pop().to_dict() precreate_data = { @@ -57,7 +49,7 @@ def csvpublish( ) create_context.create( - "io.openpype.creators.traypublisher.csv_ingest", + "io.ayon_core.creators.traypublisher.csv_ingest", "Main", asset_doc=asset_doc, task_name=task_name, diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 193e439581..dd2339392f 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -79,7 +79,7 @@ configuration in project settings. ) csv_instance = CreatedInstance( - self.family, subset_name, instance_data, self + self.product_type, subset_name, instance_data, self ) self._store_new_instance(csv_instance) @@ -106,6 +106,7 @@ configuration in project settings. """Create instances from csv data""" for asset_name, _data in csv_data_for_instances.items(): + project_name = self.create_context.get_current_project_name() asset_doc = _data["asset_doc"] products = _data["products"] @@ -118,10 +119,12 @@ configuration in project settings. # create subset/product name product_name = get_product_name( - product_type, - variant, - task_name, + project_name, asset_doc, + task_name, + self.host_name, + product_type, + variant ) # make sure frame start/end is inherited from csv columns @@ -419,7 +422,7 @@ configuration in project settings. # make sure csv file contains columns from following list required_columns = [ column["name"] for column in self.columns_config["columns"] - if column["required"] + if column["required_column"] ] # get data from csv file with open(csv_file_path, "r") as csv_file: From 09effd30fb2d22c519cd16b61b82dd46413454b4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Mar 2024 16:16:52 +0100 Subject: [PATCH 153/423] Update column names and add new fields for better data representation in CSV ingest and creator plugins. --- .../plugins/create/create_csv_ingest.py | 5 +- .../server/settings/creator_plugins.py | 66 +++++++++---------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index dd2339392f..00ff7e00ed 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -545,9 +545,9 @@ configuration in project settings. # Get optional columns thumbnail_path = self._get_row_value_with_validation( - "Thumbnail", row_data) + "Version Thumbnail", row_data) colorspace = self._get_row_value_with_validation( - "Colorspace", row_data) + "Representation Colorspace", row_data) comment = self._get_row_value_with_validation( "Version Comment", row_data) repre = self._get_row_value_with_validation( @@ -583,7 +583,6 @@ configuration in project settings. "handleEnd": int(handle_end), "fps": float(fps), } - return file_path, representation_data def _get_row_value_with_validation( diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index 82c4d37739..3a07a76e6f 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -198,6 +198,20 @@ DEFAULT_CREATORS = { "required_column": True, "validation_pattern": "^(.*)$" }, + { + "name": "Product Type", + "type": "text", + "default": "", + "required_column": False, + "validation_pattern": "^(.*)$" + }, + { + "name": "Variant", + "type": "text", + "default": "", + "required_column": False, + "validation_pattern": "^(.*)$" + }, { "name": "Version", "type": "number", @@ -205,6 +219,20 @@ DEFAULT_CREATORS = { "required_column": True, "validation_pattern": "^(\\d{1,3})$" }, + { + "name": "Version Comment", + "type": "text", + "default": "", + "required_column": False, + "validation_pattern": "^(.*)$" + }, + { + "name": "Version Thumbnail", + "type": "text", + "default": "", + "required_column": False, + "validation_pattern": "^([a-zA-Z0-9#._\\/]*)$" + }, { "name": "Frame Start", "type": "number", @@ -241,25 +269,11 @@ DEFAULT_CREATORS = { "validation_pattern": "^[0-9]*\\.[0-9]+$|^[0-9]+$" }, { - "name": "Thumbnail", - "type": "text", - "default": "", + "name": "Slate Exists", + "type": "bool", + "default": True, "required_column": False, - "validation_pattern": "^([a-z0-9#._\\/]*)$" - }, - { - "name": "Colorspace", - "type": "text", - "default": "", - "required_column": False, - "validation_pattern": "^(.*)$" - }, - { - "name": "Version Comment", - "type": "text", - "default": "", - "required_column": False, - "validation_pattern": "^(.*)$" + "validation_pattern": "(True|False)" }, { "name": "Representation", @@ -269,26 +283,12 @@ DEFAULT_CREATORS = { "validation_pattern": "^(.*)$" }, { - "name": "Product Type", + "name": "Representation Colorspace", "type": "text", "default": "", "required_column": False, "validation_pattern": "^(.*)$" }, - { - "name": "Variant", - "type": "text", - "default": "", - "required_column": False, - "validation_pattern": "^(.*)$" - }, - { - "name": "Slate Exists", - "type": "bool", - "default": True, - "required_column": False, - "validation_pattern": "(True|False)" - }, { "name": "Representation Tags", "type": "text", From 8ecb1fa3a542b49308bbfa3c35e11de4ebed5379 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Mar 2024 16:17:08 +0100 Subject: [PATCH 154/423] Refactor representation data handling for thumbnails and media types. - Refactored how thumbnail and media representation data is processed. - Added logic to handle different types of representations based on colorspaces. --- .../plugins/create/create_csv_ingest.py | 40 ++++++++++++++----- .../collect_csv_ingest_instance_data.py | 21 ++++++++-- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 00ff7e00ed..720ef5ddb3 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -246,15 +246,27 @@ configuration in project settings. "stagingDir": thumb_dir, "outputName": explicit_output_name, }) - new_instance["prepared_data_for_repres"].append( - ("_thumbnail_", thumbnail_repr_data) - ) + new_instance["prepared_data_for_repres"].append({ + "type": "thumbnail", + "colorspace": None, + "representation": thumbnail_repr_data, + }) + # also add thumbnailPath for ayon to integrate + if not new_instance.get("thumbnailPath"): + new_instance["thumbnailPath"] = ( + os.path.join(thumb_dir, thumb_file) + ) elif ( thumbnails and not multiple_thumbnails and not thumbnails_processed or not reviewable ): + """ + For case where we have only one thumbnail + and not reviewable medias. This needs to be processed + only once per instance. + """ if not thumbnails: continue # here we will use only one thumbnail for @@ -273,9 +285,17 @@ configuration in project settings. "files": thumb_file, "stagingDir": thumb_dir }) - new_instance["prepared_data_for_repres"].append( - ("_thumbnail_", thumbnail_repr_data) - ) + new_instance["prepared_data_for_repres"].append({ + "type": "thumbnail", + "colorspace": None, + "representation": thumbnail_repr_data, + }) + # also add thumbnailPath for ayon to integrate + if not new_instance.get("thumbnailPath"): + new_instance["thumbnailPath"] = ( + os.path.join(thumb_dir, thumb_file) + ) + thumbnails_processed = True # get representation data @@ -284,9 +304,11 @@ configuration in project settings. explicit_output_name ) - new_instance["prepared_data_for_repres"].append( - (repre_data["colorspace"], representation_data) - ) + new_instance["prepared_data_for_repres"].append({ + "type": "media", + "colorspace": repre_data["colorspace"], + "representation": representation_data, + }) def _get_refactor_thumbnail_path( self, staging_dir, relative_thumbnail_path): diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py index f76ef93c95..33536d0854 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_csv_ingest_instance_data.py @@ -22,13 +22,26 @@ class CollectCSVIngestInstancesData( prepared_repres_data_items = instance.data[ "prepared_data_for_repres"] - for colorspace, repre_data in prepared_repres_data_items: - # only apply colorspace to those which are not marked as thumbnail - if colorspace != "_thumbnail_": + for prep_repre_data in prepared_repres_data_items: + type = prep_repre_data["type"] + colorspace = prep_repre_data["colorspace"] + repre_data = prep_repre_data["representation"] + + # thumbnails should be skipped + if type == "media": # colorspace name is passed from CSV column self.set_representation_colorspace( repre_data, instance.context, colorspace ) + elif type == "media" and colorspace is None: + # TODO: implement colorspace file rules file parsing + self.log.warning( + "Colorspace is not defined in csv for following" + f" representation: {pformat(repre_data)}" + ) + pass + elif type == "thumbnail": + # thumbnails should be skipped + pass instance.data["representations"].append(repre_data) - From 8bb9070a9e3d4a775360025197076a528f5c2af4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Mar 2024 16:19:37 +0100 Subject: [PATCH 155/423] comment Improve handling of thumbnails by removing unnecessary iteration. --- .../hosts/traypublisher/plugins/create/create_csv_ingest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 720ef5ddb3..84ab5b72a1 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -272,6 +272,8 @@ configuration in project settings. # here we will use only one thumbnail for # all representations relative_thumbnail_path = repre_data["thumbnailPath"] + # popping last thumbnail from list since it is only one + # and we do not need to iterate again over it if not relative_thumbnail_path: relative_thumbnail_path = thumbnails.pop() thumb_dir, thumb_file = \ 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 156/423] 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 157/423] 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 158/423] 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 159/423] 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 160/423] 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 346db35546d67b38afcb9cdd09529720b6462c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 11 Mar 2024 12:17:20 +0100 Subject: [PATCH 161/423] Update client/ayon_core/hosts/traypublisher/csv_publish.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hosts/traypublisher/csv_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py index 6f2e335f89..c9fbbf917a 100644 --- a/client/ayon_core/hosts/traypublisher/csv_publish.py +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -49,7 +49,7 @@ def csvpublish( ) create_context.create( - "io.ayon_core.creators.traypublisher.csv_ingest", + "io.ayon.creators.traypublisher.csv_ingest", "Main", asset_doc=asset_doc, task_name=task_name, From feffa4db66efa415683f3b0253de01ae773c2bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 11 Mar 2024 12:17:27 +0100 Subject: [PATCH 162/423] Update client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/traypublisher/plugins/create/create_csv_ingest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 84ab5b72a1..4ec6ca302a 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -24,7 +24,7 @@ class IngestCSV(TrayPublishCreator): label = "CSV Ingest" product_type = "csv_ingest_file" - identifier = "io.ayon_core.creators.traypublisher.csv_ingest" + identifier = "io.ayon.creators.traypublisher.csv_ingest" default_variants = ["Main"] From 0252e8796259221b418c4e3aa6e6e951cffe2674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 11 Mar 2024 12:22:28 +0100 Subject: [PATCH 163/423] Update client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/traypublisher/plugins/create/create_csv_ingest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 4ec6ca302a..31d0a022c5 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -141,7 +141,7 @@ configuration in project settings. version_comment = next( iter( repre_data["comment"] - for _, repre_data in product_data["representations"].items() # noqa: E501 + for repre_data in product_data["representations"].values() # noqa: E501 if repre_data["comment"] ), None From 638c68324139fbe365d36615ef21910a83f2fab2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Mar 2024 10:19:50 +0100 Subject: [PATCH 164/423] Update CLI options and function parameters for CSV file ingestion and publishing. Refactor variable names for clarity in functions related to file handling and context creation. --- client/ayon_core/hosts/traypublisher/addon.py | 28 +++++++++---------- .../hosts/traypublisher/csv_publish.py | 16 +++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/addon.py b/client/ayon_core/hosts/traypublisher/addon.py index ae0705cee2..ee42784f98 100644 --- a/client/ayon_core/hosts/traypublisher/addon.py +++ b/client/ayon_core/hosts/traypublisher/addon.py @@ -62,25 +62,25 @@ def launch(): @cli_main.command() @click_wrap.option( - "--csv-filepath", + "--filepath", help="Full path to CSV file with data", type=str, required=True ) @click_wrap.option( - "--project-name", + "--project", help="Project name in which the context will be used", type=str, required=True ) @click_wrap.option( - "--asset-name", + "--folder", help="Asset name in which the context will be used", type=str, required=True ) @click_wrap.option( - "--task-name", + "--task", help="Task name under Asset in which the context will be used", type=str, required=False @@ -93,10 +93,10 @@ def launch(): required=False ) def ingestcsv( - csv_filepath, - project_name, - asset_name, - task_name, + filepath, + project, + folder, + task, ignore_validators ): """Ingest CSV file into project. @@ -107,13 +107,13 @@ def ingestcsv( from .csv_publish import csvpublish # use Path to check if csv_filepath exists - if not Path(csv_filepath).exists(): - raise FileNotFoundError(f"File {csv_filepath} does not exist.") + if not Path(filepath).exists(): + raise FileNotFoundError(f"File {filepath} does not exist.") csvpublish( - csv_filepath, - project_name, - asset_name, - task_name, + filepath, + project, + folder, + task, ignore_validators ) diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py index c9fbbf917a..32c2b69371 100644 --- a/client/ayon_core/hosts/traypublisher/csv_publish.py +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -3,7 +3,7 @@ import os import pyblish.api import pyblish.util -from ayon_core.client import get_asset_by_name +from ayon_api import get_folder_by_name from ayon_core.lib.attribute_definitions import FileDefItem from ayon_core.pipeline import install_host from ayon_core.pipeline.create import CreateContext @@ -12,18 +12,18 @@ from ayon_core.hosts.traypublisher.api import TrayPublisherHost def csvpublish( - csv_filepath, + filepath, project_name, - asset_name, + folder_name, task_name=None, ignore_validators=False ): """Publish CSV file. Args: - csv_filepath (str): Path to CSV file. + filepath (str): Path to CSV file. project_name (str): Project name. - asset_name (str): Asset name. + folder_name (str): Folder name. task_name (Optional[str]): Task name. ignore_validators (Optional[bool]): Option to ignore validators. """ @@ -36,16 +36,16 @@ def csvpublish( host.set_project_name(project_name) # form precreate data with field values - file_field = FileDefItem.from_paths([csv_filepath], False).pop().to_dict() + file_field = FileDefItem.from_paths([filepath], False).pop().to_dict() precreate_data = { "csv_filepath_data": file_field, } # create context initialization create_context = CreateContext(host, headless=True) - asset_doc = get_asset_by_name( + asset_doc = get_folder_by_name( project_name, - asset_name + folder_name=folder_name ) create_context.create( From 1c11e18314ff929f1fe1b9b7048a063552143088 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Mar 2024 11:11:24 +0100 Subject: [PATCH 165/423] Update CSV ingest plugin to handle folder paths and task types. Improve error handling for missing assets and tasks. Refactor data processing for better organization and validation. --- .../plugins/create/create_csv_ingest.py | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 31d0a022c5..9d6f04ae99 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -4,14 +4,14 @@ import csv import clique from copy import deepcopy, copy -from ayon_core.client import get_asset_by_name +from ayon_api import get_folder_by_path, get_task_by_name from ayon_core.pipeline.create import get_product_name from ayon_core.pipeline import CreatedInstance from ayon_core.lib import FileDef, BoolDef from ayon_core.lib.transcoding import ( VIDEO_EXTENSIONS, IMAGE_EXTENSIONS ) - +from ayon_core.pipeline.create import CreatorError from ayon_core.hosts.traypublisher.api.plugin import ( TrayPublishCreator ) @@ -54,7 +54,7 @@ configuration in project settings. folder = csv_filepath_data.get("directory", "") if not os.path.exists(folder): - raise FileNotFoundError( + raise CreatorError( f"Directory '{folder}' does not exist." ) filename = csv_filepath_data.get("filenames", []) @@ -105,14 +105,14 @@ configuration in project settings. ): """Create instances from csv data""" - for asset_name, _data in csv_data_for_instances.items(): + for folder_path, prepared_data in csv_data_for_instances.items(): project_name = self.create_context.get_current_project_name() - asset_doc = _data["asset_doc"] - products = _data["products"] + products = prepared_data["products"] for instance_name, product_data in products.items(): # get important instance variables task_name = product_data["task_name"] + task_type = product_data["task_type"] variant = product_data["variant"] product_type = product_data["product_type"] version = product_data["version"] @@ -120,8 +120,8 @@ configuration in project settings. # create subset/product name product_name = get_product_name( project_name, - asset_doc, task_name, + task_type, self.host_name, product_type, variant @@ -155,7 +155,7 @@ configuration in project settings. # get representations from product data representations = product_data["representations"] - label = f"{asset_name}_{product_name}_v{version:>03}" + label = f"{folder_path}_{product_name}_v{version:>03}" families = ["csv_ingest"] if slate_exists: @@ -166,7 +166,7 @@ configuration in project settings. # make product data product_data = { "name": instance_name, - "asset": asset_name, + "folderPath": folder_path, "families": families, "label": label, "task": task_name, @@ -471,8 +471,10 @@ configuration in project settings. # get data from csv file for row in csv_reader: # Get required columns first - context_asset_name = self._get_row_value_with_validation( - "Folder Context", row) + # TODO: will need to be folder path in CSV + # TODO: `context_asset_name` is now `folder_path` + folder_path = self._get_row_value_with_validation( + "Folder Path", row) task_name = self._get_row_value_with_validation( "Task Name", row) version = self._get_row_value_with_validation( @@ -493,31 +495,40 @@ configuration in project settings. filename, representation_data = \ self._get_representation_row_data(row) + # TODO: batch query of all folder paths and task names + + # get folder entity from folder path + folder_entity = get_folder_by_path( + project_name, folder_path) + + # make sure asset exists + if not folder_entity: + raise CreatorError( + f"Asset '{folder_path}' not found." + ) + + # first get all tasks on the folder entity and then find + task_entity = get_task_by_name( + project_name, folder_entity["id"], task_name) + + # check if task name is valid task in asset doc + if not task_entity: + raise CreatorError( + f"Task '{task_name}' not found in asset doc." + ) + # get all csv data into one dict and make sure there are no # duplicates data are already validated and sorted under # correct existing asset also check if asset exists and if # task name is valid task in asset doc and representations # are distributed under products following variants - if context_asset_name not in csv_data: - asset_doc = get_asset_by_name( - project_name, context_asset_name) - - # make sure asset exists - if not asset_doc: - raise ValueError( - f"Asset '{context_asset_name}' not found." - ) - # check if task name is valid task in asset doc - if task_name not in asset_doc["data"]["tasks"]: - raise ValueError( - f"Task '{task_name}' not found in asset doc." - ) - - csv_data[context_asset_name] = { - "asset_doc": asset_doc, + if folder_path not in csv_data: + csv_data[folder_path] = { + "folder_entity": folder_entity, "products": { pre_product_name: { "task_name": task_name, + "task_type": task_entity["taskType"], "variant": variant, "product_type": product_type, "version": version, @@ -528,11 +539,11 @@ configuration in project settings. } } else: - asset_doc = csv_data[context_asset_name]["asset_doc"] - csv_products = csv_data[context_asset_name]["products"] + csv_products = csv_data[folder_path]["products"] if pre_product_name not in csv_products: csv_products[pre_product_name] = { "task_name": task_name, + "task_type": task_entity["taskType"], "variant": variant, "product_type": product_type, "version": version, From 175d299ccc5b20feea95242f156f7d15f6e0b132 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Mar 2024 13:12:30 +0100 Subject: [PATCH 166/423] Refactor error handling to use custom CreatorError class. - Replaced KeyError, TypeError, NotADirectoryError, ValueError with CreatorError for consistency and better error management. --- .../plugins/create/create_csv_ingest.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 9d6f04ae99..1381059fbb 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -346,14 +346,14 @@ configuration in project settings. break if not repre_config_data: - raise KeyError( + raise CreatorError( f"Representation '{repre_name}' not found " "in config representation data." ) validate_extensions = repre_config_data["extensions"] if extension not in validate_extensions: - raise TypeError( + raise CreatorError( f"File extension '{extension}' not valid for " f"output '{validate_extensions}'." ) @@ -373,7 +373,7 @@ configuration in project settings. # check if dirname exists if not os.path.isdir(dirname): - raise NotADirectoryError( + raise CreatorError( f"Directory '{dirname}' does not exist." ) @@ -389,7 +389,7 @@ configuration in project settings. collections = collections[0] else: if is_sequence: - raise ValueError( + raise CreatorError( f"No collections found in directory '{dirname}'." ) @@ -463,7 +463,7 @@ configuration in project settings. # check if csv file contains all required columns if any(column not in all_columns for column in required_columns): - raise KeyError( + raise CreatorError( f"Missing required columns: {required_columns}" ) @@ -555,7 +555,7 @@ configuration in project settings. csv_representations = \ csv_products[pre_product_name]["representations"] if filename in csv_representations: - raise ValueError( + raise CreatorError( f"Duplicate filename '{filename}' in csv file." ) csv_representations[filename] = representation_data @@ -633,7 +633,7 @@ configuration in project settings. break if not column_data: - raise KeyError( + raise CreatorError( f"Column '{column_name}' not found in column config." ) @@ -643,7 +643,7 @@ configuration in project settings. # check if column value is not empty string and column is required if column_value == "" and column_required: - raise ValueError( + raise CreatorError( f"Value in column '{column_name}' is required." ) @@ -675,7 +675,7 @@ configuration in project settings. column_value is not None and not re.match(str(column_validation), str(column_value)) ): - raise ValueError( + raise CreatorError( f"Column '{column_name}' value '{column_value}' " f"does not match validation regex '{column_validation}' \n" f"Row data: {row_data} \n" @@ -719,7 +719,7 @@ configuration in project settings. Returns: list: list of attribute object instances """ - # Use same attributes as for instance attrobites + # Use same attributes as for instance attributes attr_defs = [ FileDef( "csv_filepath_data", From 9090706252175d1cfcb38203214a376f1fd5a84b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Mar 2024 13:19:25 +0100 Subject: [PATCH 167/423] Refactor CSV file reading and data processing logic - Added import for StringIO - Refactored CSV file reading using StringIO - Improved handling of fieldnames and required columns detection --- .../plugins/create/create_csv_ingest.py | 196 +++++++++--------- 1 file changed, 102 insertions(+), 94 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py index 1381059fbb..8143e8b45b 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_csv_ingest.py @@ -2,6 +2,7 @@ import os import re import csv import clique +from io import StringIO from copy import deepcopy, copy from ayon_api import get_folder_by_path, get_task_by_name @@ -335,7 +336,7 @@ configuration in project settings. # get extension of file basename = os.path.basename(filepath) - _, extension = os.path.splitext(filepath) + extension = os.path.splitext(filepath)[-1].lower() # validate filepath is having correct extension based on output repre_name = repre_data["representationName"] @@ -448,100 +449,92 @@ configuration in project settings. column["name"] for column in self.columns_config["columns"] if column["required_column"] ] - # get data from csv file + + # read csv file with open(csv_file_path, "r") as csv_file: - csv_reader = csv.DictReader( - csv_file, delimiter=self.columns_config["csv_delimiter"]) + csv_content = csv_file.read() - # fix fieldnames - # sometimes someone can keep extra space at the start or end of - # the column name - all_columns = [ - " ".join(column.rsplit()) for column in csv_reader.fieldnames] - # return back fixed fieldnames - csv_reader.fieldnames = all_columns + # read csv file with DictReader + csv_reader = csv.DictReader( + StringIO(csv_content), + delimiter=self.columns_config["csv_delimiter"] + ) - # check if csv file contains all required columns - if any(column not in all_columns for column in required_columns): + # fix fieldnames + # sometimes someone can keep extra space at the start or end of + # the column name + all_columns = [ + " ".join(column.rsplit()) for column in csv_reader.fieldnames] + + # return back fixed fieldnames + csv_reader.fieldnames = all_columns + + # check if csv file contains all required columns + if any(column not in all_columns for column in required_columns): + raise CreatorError( + f"Missing required columns: {required_columns}" + ) + + csv_data = {} + # get data from csv file + for row in csv_reader: + # Get required columns first + # TODO: will need to be folder path in CSV + # TODO: `context_asset_name` is now `folder_path` + folder_path = self._get_row_value_with_validation( + "Folder Path", row) + task_name = self._get_row_value_with_validation( + "Task Name", row) + version = self._get_row_value_with_validation( + "Version", row) + + # Get optional columns + variant = self._get_row_value_with_validation( + "Variant", row) + product_type = self._get_row_value_with_validation( + "Product Type", row) + + pre_product_name = ( + f"{task_name}{variant}{product_type}" + f"{version}".replace(" ", "").lower() + ) + + # get representation data + filename, representation_data = \ + self._get_representation_row_data(row) + + # TODO: batch query of all folder paths and task names + + # get folder entity from folder path + folder_entity = get_folder_by_path( + project_name, folder_path) + + # make sure asset exists + if not folder_entity: raise CreatorError( - f"Missing required columns: {required_columns}" + f"Asset '{folder_path}' not found." ) - csv_data = {} - # get data from csv file - for row in csv_reader: - # Get required columns first - # TODO: will need to be folder path in CSV - # TODO: `context_asset_name` is now `folder_path` - folder_path = self._get_row_value_with_validation( - "Folder Path", row) - task_name = self._get_row_value_with_validation( - "Task Name", row) - version = self._get_row_value_with_validation( - "Version", row) + # first get all tasks on the folder entity and then find + task_entity = get_task_by_name( + project_name, folder_entity["id"], task_name) - # Get optional columns - variant = self._get_row_value_with_validation( - "Variant", row) - product_type = self._get_row_value_with_validation( - "Product Type", row) - - pre_product_name = ( - f"{task_name}{variant}{product_type}" - f"{version}".replace(" ", "").lower() + # check if task name is valid task in asset doc + if not task_entity: + raise CreatorError( + f"Task '{task_name}' not found in asset doc." ) - # get representation data - filename, representation_data = \ - self._get_representation_row_data(row) - - # TODO: batch query of all folder paths and task names - - # get folder entity from folder path - folder_entity = get_folder_by_path( - project_name, folder_path) - - # make sure asset exists - if not folder_entity: - raise CreatorError( - f"Asset '{folder_path}' not found." - ) - - # first get all tasks on the folder entity and then find - task_entity = get_task_by_name( - project_name, folder_entity["id"], task_name) - - # check if task name is valid task in asset doc - if not task_entity: - raise CreatorError( - f"Task '{task_name}' not found in asset doc." - ) - - # get all csv data into one dict and make sure there are no - # duplicates data are already validated and sorted under - # correct existing asset also check if asset exists and if - # task name is valid task in asset doc and representations - # are distributed under products following variants - if folder_path not in csv_data: - csv_data[folder_path] = { - "folder_entity": folder_entity, - "products": { - pre_product_name: { - "task_name": task_name, - "task_type": task_entity["taskType"], - "variant": variant, - "product_type": product_type, - "version": version, - "representations": { - filename: representation_data, - }, - } - } - } - else: - csv_products = csv_data[folder_path]["products"] - if pre_product_name not in csv_products: - csv_products[pre_product_name] = { + # get all csv data into one dict and make sure there are no + # duplicates data are already validated and sorted under + # correct existing asset also check if asset exists and if + # task name is valid task in asset doc and representations + # are distributed under products following variants + if folder_path not in csv_data: + csv_data[folder_path] = { + "folder_entity": folder_entity, + "products": { + pre_product_name: { "task_name": task_name, "task_type": task_entity["taskType"], "variant": variant, @@ -551,14 +544,29 @@ configuration in project settings. filename: representation_data, }, } - else: - csv_representations = \ - csv_products[pre_product_name]["representations"] - if filename in csv_representations: - raise CreatorError( - f"Duplicate filename '{filename}' in csv file." - ) - csv_representations[filename] = representation_data + } + } + else: + csv_products = csv_data[folder_path]["products"] + if pre_product_name not in csv_products: + csv_products[pre_product_name] = { + "task_name": task_name, + "task_type": task_entity["taskType"], + "variant": variant, + "product_type": product_type, + "version": version, + "representations": { + filename: representation_data, + }, + } + else: + csv_representations = \ + csv_products[pre_product_name]["representations"] + if filename in csv_representations: + raise CreatorError( + f"Duplicate filename '{filename}' in csv file." + ) + csv_representations[filename] = representation_data return csv_data From 6a01b8b6ac6a7f3701b6be7fdfcbdd073864c2a9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Mar 2024 13:19:35 +0100 Subject: [PATCH 168/423] Update creator plugin field name from "Folder Context" to "Folder Path" and adjust validation pattern to allow slashes. --- server_addon/traypublisher/server/settings/creator_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index 3a07a76e6f..1ff14002aa 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -185,11 +185,11 @@ DEFAULT_CREATORS = { "validation_pattern": "^([a-z0-9#._\\/]*)$" }, { - "name": "Folder Context", + "name": "Folder Path", "type": "text", "default": "", "required_column": True, - "validation_pattern": "^([a-zA-Z0-9_]*)$" + "validation_pattern": "^([a-zA-Z0-9_\\/]*)$" }, { "name": "Task Name", From 30e84371e0be60bfd31d912ae342b70f7f5383b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:16:32 +0100 Subject: [PATCH 169/423] Blender: Export Alembic with subdiv schema (creases) --- .../blender/plugins/publish/extract_abc.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py index 094f88fd8c..6251c1c0c5 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py @@ -2,6 +2,7 @@ import os import bpy +from ayon_core.lib import BoolDef from ayon_core.pipeline import publish from ayon_core.hosts.blender.api import plugin @@ -17,6 +18,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): if not self.is_active(instance.data): return + attr_values = self.get_attr_values_from_data(instance.data) + # Define extract output file path stagingdir = self.staging_dir(instance) folder_name = instance.data["folderEntity"]["name"] @@ -46,7 +49,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): bpy.ops.wm.alembic_export( filepath=filepath, selected=True, - flatten=False + flatten=False, + subdiv_schema=attr_values.get("subdiv_schema", False) ) plugin.deselect_all() @@ -65,6 +69,21 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): self.log.debug("Extracted instance '%s' to: %s", instance.name, representation) + @classmethod + def get_attribute_defs(cls): + return [ + BoolDef( + "subdiv_schema", + label="Alembic Mesh Subdiv Schema", + tooltip="Export Meshes using Alembic's subdivision schema.\n" + "Enabling this includes creases with the export but " + "excludes the mesh's normals.\n" + "Enabling this usually result in smaller file size " + "due to lack of normals.", + default=True + ) + ] + class ExtractModelABC(ExtractABC): """Extract model as ABC.""" From b2af91ba37adcb8cff5fd669d8f2e65b5eb91aa5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:17:42 +0100 Subject: [PATCH 170/423] Default to False for backwards compatibility --- client/ayon_core/hosts/blender/plugins/publish/extract_abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py index 6251c1c0c5..6590be515c 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py @@ -80,7 +80,7 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): "excludes the mesh's normals.\n" "Enabling this usually result in smaller file size " "due to lack of normals.", - default=True + default=False ) ] From 3791731b6ac2b42583f56ccc69501f39b6372f06 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Mar 2024 14:45:30 +0100 Subject: [PATCH 171/423] Refactor option names and handle folder & task entities. - Renamed "--folder" to "--folder-path" for clarity. - Updated function parameters to use "folder_path" consistently. - Added error handling for non-existent folder or task paths. --- client/ayon_core/hosts/traypublisher/addon.py | 6 ++-- .../hosts/traypublisher/csv_publish.py | 32 +++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/addon.py b/client/ayon_core/hosts/traypublisher/addon.py index ee42784f98..3dd275f223 100644 --- a/client/ayon_core/hosts/traypublisher/addon.py +++ b/client/ayon_core/hosts/traypublisher/addon.py @@ -74,7 +74,7 @@ def launch(): required=True ) @click_wrap.option( - "--folder", + "--folder-path", help="Asset name in which the context will be used", type=str, required=True @@ -95,7 +95,7 @@ def launch(): def ingestcsv( filepath, project, - folder, + folder_path, task, ignore_validators ): @@ -113,7 +113,7 @@ def ingestcsv( csvpublish( filepath, project, - folder, + folder_path, task, ignore_validators ) diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py index 32c2b69371..b43792a357 100644 --- a/client/ayon_core/hosts/traypublisher/csv_publish.py +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -3,7 +3,7 @@ import os import pyblish.api import pyblish.util -from ayon_api import get_folder_by_name +from ayon_api import get_folder_by_path, get_task_by_name from ayon_core.lib.attribute_definitions import FileDefItem from ayon_core.pipeline import install_host from ayon_core.pipeline.create import CreateContext @@ -14,7 +14,7 @@ from ayon_core.hosts.traypublisher.api import TrayPublisherHost def csvpublish( filepath, project_name, - folder_name, + folder_path, task_name=None, ignore_validators=False ): @@ -23,7 +23,7 @@ def csvpublish( Args: filepath (str): Path to CSV file. project_name (str): Project name. - folder_name (str): Folder name. + folder_path (str): Folder path. task_name (Optional[str]): Task name. ignore_validators (Optional[bool]): Option to ignore validators. """ @@ -43,16 +43,34 @@ def csvpublish( # create context initialization create_context = CreateContext(host, headless=True) - asset_doc = get_folder_by_name( + folder_entity = get_folder_by_path( project_name, - folder_name=folder_name + folder_path=folder_path, ) + if not folder_entity: + ValueError( + f"Folder path '{folder_path}' doesn't " + f"exists at project '{project_name}'." + ) + + task_entity = get_task_by_name( + project_name, + folder_entity["id"], + task_name, + ) + + if not task_entity: + ValueError( + f"Task name '{task_name}' doesn't " + f"exists at folder '{folder_path}'." + ) + create_context.create( "io.ayon.creators.traypublisher.csv_ingest", "Main", - asset_doc=asset_doc, - task_name=task_name, + folder_entity=folder_entity, + task_entity=task_entity, pre_create_data=precreate_data, ) From 49cfea34c7893cd221a2fbf2973241b353a4923a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 01:25:46 +0100 Subject: [PATCH 172/423] 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 173/423] 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 018e98df0ba1c1d3e68207d982e6d6c8e3d52704 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 10:17:42 +0100 Subject: [PATCH 174/423] Fix #253 - Do not create `unpack`, `normal` and `null` on load --- .../houdini/plugins/load/load_alembic.py | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py index a77d06d409..3dba625da9 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -45,33 +45,11 @@ class AbcLoader(load.LoaderPlugin): alembic = container.createNode("alembic", node_name=node_name) alembic.setParms({"fileName": file_path}) - # Add unpack node - unpack_name = "unpack_{}".format(name) - unpack = container.createNode("unpack", node_name=unpack_name) - unpack.setInput(0, alembic) - unpack.setParms({"transfer_attributes": "path"}) + # Position nodes nicely + container.moveToGoodPosition() + container.layoutChildren() - # Add normal to points - # Order of menu ['point', 'vertex', 'prim', 'detail'] - normal_name = "normal_{}".format(name) - normal_node = container.createNode("normal", node_name=normal_name) - normal_node.setParms({"type": 0}) - - normal_node.setInput(0, unpack) - - null = container.createNode("null", node_name="OUT".format(name)) - null.setInput(0, normal_node) - - # Ensure display flag is on the Alembic input node and not on the OUT - # node to optimize "debug" displaying in the viewport. - alembic.setDisplayFlag(True) - - # Set new position for unpack node else it gets cluttered - nodes = [container, alembic, unpack, normal_node, null] - for nr, node in enumerate(nodes): - node.setPosition([0, (0 - nr)]) - - self[:] = nodes + nodes = [container, alembic] return pipeline.containerise( node_name, From 9666352b6e625f8b45e577bfea9bc39f18af7585 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 12:08:35 +0100 Subject: [PATCH 175/423] 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 efcf5148bd253968b79af43d68654eef66695e81 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 14:47:31 +0100 Subject: [PATCH 176/423] Maya: Yeti - Implement writing and loading user variables with a yeti cache --- client/ayon_core/hosts/maya/api/yeti.py | 101 ++++++++++++++++++ .../maya/plugins/load/load_yeti_cache.py | 41 +++++++ .../plugins/publish/collect_yeti_cache.py | 20 +++- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/hosts/maya/api/yeti.py diff --git a/client/ayon_core/hosts/maya/api/yeti.py b/client/ayon_core/hosts/maya/api/yeti.py new file mode 100644 index 0000000000..1526c3a2f3 --- /dev/null +++ b/client/ayon_core/hosts/maya/api/yeti.py @@ -0,0 +1,101 @@ +from typing import List + +from maya import cmds + + +def get_yeti_user_variables(yeti_shape_node: str) -> List[str]: + """Get user defined yeti user variables for a `pgYetiMaya` shape node. + + Arguments: + yeti_shape_node (str): The `pgYetiMaya` shape node. + + Returns: + list: Attribute names (for a vector attribute it only lists the top + parent attribute, not the attribute per axis) + """ + + attrs = cmds.listAttr(yeti_shape_node, + userDefined=True, + string=("yetiVariableV_*", + "yetiVariableF_*")) or [] + valid_attrs = [] + for attr in attrs: + attr_type = cmds.attributeQuery(attr, node=yeti_shape_node, + attributeType=True) + if attr.startswith("yetiVariableV_") and attr_type == "double3": + # vector + valid_attrs.append(attr) + elif attr.startswith("yetiVariableF_") and attr_type == "double": + valid_attrs.append(attr) + + return valid_attrs + + +def create_yeti_variable(yeti_shape_node: str, + attr_name: str, + value=None, + force_value: bool = False) -> bool: + """Get user defined yeti user variables for a `pgYetiMaya` shape node. + + Arguments: + yeti_shape_node (str): The `pgYetiMaya` shape node. + attr_name (str): The fully qualified yeti variable name, e.g. + "yetiVariableF_myfloat" or "yetiVariableV_myvector" + value (object): The value to set (must match the type of the attribute) + When value is None it will ignored and not be set. + force_value (bool): Whether to set the value if the attribute already + exists or not. + + Returns: + bool: Whether the attribute value was set or not. + + """ + exists = cmds.attributeQuery(attr_name, node=yeti_shape_node, exists=True) + if not exists: + if attr_name.startswith("yetiVariableV_"): + _create_vector_yeti_user_variable(yeti_shape_node, attr_name) + if attr_name.startswith("yetiVariableF_"): + _create_float_yeti_user_variable(yeti_shape_node, attr_name) + + if value is not None and (not exists or force_value): + plug = "{}.{}".format(yeti_shape_node, attr_name) + if ( + isinstance(value, (list, tuple)) + and attr_name.startswith("yetiVariableV_") + ): + cmds.setAttr(plug, *value, type="double3") + else: + cmds.setAttr(plug, value) + + return True + return False + + +def _create_vector_yeti_user_variable(yeti_shape_node: str, attr_name: str): + if not attr_name.startswith("yetiVariableV_"): + raise ValueError("Must start with yetiVariableV_") + cmds.addAttr(yeti_shape_node, + longName=attr_name, + attributeType="double3", + cachedInternally=True, + keyable=True) + for axis in "XYZ": + cmds.addAttr(yeti_shape_node, + longName="{}{}".format(attr_name, axis), + attributeType="double", + parent=attr_name, + cachedInternally=True, + keyable=True) + + +def _create_float_yeti_user_variable(yeti_node: str, attr_name: str): + if not attr_name.startswith("yetiVariableF_"): + raise ValueError("Must start with yetiVariableF_") + + cmds.addAttr(yeti_node, + longName=attr_name, + attributeType="double", + cachedInternally=True, + softMinValue=0, + softMaxValue=100, + keyable=True) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py index a5cd04b0f4..06f74e5107 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py @@ -12,6 +12,7 @@ from ayon_core.pipeline import ( get_representation_path ) from ayon_core.hosts.maya.api import lib +from ayon_core.hosts.maya.api.yeti import create_yeti_variable from ayon_core.hosts.maya.api.pipeline import containerise from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type @@ -23,8 +24,19 @@ SKIP_UPDATE_ATTRS = { "viewportDensity", "viewportWidth", "viewportLength", + "renderDensity", + "renderWidth", + "renderLength", + "increaseRenderBounds" } +SKIP_ATTR_MESSAGE = ( + "Skipping updating %s.%s to %s because it " + "is considered a local overridable attribute. " + "Either set manually or the load the cache " + "anew." +) + def set_attribute(node, attr, value): """Wrapper of set attribute which ignores None values""" @@ -209,9 +221,31 @@ class YetiCacheLoader(load.LoaderPlugin): for attr, value in node_settings["attrs"].items(): if attr in SKIP_UPDATE_ATTRS: + self.log.info( + SKIP_ATTR_MESSAGE, yeti_node, attr, value + ) continue set_attribute(attr, value, yeti_node) + # Set up user defined attributes + user_variables = node_settings.get("user_variables", {}) + for attr, value in user_variables.items(): + was_value_set = create_yeti_variable( + yeti_shape_node=yeti_node, + attr_name=attr, + value=value, + # We do not want to update the + # value if it already exists so + # that any local overrides that + # may have been applied still + # persist + force_value=False + ) + if not was_value_set: + self.log.info( + SKIP_ATTR_MESSAGE, yeti_node, attr, value + ) + cmds.setAttr("{}.representation".format(container_node), repre_entity["id"], typ="string") @@ -332,6 +366,13 @@ class YetiCacheLoader(load.LoaderPlugin): for attr, value in attributes.items(): set_attribute(attr, value, yeti_node) + # Set up user defined attributes + user_variables = node_settings.get("user_variables", {}) + for attr, value in user_variables.items(): + create_yeti_variable(yeti_shape_node=yeti_node, + attr_name=attr, + value=value) + # Connect to the time node cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/publish/collect_yeti_cache.py index 067a7bc532..e1755e4212 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_yeti_cache.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api from ayon_core.hosts.maya.api import lib +from ayon_core.hosts.maya.api.yeti import get_yeti_user_variables SETTINGS = { @@ -34,7 +35,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): - "increaseRenderBounds" - "imageSearchPath" - Other information is the name of the transform and it's Colorbleed ID + Other information is the name of the transform and its `cbId` """ order = pyblish.api.CollectorOrder + 0.45 @@ -54,6 +55,16 @@ class CollectYetiCache(pyblish.api.InstancePlugin): # Get specific node attributes attr_data = {} for attr in SETTINGS: + # Ignore non-existing attributes with a warning, e.g. cbId + # if they have not been generated yet + if not cmds.attributeQuery(attr, node=shape, exists=True): + self.log.warning( + "Attribute '{}' not found on Yeti node: {}".format( + attr, shape + ) + ) + continue + current = cmds.getAttr("%s.%s" % (shape, attr)) # change None to empty string as Maya doesn't support # NoneType in attributes @@ -61,6 +72,12 @@ class CollectYetiCache(pyblish.api.InstancePlugin): current = "" attr_data[attr] = current + # Get user variable attributes + user_variable_attrs = { + attr: lib.get_attribute("{}.{}".format(shape, attr)) + for attr in get_yeti_user_variables(shape) + } + # Get transform data parent = cmds.listRelatives(shape, parent=True)[0] transform_data = {"name": parent, "cbId": lib.get_id(parent)} @@ -70,6 +87,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): "name": shape, "cbId": lib.get_id(shape), "attrs": attr_data, + "user_variables": user_variable_attrs } settings["nodes"].append(shape_data) From f36d9804c5aea7bf2dd9ad64d956ef109cd2102c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 14:55:52 +0100 Subject: [PATCH 177/423] Maya: Create yeticache instance directly in loading a yeti rig --- .../hosts/maya/plugins/load/load_yeti_rig.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 74e33c5866..6b0288afea 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -1,8 +1,13 @@ +from typing import List + import maya.cmds as cmds from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api import lib +from openpype.pipeline import registered_host +from openpype.pipeline.create import CreateContext + class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" @@ -49,4 +54,40 @@ class YetiRigLoader(plugin.ReferenceLoader): ) self[:] = nodes + # Automatically create in instance to allow publishing the loaded + # yeti rig into a yeti cache + self._create_yeti_cache_instance(nodes, variant=namespace) + return nodes + + def _create_yeti_cache_instance(self, nodes: List[str], variant: str): + """Create a yeticache product type instance to publish the output. + + This is similar to how loading animation rig will automatically create + an animation instance for publishing any loaded character rigs, but + then for yeti rigs. + + Args: + nodes (List[str]): Nodes generated on load. + variant (str): Variant for the yeti cache instance to create. + + """ + + # Find the roots amongst the loaded nodes + yeti_nodes = cmds.ls(nodes, type="pgYetiMaya", long=True) + assert yeti_nodes, "No pgYetiMaya nodes in rig, this is a bug." + + self.log.info("Creating variant: {}".format(variant)) + + creator_identifier = "io.openpype.creators.maya.yeticache" + + host = registered_host() + create_context = CreateContext(host) + + with lib.maintained_selection(): + cmds.select(yeti_nodes, noExpand=True) + create_context.create( + creator_identifier=creator_identifier, + variant=variant, + pre_create_data={"use_selection": True} + ) From 3def9fe29e19691ca8f7d1514ca74677c8edd00a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 28 Mar 2024 10:47:41 +0000 Subject: [PATCH 178/423] 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 179/423] 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 3bae4dba34c1dd141139b914b61d948c4afedec8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:13:45 +0100 Subject: [PATCH 180/423] Maya: Fix redshift proxy export whilst in renderlayer + allow exporting from renderlayer --- .../hosts/maya/api/render_setup_tools.py | 4 +-- .../plugins/publish/extract_redshift_proxy.py | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/render_setup_tools.py b/client/ayon_core/hosts/maya/api/render_setup_tools.py index a5e04de184..9b00b53eee 100644 --- a/client/ayon_core/hosts/maya/api/render_setup_tools.py +++ b/client/ayon_core/hosts/maya/api/render_setup_tools.py @@ -19,7 +19,7 @@ from .lib import pairwise @contextlib.contextmanager -def _allow_export_from_render_setup_layer(): +def allow_export_from_render_setup_layer(): """Context manager to override Maya settings to allow RS layer export""" try: @@ -102,7 +102,7 @@ def export_in_rs_layer(path, nodes, export=None): cmds.disconnectAttr(src, dest) # Export Selected - with _allow_export_from_render_setup_layer(): + with allow_export_from_render_setup_layer(): cmds.select(nodes, noExpand=True) if export: export() diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py index 9286869c60..e6a568ef8d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -5,7 +5,13 @@ import os from maya import cmds from ayon_core.pipeline import publish -from ayon_core.hosts.maya.api.lib import maintained_selection +from ayon_core.hosts.maya.api.lib import ( + maintained_selection, + renderlayer +) +from ayon_core.hosts.maya.api.render_setup_tools import ( + allow_export_from_render_setup_layer +) class ExtractRedshiftProxy(publish.Extractor): @@ -60,14 +66,22 @@ class ExtractRedshiftProxy(publish.Extractor): # Write out rs file self.log.debug("Writing: '%s'" % file_path) + + # Allow overriding what renderlayer to export from. By default force + # it to the default render layer. (Note that the renderlayer isn't + # currently exposed as an attribute to artists) + layer = instance.data.get("renderLayer", "defaultRenderLayer") + with maintained_selection(): - cmds.select(instance.data["setMembers"], noExpand=True) - cmds.file(file_path, - pr=False, - force=True, - type="Redshift Proxy", - exportSelected=True, - options=rs_options) + with renderlayer(layer): + with allow_export_from_render_setup_layer(): + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.file(file_path, + preserveReferences=False, + force=True, + type="Redshift Proxy", + exportSelected=True, + options=rs_options) if "representations" not in instance.data: instance.data["representations"] = [] From 94488e18d24504f3e771f081dd8d8ff52e7bb5f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:14:14 +0100 Subject: [PATCH 181/423] Make sure Redshift is loaded before trying to export --- .../hosts/maya/plugins/publish/extract_redshift_proxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py index e6a568ef8d..66dd805437 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -24,6 +24,9 @@ class ExtractRedshiftProxy(publish.Extractor): def process(self, instance): """Extractor entry point.""" + # Make sure Redshift is loaded + cmds.loadPlugin("redshift4maya", quiet=True) + staging_dir = self.staging_dir(instance) file_name = "{}.rs".format(instance.name) file_path = os.path.join(staging_dir, file_name) From bf18daafe5c5d3128158b536cbd796fe856f81fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:42:00 +0100 Subject: [PATCH 182/423] 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 183/423] 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 184/423] 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 185/423] 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 186/423] 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 187/423] 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 188/423] 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 189/423] 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 bf26eba7797d5f53a5eda6b2836c43276443d7dc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:45:57 +0200 Subject: [PATCH 190/423] Bump Houdini addon version - Color management for Houdini workfiles --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index b5c9b6cb71..11ef092868 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.12" +__version__ = "0.2.13" From eb0de8685408c95f63f8e4d9e15b1c0be926779e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Apr 2024 14:43:37 +0200 Subject: [PATCH 191/423] 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 192/423] 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 193/423] 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 10c0004c80ca5437f37a7729f3acfc31b2344f94 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 15:41:58 +0800 Subject: [PATCH 194/423] delete old versions loader action does not work --- client/ayon_core/pipeline/load/plugins.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 064af4ddc1..29edc73b57 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -220,19 +220,6 @@ class LoaderPlugin(list): """ return cls.options or [] - @property - def fname(self): - """Backwards compatibility with deprecation warning""" - - self.log.warning(( - "DEPRECATION WARNING: Source - Loader plugin {}." - " The 'fname' property on the Loader plugin will be removed in" - " future versions of OpenPype. Planned version to drop the support" - " is 3.16.6 or 3.17.0." - ).format(self.__class__.__name__)) - if hasattr(self, "_fname"): - return self._fname - class ProductLoaderPlugin(LoaderPlugin): """Load product into host application From 6c8b4e33bce15361690d446f0b204040abc42c04 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 16:23:16 +0800 Subject: [PATCH 195/423] should be removing delete_old_versions.py from the load plugins --- client/ayon_core/pipeline/load/plugins.py | 13 + .../plugins/load/delete_old_versions.py | 501 ------------------ 2 files changed, 13 insertions(+), 501 deletions(-) delete mode 100644 client/ayon_core/plugins/load/delete_old_versions.py diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 29edc73b57..064af4ddc1 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -220,6 +220,19 @@ class LoaderPlugin(list): """ return cls.options or [] + @property + def fname(self): + """Backwards compatibility with deprecation warning""" + + self.log.warning(( + "DEPRECATION WARNING: Source - Loader plugin {}." + " The 'fname' property on the Loader plugin will be removed in" + " future versions of OpenPype. Planned version to drop the support" + " is 3.16.6 or 3.17.0." + ).format(self.__class__.__name__)) + if hasattr(self, "_fname"): + return self._fname + class ProductLoaderPlugin(LoaderPlugin): """Load product into host application diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py deleted file mode 100644 index 04873d8b5c..0000000000 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ /dev/null @@ -1,501 +0,0 @@ -# TODO This plugin is not converted for AYON -# -# import collections -# import os -# import uuid -# -# import clique -# import ayon_api -# from pymongo import UpdateOne -# import qargparse -# from qtpy import QtWidgets, QtCore -# -# from ayon_core import style -# from ayon_core.addon import AddonsManager -# from ayon_core.lib import format_file_size -# from ayon_core.pipeline import load, Anatomy -# from ayon_core.pipeline.load import ( -# get_representation_path_with_anatomy, -# InvalidRepresentationContext, -# ) -# -# -# class DeleteOldVersions(load.ProductLoaderPlugin): -# """Deletes specific number of old version""" -# -# is_multiple_contexts_compatible = True -# sequence_splitter = "__sequence_splitter__" -# -# representations = ["*"] -# product_types = {"*"} -# tool_names = ["library_loader"] -# -# label = "Delete Old Versions" -# order = 35 -# icon = "trash" -# color = "#d8d8d8" -# -# options = [ -# qargparse.Integer( -# "versions_to_keep", default=2, min=0, help="Versions to keep:" -# ), -# qargparse.Boolean( -# "remove_publish_folder", help="Remove publish folder:" -# ) -# ] -# -# def delete_whole_dir_paths(self, dir_paths, delete=True): -# size = 0 -# -# for dir_path in dir_paths: -# # Delete all files and fodlers in dir path -# for root, dirs, files in os.walk(dir_path, topdown=False): -# for name in files: -# file_path = os.path.join(root, name) -# size += os.path.getsize(file_path) -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# -# for name in dirs: -# if delete: -# os.rmdir(os.path.join(root, name)) -# -# if not delete: -# continue -# -# # Delete even the folder and it's parents folders if they are empty -# while True: -# if not os.path.exists(dir_path): -# dir_path = os.path.dirname(dir_path) -# continue -# -# if len(os.listdir(dir_path)) != 0: -# break -# -# os.rmdir(os.path.join(dir_path)) -# -# return size -# -# def path_from_representation(self, representation, anatomy): -# try: -# context = representation["context"] -# except KeyError: -# return (None, None) -# -# try: -# path = get_representation_path_with_anatomy( -# representation, anatomy -# ) -# except InvalidRepresentationContext: -# return (None, None) -# -# sequence_path = None -# if "frame" in context: -# context["frame"] = self.sequence_splitter -# sequence_path = get_representation_path_with_anatomy( -# representation, anatomy -# ) -# -# if sequence_path: -# sequence_path = sequence_path.normalized() -# -# return (path.normalized(), sequence_path) -# -# def delete_only_repre_files(self, dir_paths, file_paths, delete=True): -# size = 0 -# -# for dir_id, dir_path in dir_paths.items(): -# dir_files = os.listdir(dir_path) -# collections, remainders = clique.assemble(dir_files) -# for file_path, seq_path in file_paths[dir_id]: -# file_path_base = os.path.split(file_path)[1] -# # Just remove file if `frame` key was not in context or -# # filled path is in remainders (single file sequence) -# if not seq_path or file_path_base in remainders: -# if not os.path.exists(file_path): -# self.log.debug( -# "File was not found: {}".format(file_path) -# ) -# continue -# -# size += os.path.getsize(file_path) -# -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# -# if file_path_base in remainders: -# remainders.remove(file_path_base) -# continue -# -# seq_path_base = os.path.split(seq_path)[1] -# head, tail = seq_path_base.split(self.sequence_splitter) -# -# final_col = None -# for collection in collections: -# if head != collection.head or tail != collection.tail: -# continue -# final_col = collection -# break -# -# if final_col is not None: -# # Fill full path to head -# final_col.head = os.path.join(dir_path, final_col.head) -# for _file_path in final_col: -# if os.path.exists(_file_path): -# -# size += os.path.getsize(_file_path) -# -# if delete: -# os.remove(_file_path) -# self.log.debug( -# "Removed file: {}".format(_file_path) -# ) -# -# _seq_path = final_col.format("{head}{padding}{tail}") -# self.log.debug("Removed files: {}".format(_seq_path)) -# collections.remove(final_col) -# -# elif os.path.exists(file_path): -# size += os.path.getsize(file_path) -# -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# else: -# self.log.debug( -# "File was not found: {}".format(file_path) -# ) -# -# # Delete as much as possible parent folders -# if not delete: -# return size -# -# for dir_path in dir_paths.values(): -# while True: -# if not os.path.exists(dir_path): -# dir_path = os.path.dirname(dir_path) -# continue -# -# if len(os.listdir(dir_path)) != 0: -# break -# -# self.log.debug("Removed folder: {}".format(dir_path)) -# os.rmdir(dir_path) -# -# return size -# -# def message(self, text): -# msgBox = QtWidgets.QMessageBox() -# msgBox.setText(text) -# msgBox.setStyleSheet(style.load_stylesheet()) -# msgBox.setWindowFlags( -# msgBox.windowFlags() | QtCore.Qt.FramelessWindowHint -# ) -# msgBox.exec_() -# -# def get_data(self, context, versions_count): -# product_entity = context["product"] -# folder_entity = context["folder"] -# project_name = context["project"]["name"] -# anatomy = Anatomy(project_name) -# -# versions = list(ayon_api.get_versions( -# project_name, product_ids=[product_entity["id"]] -# )) -# -# versions_by_parent = collections.defaultdict(list) -# for ent in versions: -# versions_by_parent[ent["productId"]].append(ent) -# -# def sort_func(ent): -# return int(ent["version"]) -# -# all_last_versions = [] -# for _parent_id, _versions in versions_by_parent.items(): -# for idx, version in enumerate( -# sorted(_versions, key=sort_func, reverse=True) -# ): -# if idx >= versions_count: -# break -# all_last_versions.append(version) -# -# self.log.debug("Collected versions ({})".format(len(versions))) -# -# # Filter latest versions -# for version in all_last_versions: -# versions.remove(version) -# -# # Update versions_by_parent without filtered versions -# versions_by_parent = collections.defaultdict(list) -# for ent in versions: -# versions_by_parent[ent["productId"]].append(ent) -# -# # Filter already deleted versions -# versions_to_pop = [] -# for version in versions: -# version_tags = version["data"].get("tags") -# if version_tags and "deleted" in version_tags: -# versions_to_pop.append(version) -# -# for version in versions_to_pop: -# msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( -# folder_entity["path"], -# product_entity["name"], -# version["version"] -# ) -# self.log.debug(( -# "Skipping version. Already tagged as `deleted`. < {} >" -# ).format(msg)) -# versions.remove(version) -# -# version_ids = [ent["id"] for ent in versions] -# -# self.log.debug( -# "Filtered versions to delete ({})".format(len(version_ids)) -# ) -# -# if not version_ids: -# msg = "Skipping processing. Nothing to delete on {}/{}".format( -# folder_entity["path"], product_entity["name"] -# ) -# self.log.info(msg) -# print(msg) -# return -# -# repres = list(ayon_api.get_representations( -# project_name, version_ids=version_ids -# )) -# -# self.log.debug( -# "Collected representations to remove ({})".format(len(repres)) -# ) -# -# dir_paths = {} -# file_paths_by_dir = collections.defaultdict(list) -# for repre in repres: -# file_path, seq_path = self.path_from_representation( -# repre, anatomy -# ) -# if file_path is None: -# self.log.debug(( -# "Could not format path for represenation \"{}\"" -# ).format(str(repre))) -# continue -# -# dir_path = os.path.dirname(file_path) -# dir_id = None -# for _dir_id, _dir_path in dir_paths.items(): -# if _dir_path == dir_path: -# dir_id = _dir_id -# break -# -# if dir_id is None: -# dir_id = uuid.uuid4() -# dir_paths[dir_id] = dir_path -# -# file_paths_by_dir[dir_id].append([file_path, seq_path]) -# -# dir_ids_to_pop = [] -# for dir_id, dir_path in dir_paths.items(): -# if os.path.exists(dir_path): -# continue -# -# dir_ids_to_pop.append(dir_id) -# -# # Pop dirs from both dictionaries -# for dir_id in dir_ids_to_pop: -# dir_paths.pop(dir_id) -# paths = file_paths_by_dir.pop(dir_id) -# # TODO report of missing directories? -# paths_msg = ", ".join([ -# "'{}'".format(path[0].replace("\\", "/")) for path in paths -# ]) -# self.log.debug(( -# "Folder does not exist. Deleting it's files skipped: {}" -# ).format(paths_msg)) -# -# return { -# "dir_paths": dir_paths, -# "file_paths_by_dir": file_paths_by_dir, -# "versions": versions, -# "folder": folder_entity, -# "product": product_entity, -# "archive_product": versions_count == 0 -# } -# -# def main(self, project_name, data, remove_publish_folder): -# # Size of files. -# size = 0 -# if not data: -# return size -# -# if remove_publish_folder: -# size = self.delete_whole_dir_paths(data["dir_paths"].values()) -# else: -# size = self.delete_only_repre_files( -# data["dir_paths"], data["file_paths_by_dir"] -# ) -# -# mongo_changes_bulk = [] -# for version in data["versions"]: -# orig_version_tags = version["data"].get("tags") or [] -# version_tags = [tag for tag in orig_version_tags] -# if "deleted" not in version_tags: -# version_tags.append("deleted") -# -# if version_tags == orig_version_tags: -# continue -# -# update_query = {"id": version["id"]} -# update_data = {"$set": {"data.tags": version_tags}} -# mongo_changes_bulk.append(UpdateOne(update_query, update_data)) -# -# if data["archive_product"]: -# mongo_changes_bulk.append(UpdateOne( -# { -# "id": data["product"]["id"], -# "type": "subset" -# }, -# {"$set": {"type": "archived_subset"}} -# )) -# -# if mongo_changes_bulk: -# dbcon = AvalonMongoDB() -# dbcon.Session["AYON_PROJECT_NAME"] = project_name -# dbcon.install() -# dbcon.bulk_write(mongo_changes_bulk) -# dbcon.uninstall() -# -# self._ftrack_delete_versions(data) -# -# return size -# -# def _ftrack_delete_versions(self, data): -# """Delete version on ftrack. -# -# Handling of ftrack logic in this plugin is not ideal. But in OP3 it is -# almost impossible to solve the issue other way. -# -# Note: -# Asset versions on ftrack are not deleted but marked as -# "not published" which cause that they're invisible. -# -# Args: -# data (dict): Data sent to product loader with full context. -# """ -# -# # First check for ftrack id on folder entity -# # - skip if ther is none -# ftrack_id = data["folder"]["attrib"].get("ftrackId") -# if not ftrack_id: -# self.log.info(( -# "Folder does not have filled ftrack id. Skipped delete" -# " of ftrack version." -# )) -# return -# -# # Check if ftrack module is enabled -# addons_manager = AddonsManager() -# ftrack_addon = addons_manager.get("ftrack") -# if not ftrack_addon or not ftrack_addon.enabled: -# return -# -# import ftrack_api -# -# session = ftrack_api.Session() -# product_name = data["product"]["name"] -# versions = { -# '"{}"'.format(version_doc["name"]) -# for version_doc in data["versions"] -# } -# asset_versions = session.query( -# ( -# "select id, is_published from AssetVersion where" -# " asset.parent.id is \"{}\"" -# " and asset.name is \"{}\"" -# " and version in ({})" -# ).format( -# ftrack_id, -# product_name, -# ",".join(versions) -# ) -# ).all() -# -# # Set attribute `is_published` to `False` on ftrack AssetVersions -# for asset_version in asset_versions: -# asset_version["is_published"] = False -# -# try: -# session.commit() -# -# except Exception: -# msg = ( -# "Could not set `is_published` attribute to `False`" -# " for selected AssetVersions." -# ) -# self.log.error(msg) -# self.message(msg) -# -# def load(self, contexts, name=None, namespace=None, options=None): -# try: -# size = 0 -# for count, context in enumerate(contexts): -# versions_to_keep = 2 -# remove_publish_folder = False -# if options: -# versions_to_keep = options.get( -# "versions_to_keep", versions_to_keep -# ) -# remove_publish_folder = options.get( -# "remove_publish_folder", remove_publish_folder -# ) -# -# data = self.get_data(context, versions_to_keep) -# if not data: -# continue -# -# project_name = context["project"]["name"] -# size += self.main(project_name, data, remove_publish_folder) -# print("Progressing {}/{}".format(count + 1, len(contexts))) -# -# msg = "Total size of files: {}".format(format_file_size(size)) -# self.log.info(msg) -# self.message(msg) -# -# except Exception: -# self.log.error("Failed to delete versions.", exc_info=True) -# -# -# class CalculateOldVersions(DeleteOldVersions): -# """Calculate file size of old versions""" -# label = "Calculate Old Versions" -# order = 30 -# tool_names = ["library_loader"] -# -# options = [ -# qargparse.Integer( -# "versions_to_keep", default=2, min=0, help="Versions to keep:" -# ), -# qargparse.Boolean( -# "remove_publish_folder", help="Remove publish folder:" -# ) -# ] -# -# def main(self, project_name, data, remove_publish_folder): -# size = 0 -# -# if not data: -# return size -# -# if remove_publish_folder: -# size = self.delete_whole_dir_paths( -# data["dir_paths"].values(), delete=False -# ) -# else: -# size = self.delete_only_repre_files( -# data["dir_paths"], data["file_paths_by_dir"], delete=False -# ) -# -# return size From be694cba5619f8cb7ac66cb378435614f52a98d8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 17:43:24 +0800 Subject: [PATCH 196/423] remove the old versions loader action should be working --- .../plugins/load/delete_old_versions.py | 478 ++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 client/ayon_core/plugins/load/delete_old_versions.py diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py new file mode 100644 index 0000000000..4f591a503a --- /dev/null +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -0,0 +1,478 @@ +import collections +import os +import uuid + +import clique +import ayon_api +import qargparse +from qtpy import QtWidgets, QtCore + +from ayon_core import style +from ayon_core.addon import AddonsManager +from ayon_core.lib import format_file_size +from ayon_core.pipeline import load, Anatomy +from ayon_core.pipeline.load import ( + get_representation_path_with_anatomy, + InvalidRepresentationContext, +) + + +class DeleteOldVersions(load.ProductLoaderPlugin): + """Deletes specific number of old version""" + + is_multiple_contexts_compatible = True + sequence_splitter = "__sequence_splitter__" + + representations = ["*"] + product_types = {"*"} + tool_names = ["library_loader"] + + label = "Delete Old Versions" + order = 35 + icon = "trash" + color = "#d8d8d8" + + options = [ + qargparse.Integer( + "versions_to_keep", default=2, min=0, help="Versions to keep:" + ), + qargparse.Boolean( + "remove_publish_folder", help="Remove publish folder:" + ) + ] + + def delete_whole_dir_paths(self, dir_paths, delete=True): + size = 0 + + for dir_path in dir_paths: + # Delete all files and fodlers in dir path + for root, dirs, files in os.walk(dir_path, topdown=False): + for name in files: + file_path = os.path.join(root, name) + size += os.path.getsize(file_path) + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + + for name in dirs: + if delete: + os.rmdir(os.path.join(root, name)) + + if not delete: + continue + + # Delete even the folder and it's parents folders if they are empty + while True: + if not os.path.exists(dir_path): + dir_path = os.path.dirname(dir_path) + continue + + if len(os.listdir(dir_path)) != 0: + break + + os.rmdir(os.path.join(dir_path)) + + return size + + def path_from_representation(self, representation, anatomy): + try: + context = representation["context"] + except KeyError: + return (None, None) + + try: + path = get_representation_path_with_anatomy( + representation, anatomy + ) + except InvalidRepresentationContext: + return (None, None) + + sequence_path = None + if "frame" in context: + context["frame"] = self.sequence_splitter + sequence_path = get_representation_path_with_anatomy( + representation, anatomy + ) + + if sequence_path: + sequence_path = sequence_path.normalized() + + return (path.normalized(), sequence_path) + + def delete_only_repre_files(self, dir_paths, file_paths, delete=True): + size = 0 + + for dir_id, dir_path in dir_paths.items(): + dir_files = os.listdir(dir_path) + collections, remainders = clique.assemble(dir_files) + for file_path, seq_path in file_paths[dir_id]: + file_path_base = os.path.split(file_path)[1] + # Just remove file if `frame` key was not in context or + # filled path is in remainders (single file sequence) + if not seq_path or file_path_base in remainders: + if not os.path.exists(file_path): + self.log.debug( + "File was not found: {}".format(file_path) + ) + continue + + size += os.path.getsize(file_path) + + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + + if file_path_base in remainders: + remainders.remove(file_path_base) + continue + + seq_path_base = os.path.split(seq_path)[1] + head, tail = seq_path_base.split(self.sequence_splitter) + + final_col = None + for collection in collections: + if head != collection.head or tail != collection.tail: + continue + final_col = collection + break + + if final_col is not None: + # Fill full path to head + final_col.head = os.path.join(dir_path, final_col.head) + for _file_path in final_col: + if os.path.exists(_file_path): + + size += os.path.getsize(_file_path) + + if delete: + os.remove(_file_path) + self.log.debug( + "Removed file: {}".format(_file_path) + ) + + _seq_path = final_col.format("{head}{padding}{tail}") + self.log.debug("Removed files: {}".format(_seq_path)) + collections.remove(final_col) + + elif os.path.exists(file_path): + size += os.path.getsize(file_path) + + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + else: + self.log.debug( + "File was not found: {}".format(file_path) + ) + + # Delete as much as possible parent folders + if not delete: + return size + + for dir_path in dir_paths.values(): + while True: + if not os.path.exists(dir_path): + dir_path = os.path.dirname(dir_path) + continue + + if len(os.listdir(dir_path)) != 0: + break + + self.log.debug("Removed folder: {}".format(dir_path)) + os.rmdir(dir_path) + + return size + + def message(self, text): + msgBox = QtWidgets.QMessageBox() + msgBox.setText(text) + msgBox.setStyleSheet(style.load_stylesheet()) + msgBox.setWindowFlags( + msgBox.windowFlags() | QtCore.Qt.FramelessWindowHint + ) + msgBox.exec_() + + def get_data(self, context, versions_count): + product_entity = context["product"] + folder_entity = context["folder"] + project_name = context["project"]["name"] + anatomy = Anatomy(project_name) + + versions = list(ayon_api.get_versions( + project_name, product_ids=[product_entity["id"]] + )) + self.log.debug( + "Version Number ({})".format(len(versions)) + ) + versions_by_parent = collections.defaultdict(list) + for ent in versions: + versions_by_parent[ent["productId"]].append(ent) + + def sort_func(ent): + return int(ent["version"]) + + all_last_versions = [] + for _parent_id, _versions in versions_by_parent.items(): + for idx, version in enumerate( + sorted(_versions, key=sort_func, reverse=True) + ): + if idx >= versions_count: + break + all_last_versions.append(version) + + self.log.debug("Collected versions ({})".format(len(versions))) + + # Filter latest versions + for version in all_last_versions: + versions.remove(version) + + # Update versions_by_parent without filtered versions + versions_by_parent = collections.defaultdict(list) + for ent in versions: + versions_by_parent[ent["productId"]].append(ent) + + # Filter already deleted versions + versions_to_pop = [] + for version in versions: + version_tags = version["data"].get("tags") + if version_tags and "deleted" in version_tags: + versions_to_pop.append(version) + + for version in versions_to_pop: + msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( + folder_entity["path"], + product_entity["name"], + version["version"] + ) + self.log.debug(( + "Skipping version. Already tagged as `deleted`. < {} >" + ).format(msg)) + versions.remove(version) + + version_ids = [ent["id"] for ent in versions] + + self.log.debug( + "Filtered versions to delete ({})".format(len(version_ids)) + ) + + if not version_ids: + msg = "Skipping processing. Nothing to delete on {}/{}".format( + folder_entity["path"], product_entity["name"] + ) + self.log.info(msg) + print(msg) + return + + repres = list(ayon_api.get_representations( + project_name, version_ids=version_ids + )) + + self.log.debug( + "Collected representations to remove ({})".format(len(repres)) + ) + + dir_paths = {} + file_paths_by_dir = collections.defaultdict(list) + for repre in repres: + file_path, seq_path = self.path_from_representation( + repre, anatomy + ) + if file_path is None: + self.log.debug(( + "Could not format path for represenation \"{}\"" + ).format(str(repre))) + continue + + dir_path = os.path.dirname(file_path) + dir_id = None + for _dir_id, _dir_path in dir_paths.items(): + if _dir_path == dir_path: + dir_id = _dir_id + break + + if dir_id is None: + dir_id = uuid.uuid4() + dir_paths[dir_id] = dir_path + + file_paths_by_dir[dir_id].append([file_path, seq_path]) + + dir_ids_to_pop = [] + for dir_id, dir_path in dir_paths.items(): + if os.path.exists(dir_path): + continue + + dir_ids_to_pop.append(dir_id) + + # Pop dirs from both dictionaries + for dir_id in dir_ids_to_pop: + dir_paths.pop(dir_id) + paths = file_paths_by_dir.pop(dir_id) + # TODO report of missing directories? + paths_msg = ", ".join([ + "'{}'".format(path[0].replace("\\", "/")) for path in paths + ]) + self.log.debug(( + "Folder does not exist. Deleting it's files skipped: {}" + ).format(paths_msg)) + + return { + "dir_paths": dir_paths, + "file_paths_by_dir": file_paths_by_dir, + "versions": versions, + "folder": folder_entity, + "product": product_entity, + "archive_product": versions_count == 0 + } + + def main(self, data, remove_publish_folder): + # Size of files. + size = 0 + if not data: + return size + + if remove_publish_folder: + size = self.delete_whole_dir_paths(data["dir_paths"].values()) + else: + size = self.delete_only_repre_files( + data["dir_paths"], data["file_paths_by_dir"] + ) + + for version in data["versions"]: + orig_version_tags = version["data"].get("tags") or [] + version_tags = [tag for tag in orig_version_tags] + if "deleted" not in version_tags: + version_tags.append("deleted") + + if version_tags == orig_version_tags: + continue + + self._ftrack_delete_versions(data) + + return size + + def _ftrack_delete_versions(self, data): + """Delete version on ftrack. + + Handling of ftrack logic in this plugin is not ideal. But in OP3 it is + almost impossible to solve the issue other way. + + Note: + Asset versions on ftrack are not deleted but marked as + "not published" which cause that they're invisible. + + Args: + data (dict): Data sent to product loader with full context. + """ + + # First check for ftrack id on folder entity + # - skip if ther is none + ftrack_id = data["folder"]["attrib"].get("ftrackId") + if not ftrack_id: + self.log.info(( + "Folder does not have filled ftrack id. Skipped delete" + " of ftrack version." + )) + return + + # Check if ftrack module is enabled + addons_manager = AddonsManager() + ftrack_addon = addons_manager.get("ftrack") + if not ftrack_addon or not ftrack_addon.enabled: + return + + import ftrack_api + + session = ftrack_api.Session() + product_name = data["product"]["name"] + versions = { + '"{}"'.format(version_doc["name"]) + for version_doc in data["versions"] + } + asset_versions = session.query( + ( + "select id, is_published from AssetVersion where" + " asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version in ({})" + ).format( + ftrack_id, + product_name, + ",".join(versions) + ) + ).all() + + # Set attribute `is_published` to `False` on ftrack AssetVersions + for asset_version in asset_versions: + asset_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) + + def load(self, contexts, name=None, namespace=None, options=None): + try: + size = 0 + for count, context in enumerate(contexts): + versions_to_keep = 2 + remove_publish_folder = False + if options: + versions_to_keep = options.get( + "versions_to_keep", versions_to_keep + ) + remove_publish_folder = options.get( + "remove_publish_folder", remove_publish_folder + ) + + data = self.get_data(context, versions_to_keep) + if not data: + continue + + size += self.main(data, remove_publish_folder) + print("Progressing {}/{}".format(count + 1, len(contexts))) + + msg = "Total size of files: {}".format(format_file_size(size)) + self.log.info(msg) + self.message(msg) + + except Exception: + self.log.error("Failed to delete versions.", exc_info=True) + + +class CalculateOldVersions(DeleteOldVersions): + """Calculate file size of old versions""" + label = "Calculate Old Versions" + order = 30 + tool_names = ["library_loader"] + + options = [ + qargparse.Integer( + "versions_to_keep", default=2, min=0, help="Versions to keep:" + ), + qargparse.Boolean( + "remove_publish_folder", help="Remove publish folder:" + ) + ] + + def main(self, data, remove_publish_folder): + size = 0 + + if not data: + return size + + if remove_publish_folder: + size = self.delete_whole_dir_paths( + data["dir_paths"].values(), delete=False + ) + else: + size = self.delete_only_repre_files( + data["dir_paths"], data["file_paths_by_dir"], delete=False + ) + + return size From a7ee5154b9867c53d0efe192b9f1f12001e032e1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 4 Apr 2024 11:35:26 +0100 Subject: [PATCH 197/423] 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 198/423] 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 199/423] 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 200/423] 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 201/423] 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 202/423] 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 203/423] 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 204/423] 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 205/423] 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 206/423] 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 207/423] 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 914367dba226b7424ef9f149e1faf68e44b82a25 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:54:01 +0800 Subject: [PATCH 208/423] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 4f591a503a..79958af447 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -196,7 +196,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): product_entity = context["product"] folder_entity = context["folder"] project_name = context["project"]["name"] - anatomy = Anatomy(project_name) + anatomy = Anatomy(project_name, project_entity=context["project"]) versions = list(ayon_api.get_versions( project_name, product_ids=[product_entity["id"]] From 03e7a85e204248012271a995ec33d3d11b528a62 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:54:12 +0800 Subject: [PATCH 209/423] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 79958af447..28e76b73d6 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -312,7 +312,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): "'{}'".format(path[0].replace("\\", "/")) for path in paths ]) self.log.debug(( - "Folder does not exist. Deleting it's files skipped: {}" + "Folder does not exist. Deleting its files skipped: {}" ).format(paths_msg)) return { From f6480281b40f8132446e09dea337e1786dee9efe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 17:40:06 +0800 Subject: [PATCH 210/423] remove ftrack-related functions --- .../plugins/load/delete_old_versions.py | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 28e76b73d6..4e2747e1dc 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -8,7 +8,6 @@ import qargparse from qtpy import QtWidgets, QtCore from ayon_core import style -from ayon_core.addon import AddonsManager from ayon_core.lib import format_file_size from ayon_core.pipeline import load, Anatomy from ayon_core.pipeline.load import ( @@ -346,76 +345,8 @@ class DeleteOldVersions(load.ProductLoaderPlugin): if version_tags == orig_version_tags: continue - self._ftrack_delete_versions(data) - return size - def _ftrack_delete_versions(self, data): - """Delete version on ftrack. - - Handling of ftrack logic in this plugin is not ideal. But in OP3 it is - almost impossible to solve the issue other way. - - Note: - Asset versions on ftrack are not deleted but marked as - "not published" which cause that they're invisible. - - Args: - data (dict): Data sent to product loader with full context. - """ - - # First check for ftrack id on folder entity - # - skip if ther is none - ftrack_id = data["folder"]["attrib"].get("ftrackId") - if not ftrack_id: - self.log.info(( - "Folder does not have filled ftrack id. Skipped delete" - " of ftrack version." - )) - return - - # Check if ftrack module is enabled - addons_manager = AddonsManager() - ftrack_addon = addons_manager.get("ftrack") - if not ftrack_addon or not ftrack_addon.enabled: - return - - import ftrack_api - - session = ftrack_api.Session() - product_name = data["product"]["name"] - versions = { - '"{}"'.format(version_doc["name"]) - for version_doc in data["versions"] - } - asset_versions = session.query( - ( - "select id, is_published from AssetVersion where" - " asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version in ({})" - ).format( - ftrack_id, - product_name, - ",".join(versions) - ) - ).all() - - # Set attribute `is_published` to `False` on ftrack AssetVersions - for asset_version in asset_versions: - asset_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." - ) - self.log.error(msg) - self.message(msg) - def load(self, contexts, name=None, namespace=None, options=None): try: size = 0 From 043b528155450ca81dba41ca8d7a49667f6f282f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 18:43:59 +0800 Subject: [PATCH 211/423] uses OperationsSessions from ayon_api to delete version version_tags --- .../plugins/load/delete_old_versions.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 4e2747e1dc..ee116a71cf 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -4,6 +4,7 @@ import uuid import clique import ayon_api +from ayon_api.operations import OperationsSession import qargparse from qtpy import QtWidgets, QtCore @@ -231,20 +232,16 @@ class DeleteOldVersions(load.ProductLoaderPlugin): versions_by_parent[ent["productId"]].append(ent) # Filter already deleted versions - versions_to_pop = [] for version in versions: - version_tags = version["data"].get("tags") - if version_tags and "deleted" in version_tags: - versions_to_pop.append(version) - - for version in versions_to_pop: + if version["active"] or "deleted" in version["tags"]: + continue msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], product_entity["name"], version["version"] ) self.log.debug(( - "Skipping version. Already tagged as `deleted`. < {} >" + "Skipping version. Already tagged as inactive. < {} >" ).format(msg)) versions.remove(version) @@ -323,7 +320,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): "archive_product": versions_count == 0 } - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): # Size of files. size = 0 if not data: @@ -336,14 +333,25 @@ class DeleteOldVersions(load.ProductLoaderPlugin): data["dir_paths"], data["file_paths_by_dir"] ) + op_session = OperationsSession() for version in data["versions"]: - orig_version_tags = version["data"].get("tags") or [] - version_tags = [tag for tag in orig_version_tags] + orig_version_tags = version["tags"] + version_tags = list(orig_version_tags) + changes = {} if "deleted" not in version_tags: version_tags.append("deleted") + changes["tags"] = version_tags - if version_tags == orig_version_tags: + if version["active"]: + changes["active"] = False + + if not changes: continue + op_session.update_entity( + project_name, "version", version["id"], changes + ) + + op_session.commit() return size @@ -364,8 +372,8 @@ class DeleteOldVersions(load.ProductLoaderPlugin): data = self.get_data(context, versions_to_keep) if not data: continue - - size += self.main(data, remove_publish_folder) + project_name = context["project"]["name"] + size += self.main(project_name, data, remove_publish_folder) print("Progressing {}/{}".format(count + 1, len(contexts))) msg = "Total size of files: {}".format(format_file_size(size)) From c8f4f3681ac0290b13debe1283bdf1e47426d3c1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 18:45:23 +0800 Subject: [PATCH 212/423] make sure to skip hero version --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index ee116a71cf..bfbccce33d 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -199,7 +199,10 @@ class DeleteOldVersions(load.ProductLoaderPlugin): anatomy = Anatomy(project_name, project_entity=context["project"]) versions = list(ayon_api.get_versions( - project_name, product_ids=[product_entity["id"]] + project_name, + product_ids=[product_entity["id"]], + active=None, + hero=False )) self.log.debug( "Version Number ({})".format(len(versions)) From cf5d7d9a7d12c65f723cfbd044d9d68c01023a72 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:52:34 +0800 Subject: [PATCH 213/423] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index bfbccce33d..78603104d8 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -236,7 +236,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): # Filter already deleted versions for version in versions: - if version["active"] or "deleted" in version["tags"]: + if "deleted" in version["tags"]: continue msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], From df201386f52721803aa6280da236886881ee6887 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 16:04:25 +0200 Subject: [PATCH 214/423] 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 215/423] 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 216/423] 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 364dee88f1a99e3410b6cc0d31566ceb25485b67 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 10 Apr 2024 22:03:37 +0800 Subject: [PATCH 217/423] add the version tags if there is not one --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 78603104d8..fd331ec14b 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -198,11 +198,14 @@ class DeleteOldVersions(load.ProductLoaderPlugin): project_name = context["project"]["name"] anatomy = Anatomy(project_name, project_entity=context["project"]) + version_fields = ayon_api.get_default_fields_for_type("version") + version_fields.add("tags") versions = list(ayon_api.get_versions( project_name, product_ids=[product_entity["id"]], active=None, - hero=False + hero=False, + fields=version_fields )) self.log.debug( "Version Number ({})".format(len(versions)) From c5ccf8a3904dd9a2dbe5c14538044e39fe89dece Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 10 Apr 2024 23:24:28 +0800 Subject: [PATCH 218/423] add texture resolution setting when loading mesh to set up project in substance painter --- .../plugins/load/load_mesh.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index d940d7b05c..07b53fb85c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,6 +2,7 @@ from ayon_core.pipeline import ( load, get_representation_path, ) +from ayon_core.lib import BoolDef, EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, @@ -11,7 +12,6 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh import substance_painter.project -import qargparse class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -25,26 +25,35 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): icon = "code-fork" color = "orange" - options = [ - qargparse.Boolean( - "preserve_strokes", - default=True, - help="Preserve strokes positions on mesh.\n" - "(only relevant when loading into existing project)" - ), - qargparse.Boolean( - "import_cameras", - default=True, - help="Import cameras from the mesh file." - ) - ] + @classmethod + def get_options(cls, contexts): + return [ + BoolDef("preserve_strokes", + default=True, + label="Preserve Strokes", + tooltip=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)")), + BoolDef("import_cameras", + default=True, + label="Import Cameras", + tooltip="Import cameras from the mesh file." + ), + EnumDef("texture_resolution", + items=[128, 256, 512, 1024, 2048, 4096], + default=1024, + label="Texture Resolution", + tooltip="Set texture resolution for the project") + ] - def load(self, context, name, namespace, data): + def load(self, context, name, namespace, options=None): # Get user inputs - import_cameras = data.get("import_cameras", True) - preserve_strokes = data.get("preserve_strokes", True) + import_cameras = options.get("import_cameras", True) + preserve_strokes = options.get("preserve_strokes", True) + texture_resolution = options.get("texture_resolution", 1024) sp_settings = substance_painter.project.Settings( + default_texture_resolution=texture_resolution, import_cameras=import_cameras ) if not substance_painter.project.is_open(): From d16945311574f13fcbf9311b58273737bcb438fb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:16:51 +0100 Subject: [PATCH 219/423] 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 220/423] 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 221/423] 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 222/423] 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 223/423] 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 224/423] 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 225/423] 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 226/423] 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 227/423] 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 228/423] 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 6758a2a7c57ceb4f9d8e707506321830a478b7ae Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Apr 2024 18:17:36 +0800 Subject: [PATCH 229/423] add the boolean options to allow user to set the project setting by their own --- client/ayon_core/hosts/substancepainter/api/lib.py | 6 +++--- .../hosts/substancepainter/plugins/load/load_mesh.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 1cb480b552..95d45da436 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -549,7 +549,7 @@ def _get_new_project_action(): return new_action -def prompt_new_file_with_mesh(mesh_filepath): +def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): """Prompts the user for a new file using Substance Painter's own dialog. This will set the mesh path to load to the given mesh and disables the @@ -586,7 +586,6 @@ def prompt_new_file_with_mesh(mesh_filepath): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) @@ -624,7 +623,8 @@ def prompt_new_file_with_mesh(mesh_filepath): f"{mesh_filepath}\n\n" "Creating new project directly with the mesh path instead.") else: - dialog.done(dialog.Accepted) + if not allow_user_setting: + dialog.done(dialog.Accepted) new_action = _get_new_project_action() if not new_action: diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 07b53fb85c..8809f3b5bf 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -28,6 +28,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): return [ + BoolDef("allow_user_setting", + default=True, + label="Allow User Setting", + tooltip=("Allow user to set up the project" + " by their own\n")), BoolDef("preserve_strokes", default=True, label="Preserve Strokes", @@ -49,6 +54,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs + allow_user_setting = options.get("allow_user_setting", True) import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) @@ -61,7 +67,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): path = self.filepath_from_context(context) # TODO: improve the prompt dialog function to not # only works for simple polygon scene - result = prompt_new_file_with_mesh(mesh_filepath=path) + result = prompt_new_file_with_mesh( + mesh_filepath=path, allow_user_setting=allow_user_setting) if not result: self.log.info("User cancelled new project prompt." "Creating new project directly from" From be4d65c3d234826ab56d5b5276f35fde55e87d90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Apr 2024 18:26:31 +0800 Subject: [PATCH 230/423] cosmetic fix --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 8809f3b5bf..b355400233 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -48,7 +48,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): items=[128, 256, 512, 1024, 2048, 4096], default=1024, label="Texture Resolution", - tooltip="Set texture resolution for the project") + tooltip="Set texture resolution when creating new project") ] def load(self, context, name, namespace, options=None): From 1229311d679283e2d8b93e0881b3d85126955da8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 00:20:05 +0800 Subject: [PATCH 231/423] commit to test on the triggering different file format and file size --- client/ayon_core/hosts/substancepainter/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 95d45da436..b1aed5d4f3 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -577,7 +577,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): assert isinstance(file_dialog, QtWidgets.QFileDialog) # Quickly hide the dialog - file_dialog.hide() + # file_dialog.hide() app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) file_dialog.setDirectory(os.path.dirname(mesh_filepath)) @@ -586,7 +586,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - file_dialog.done(file_dialog.Accepted) + # file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): From a7450e4ab5c72e9b33fa6909371dd23f6f717d45 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 11 Apr 2024 19:44:31 +0100 Subject: [PATCH 232/423] 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 ede44ea0fba00303dd56986e5b6ab7920182bc08 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 17:00:02 +0800 Subject: [PATCH 233/423] specific the time for the process event --- client/ayon_core/hosts/substancepainter/api/lib.py | 11 ++++------- .../hosts/substancepainter/plugins/load/load_mesh.py | 3 +-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index b1aed5d4f3..64c39943ce 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -549,7 +549,7 @@ def _get_new_project_action(): return new_action -def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): +def prompt_new_file_with_mesh(mesh_filepath): """Prompts the user for a new file using Substance Painter's own dialog. This will set the mesh path to load to the given mesh and disables the @@ -577,7 +577,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): assert isinstance(file_dialog, QtWidgets.QFileDialog) # Quickly hide the dialog - # file_dialog.hide() + file_dialog.hide() app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) file_dialog.setDirectory(os.path.dirname(mesh_filepath)) @@ -586,7 +586,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - # file_dialog.done(file_dialog.Accepted) + file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): @@ -605,7 +605,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): mesh_select.setVisible(False) # Ensure UI is visually up-to-date - app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 8000) # Trigger the 'select file' dialog to set the path and have the # new file dialog to use the path. @@ -622,9 +622,6 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): "Failed to set mesh path with the prompt dialog:" f"{mesh_filepath}\n\n" "Creating new project directly with the mesh path instead.") - else: - if not allow_user_setting: - dialog.done(dialog.Accepted) new_action = _get_new_project_action() if not new_action: diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 6a521493db..89dbcdbddd 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -67,8 +67,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): path = self.filepath_from_context(context) # TODO: improve the prompt dialog function to not # only works for simple polygon scene - result = prompt_new_file_with_mesh( - mesh_filepath=path, allow_user_setting=allow_user_setting) + result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: self.log.info("User cancelled new project prompt." "Creating new project directly from" From dada34d6f4bb6b263298dfd5a4c91fd73f47b31c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 17:00:35 +0800 Subject: [PATCH 234/423] remove allow user settings --- .../hosts/substancepainter/plugins/load/load_mesh.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 89dbcdbddd..ad81309957 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -28,11 +28,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): return [ - BoolDef("allow_user_setting", - default=True, - label="Allow User Setting", - tooltip=("Allow user to set up the project" - " by their own\n")), BoolDef("preserve_strokes", default=True, label="Preserve Strokes", @@ -54,7 +49,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - allow_user_setting = options.get("allow_user_setting", True) import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) From 74bc0102c829bfad88bbad6d40f8dee6da972447 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 18:23:33 +0800 Subject: [PATCH 235/423] add supports for udim settings in mesh loader --- .../substancepainter/plugins/load/load_mesh.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index ad81309957..562ccc1f80 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -27,6 +27,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): + project_workflow_option = { + substance_painter.project.ProjectWorkflow.Default: "default", + substance_painter.project.ProjectWorkflow.UVTile: "uvTile", + substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa + } return [ BoolDef("preserve_strokes", default=True, @@ -43,7 +48,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): items=[128, 256, 512, 1024, 2048, 4096], default=1024, label="Texture Resolution", - tooltip="Set texture resolution when creating new project") + tooltip="Set texture resolution when creating new project"), + EnumDef("project_uv_workflow", + items=project_workflow_option, + default="default", + label="UV Workflow", + tooltip="Set UV workflow when creating new project") ] def load(self, context, name, namespace, options=None): @@ -52,9 +62,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) + uv_workflow = options.get("project_uv_workflow", "default") sp_settings = substance_painter.project.Settings( default_texture_resolution=texture_resolution, - import_cameras=import_cameras + import_cameras=import_cameras, + project_workflow=uv_workflow ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project From 953238490bf943c083dcbd975abb9bc55b617588 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:30:19 +0100 Subject: [PATCH 236/423] 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 237/423] 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 238/423] 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 239/423] 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 240/423] 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 241/423] 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 242/423] 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 243/423] 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 244/423] 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 245/423] 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 246/423] 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 247/423] 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 248/423] 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 249/423] 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 250/423] 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 251/423] 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 252/423] 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 253/423] 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 254/423] 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 255/423] 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 256/423] 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 257/423] 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 258/423] 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 259/423] 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 ef71dad1d35f025627645e2c63033fd51c0be861 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Apr 2024 17:37:43 +0800 Subject: [PATCH 260/423] rename project_uv_workflow_items --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 562ccc1f80..0816a67b6a 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -27,7 +27,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): - project_workflow_option = { + project_uv_workflow_items = { substance_painter.project.ProjectWorkflow.Default: "default", substance_painter.project.ProjectWorkflow.UVTile: "uvTile", substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa @@ -50,7 +50,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): label="Texture Resolution", tooltip="Set texture resolution when creating new project"), EnumDef("project_uv_workflow", - items=project_workflow_option, + items=project_uv_workflow_items, default="default", label="UV Workflow", tooltip="Set UV workflow when creating new project") From c9919ec8801a3112b8147ff31e5f7b611b36f942 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 10:38:45 +0100 Subject: [PATCH 261/423] 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 262/423] 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 263/423] 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 264/423] 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 265/423] 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 f2a9eedbda9fca088629a0fa95be4771b6365680 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Apr 2024 21:27:40 +0800 Subject: [PATCH 266/423] fix the default settings not being able to be cased as python instance --- .../plugins/load/load_mesh.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 0816a67b6a..03f47eb451 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -14,6 +14,15 @@ from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh import substance_painter.project +def get_uv_workflow(uv_option="default"): + if uv_option == "default": + return substance_painter.project.ProjectWorkflow.Default + elif uv_option == "uvTile": + return substance_painter.project.ProjectWorkflow.UVTile + else: + return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile + + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -50,7 +59,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): label="Texture Resolution", tooltip="Set texture resolution when creating new project"), EnumDef("project_uv_workflow", - items=project_uv_workflow_items, + items=["default", "uvTile", "textureSetPerUVTile"], default="default", label="UV Workflow", tooltip="Set UV workflow when creating new project") @@ -62,7 +71,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) - uv_workflow = options.get("project_uv_workflow", "default") + uv_option = options.get("project_uv_workflow", "default") + uv_workflow = get_uv_workflow(uv_option=uv_option) sp_settings = substance_painter.project.Settings( default_texture_resolution=texture_resolution, import_cameras=import_cameras, @@ -75,12 +85,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # only works for simple polygon scene result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: - self.log.info("User cancelled new project prompt." - "Creating new project directly from" - " Substance Painter API Instead.") - settings = substance_painter.project.create( - mesh_file_path=path, settings=sp_settings - ) + if not substance_painter.project.is_open(): + self.log.info("User cancelled new project prompt." + "Creating new project directly from" + " Substance Painter API Instead.") + settings = substance_painter.project.create( + mesh_file_path=path, settings=sp_settings + ) + else: + self.log.info("The project is already created after " + "the new project prompt action") else: # Reload the mesh From affb42e2eda5eb227f40f10e75db795fcd11ef84 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 14:58:31 +0100 Subject: [PATCH 267/423] 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 268/423] 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 269/423] 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 028375620fe7a7c44955e4ec6c025f4cf9ed13a0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:02:56 +0200 Subject: [PATCH 270/423] filter representations by id earlier --- .../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 5e63ba444a..20783dd1ca 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1587,14 +1587,14 @@ class PlaceholderLoadMixin(object): placeholder_representations = self._get_representations(placeholder) - filtered_representations = [] - for representation in self._reduce_last_version_repre_entities( - placeholder_representations - ): - repre_id = representation["id"] - if repre_id not in ignore_repre_ids: - filtered_representations.append(representation) - + filtered_representations = [ + repre_entity + for repre_entity in self._get_representations(placeholder) + if repre_entity["id"] not in ignore_repre_ids + ] + filtered_representations = self._reduce_last_version_repre_entities( + filtered_representations + ) if not filtered_representations: self.log.info(( "There's no representation for this placeholder: {}" From f3ab54493d3f2c276680fe4aca6c931800093d03 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:03:31 +0200 Subject: [PATCH 271/423] get and use repre contexts earlier --- .../workfile/workfile_template_builder.py | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 20783dd1ca..ae54b732f3 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1529,35 +1529,24 @@ class PlaceholderLoadMixin(object): pass - def _reduce_last_version_repre_entities(self, representations): + def _reduce_last_version_repre_entities(self, repre_contexts): """Reduce representations to last verison.""" mapping = {} - # TODO use representation context with entities - # - using 'folder', 'subset' and 'version' from context on - # representation is danger - for repre_entity in representations: - repre_context = repre_entity["context"] + for repre_context in repre_contexts: + folder_id = repre_context["folder"]["id"] + product_id = repre_context["product"]["id"] + version = repre_context["version"]["version"] - folder_name = repre_context["asset"] - product_name = repre_context["subset"] - version = repre_context.get("version", -1) + parents_path = "/".join([folder_id, product_id]) + version_mapping = mapping.setdefault(parents_path, {}) - if folder_name not in mapping: - mapping[folder_name] = {} - - product_mapping = mapping[folder_name] - if product_name not in product_mapping: - product_mapping[product_name] = collections.defaultdict(list) - - version_mapping = product_mapping[product_name] - version_mapping[version].append(repre_entity) + version_mapping[version].append(repre_context) output = [] - for product_mapping in mapping.values(): - for version_mapping in product_mapping.values(): - last_version = tuple(sorted(version_mapping.keys()))[-1] - output.extend(version_mapping[last_version]) + for version_mapping in mapping.values(): + last_version = tuple(sorted(version_mapping.keys()))[-1] + output.extend(version_mapping[last_version]) return output def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): @@ -1585,32 +1574,31 @@ class PlaceholderLoadMixin(object): loader_name = placeholder.data["loader"] loader_args = self.parse_loader_args(placeholder.data["loader_args"]) - placeholder_representations = self._get_representations(placeholder) - - filtered_representations = [ + placeholder_representations = [ repre_entity for repre_entity in self._get_representations(placeholder) if repre_entity["id"] not in ignore_repre_ids ] - filtered_representations = self._reduce_last_version_repre_entities( - filtered_representations + + repre_load_contexts = get_representation_contexts( + self.project_name, placeholder_representations ) - if not filtered_representations: + filtered_repre_contexts = self._reduce_last_version_repre_entities( + repre_load_contexts + ) + if not filtered_repre_contexts: self.log.info(( "There's no representation for this placeholder: {}" ).format(placeholder.scene_identifier)) return - repre_load_contexts = get_representation_contexts( - self.project_name, filtered_representations - ) loaders_by_name = self.builder.get_loaders_by_name() self._before_placeholder_load( placeholder ) failed = False - for repre_load_context in repre_load_contexts.values(): + for repre_load_context in filtered_repre_contexts: folder_path = repre_load_context["folder"]["path"] product_name = repre_load_context["product"]["name"] representation = repre_load_context["representation"] From b1e73835bbe9868b0dbf30ed5ce8fcb17c4646c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:25:26 +0200 Subject: [PATCH 272/423] skip folder id in mapping --- .../pipeline/workfile/workfile_template_builder.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index ae54b732f3..3e4d47f195 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1532,19 +1532,17 @@ class PlaceholderLoadMixin(object): def _reduce_last_version_repre_entities(self, repre_contexts): """Reduce representations to last verison.""" - mapping = {} + version_mapping_by_product_id = {} for repre_context in repre_contexts: - folder_id = repre_context["folder"]["id"] product_id = repre_context["product"]["id"] version = repre_context["version"]["version"] - - parents_path = "/".join([folder_id, product_id]) - version_mapping = mapping.setdefault(parents_path, {}) - + version_mapping = version_mapping_by_product_id.setdefault( + product_id, {} + ) version_mapping[version].append(repre_context) output = [] - for version_mapping in mapping.values(): + for version_mapping in version_mapping_by_product_id.values(): last_version = tuple(sorted(version_mapping.keys()))[-1] output.extend(version_mapping[last_version]) return output From 78c73259ef4d724b72a7c442c3dcda7c3752a6d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:30:10 +0200 Subject: [PATCH 273/423] fix typo Co-authored-by: Roy Nieterau --- 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 3e4d47f195..c3b1267466 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1530,7 +1530,7 @@ class PlaceholderLoadMixin(object): pass def _reduce_last_version_repre_entities(self, repre_contexts): - """Reduce representations to last verison.""" + """Reduce representations to last version.""" version_mapping_by_product_id = {} for repre_context in repre_contexts: From 64a26e21874681102e73cd434de95699f6ac6b09 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 17:40:18 +0800 Subject: [PATCH 274/423] update server-addon settings for template --- .../substancepainter/server/settings/main.py | 79 ++++++++++++++++++- .../substancepainter/server/version.py | 2 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/server_addon/substancepainter/server/settings/main.py b/server_addon/substancepainter/server/settings/main.py index f80fa9fe1e..20cf6d77b2 100644 --- a/server_addon/substancepainter/server/settings/main.py +++ b/server_addon/substancepainter/server/settings/main.py @@ -2,6 +2,78 @@ from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS +def normal_map_format_enum(): + return [ + {"label": "DirectX", "value": "DirectX"}, + {"label": "OpenGL", "value": "OpenGL"}, + ] + + +def tangent_space_enum(): + return [ + {"label": "PerFragment", "value": "PerFragment"}, + {"label": "PerVertex", "value": "PerVertex"}, + ] + + +def uv_workflow_enum(): + return [ + {"label": "Default", "value": "default"}, + {"label": "UV Tile", "value": "uvTile"}, + {"label": "Texture Set Per UV Tile", + "value": "textureSetPerUVTile"} + ] + + +def document_resolution_enum(): + return [ + {"label": "128", "value": 128}, + {"label": "256", "value": 256}, + {"label": "512", "value": 512}, + {"label": "1024", "value": 1024}, + {"label": "2048", "value": 2048}, + {"label": "4096", "value": 4096} + ] + + +class ProjectTemplatesModel(BaseSettingsModel): + _layout = "expanded" + name: str = SettingsField(title="Template Name") + document_resolution: int = SettingsField( + 1024, enum_resolver=document_resolution_enum, + title="Document Resolution", + description=("Set texture resolution when " + "creating new project.") + ) + normal_map_format: str = SettingsField( + "DirectX", enum_resolver=normal_map_format_enum, + title="Normal Map Format", + description=("Set normal map format when " + "creating new project.") + ) + tangent_space: str = SettingsField( + "PerFragment", enum_resolver=tangent_space_enum, + title="Tangent Space", + description=("An option to compute tangent space " + "when creating new project.") + ) + uv_workflow: str = SettingsField( + "default", enum_resolver=uv_workflow_enum, + title="UV Tile Settings", + description=("Set UV workflow when " + "creating new project.") + ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) + + class ShelvesSettingsModel(BaseSettingsModel): _layout = "compact" name: str = SettingsField(title="Name") @@ -17,9 +89,14 @@ class SubstancePainterSettings(BaseSettingsModel): default_factory=list, title="Shelves" ) + project_templates: list[ProjectTemplatesModel] = SettingsField( + default_factory=ProjectTemplatesModel, + title="Project Templates" + ) DEFAULT_SPAINTER_SETTINGS = { "imageio": DEFAULT_IMAGEIO_SETTINGS, - "shelves": [] + "shelves": [], + "project_templates": [], } diff --git a/server_addon/substancepainter/server/version.py b/server_addon/substancepainter/server/version.py index 3dc1f76bc6..485f44ac21 100644 --- a/server_addon/substancepainter/server/version.py +++ b/server_addon/substancepainter/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" From a3f9651de1a8825c0827d731b1467c9f01fb3e2a Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 16 Apr 2024 11:56:19 +0200 Subject: [PATCH 275/423] use ext.lower() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- .../hosts/houdini/plugins/publish/extract_composite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py index fe88adc120..0fab69ef4a 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py @@ -46,7 +46,7 @@ class ExtractComposite(publish.Extractor, "frameEnd": instance.data["frameEndHandle"], } - if ext == "exr": + if ext.lower() == "exr": # Inject colorspace with 'scene_linear' as that's the # default Houdini working colorspace and all extracted # OpenEXR images should be in that colorspace. From 00926cf9e95d19eef828344d8eff3e5c59ab532c Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 16 Apr 2024 11:58:50 +0200 Subject: [PATCH 276/423] update import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c9f79c74b9..2e97c06bff 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 @@ -1,4 +1,4 @@ -from ayon_core.lib.applications import PreLaunchHook, LaunchTypes +from ayon_applications import PreLaunchHook, LaunchTypes class SetDefaultDisplayView(PreLaunchHook): From b53281f4905115d18419279ef6a6846c3df95b6f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 12:45:34 +0200 Subject: [PATCH 277/423] update import --- client/ayon_core/hosts/houdini/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 3da81b2b51..9cac2ab210 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -839,7 +839,7 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): # fall to default review color space if the setting is empty. if not review_color_space: - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa review_color_space = get_default_display_view_colorspace() opengl_node.setParms( From e8e1a1a2dffd197e61101ccc910c9d3f69c01a1f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 14:09:21 +0200 Subject: [PATCH 278/423] add missing logic of set_review_color_space --- client/ayon_core/hosts/houdini/api/lib.py | 7 +- .../houdini/plugins/create/create_review.py | 43 ++++----- .../publish/validate_review_colorspace.py | 95 +++++++++++++------ 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 9cac2ab210..7ca8f7f8f0 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -837,16 +837,11 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # fall to default review color space if the setting is empty. - if not review_color_space: - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - review_color_space = get_default_display_view_colorspace() - opengl_node.setParms( {"ociocolorspace": review_color_space} ) - self.log.debug( + log.debug( "'OCIO Colorspace' parm on '{}' has been set to " "the view color space '{}'" .format(opengl_node, review_color_space) 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 18f7ce498d..94dcf23181 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_review.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_review.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating openGL reviews.""" -from ayon_core.hosts.houdini.api import plugin +from ayon_core.hosts.houdini.api import lib, plugin from ayon_core.lib import EnumDef, BoolDef, NumberDef import os @@ -14,6 +14,13 @@ class CreateReview(plugin.HoudiniCreator): label = "Review" product_type = "review" icon = "video-camera" + review_color_space = "" + + def apply_settings(self, project_settings): + super(CreateReview, self).apply_settings(project_settings) + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + self.review_color_space = color_settings.get("review_color_space") def create(self, product_name, instance_data, pre_create_data): @@ -85,10 +92,20 @@ class CreateReview(plugin.HoudiniCreator): instance_node.setParms(parms) - # Set OCIO Colorspace to the default output colorspace + # Set OCIO Colorspace to the default colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_colorcorrect_to_default_view_space(instance_node) + # Fall to the default value if cls.review_color_space is empty. + if not self.review_color_space: + # cls.review_color_space is an empty string + # when the imageio/workfile setting is disabled or + # when the Review colorspace setting is empty. + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + self.review_color_space = get_default_display_view_colorspace() + + lib.set_review_color_space(instance_node, + self.review_color_space, + self.log) to_lock = ["id", "productType"] @@ -131,23 +148,3 @@ class CreateReview(plugin.HoudiniCreator): minimum=0.0001, decimals=3) ] - - def set_colorcorrect_to_default_view_space(self, - instance_node): - """Set ociocolorspace to the default output space.""" - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - - # set Color Correction parameter to OpenColorIO - instance_node.setParms({"colorcorrect": 2}) - - # Get default view space for ociocolorspace parm. - default_view_space = get_default_display_view_colorspace() - instance_node.setParms( - {"ociocolorspace": default_view_space} - ) - - self.log.debug( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(instance_node, default_view_space) - ) 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 031138e21d..d3afa83b67 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 @@ -4,15 +4,19 @@ from ayon_core.pipeline import ( PublishValidationError, OptionalPyblishPluginMixin ) -from ayon_core.pipeline.publish import RepairAction +from ayon_core.pipeline.publish import ( + RepairAction, + get_plugin_settings, + apply_plugin_settings_automatically +) from ayon_core.hosts.houdini.api.action import SelectROPAction import os import hou -class SetDefaultViewSpaceAction(RepairAction): - label = "Set default view colorspace" +class ResetViewSpaceAction(RepairAction): + label = "Reset OCIO colorspace parm" icon = "mdi.monitor" @@ -27,9 +31,25 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, families = ["review"] hosts = ["houdini"] label = "Validate Review Colorspace" - actions = [SetDefaultViewSpaceAction, SelectROPAction] + actions = [ResetViewSpaceAction, SelectROPAction] optional = True + review_color_space = "" + + @classmethod + def apply_settings(cls, project_settings): + # Preserve automatic settings applying logic + settings = get_plugin_settings(plugin=cls, + project_settings=project_settings, + log=cls.log, + category="houdini") + apply_plugin_settings_automatically(cls, settings, logger=cls.log) + + # Add review color settings + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + cls.review_color_space = color_settings.get("review_color_space") + def process(self, instance): @@ -52,39 +72,54 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, " 'OpenColorIO'".format(rop_node.path()) ) - if rop_node.evalParm("ociocolorspace") not in \ - hou.Color.ocio_spaces(): - + current_color_space = rop_node.evalParm("ociocolorspace") + if current_color_space not in hou.Color.ocio_spaces(): raise PublishValidationError( "Invalid value: Colorspace name doesn't exist.\n" "Check 'OCIO Colorspace' parameter on '{}' ROP" .format(rop_node.path()) ) - @classmethod - def repair(cls, instance): - """Set Default View Space Action. + # if houdini/imageio/workfile is enabled and + # Review colorspace setting is empty then this check should + # actually check if the current_color_space setting equals + # the default colorspace value. + # However, it will make the black cmd screen show up more often + # which is very annoying. + if self.review_color_space and \ + self.review_color_space != current_color_space: - It is a helper action more than a repair action, - used to set colorspace on opengl node to the default view. - """ - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - - rop_node = hou.node(instance.data["instance_node"]) - - if rop_node.evalParm("colorcorrect") != 2: - rop_node.setParms({"colorcorrect": 2}) - cls.log.debug( - "'Color Correction' parm on '{}' has been set to" - " 'OpenColorIO'".format(rop_node.path()) + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match" + "the Colorspace specified in settings." ) - # Get default view colorspace name - default_view_space = get_default_display_view_colorspace() + @classmethod + def repair(cls, instance): + """Reset view colorspace. - rop_node.setParms({"ociocolorspace": default_view_space}) - cls.log.info( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(rop_node, default_view_space) - ) + It is used to set colorspace on opengl node. + + It uses the colorspace value specified in the Houdini addon settings. + If the value in the Houdini addon settings is empty, + it will fall to the default colorspace. + + Note: + This repair action assumes that OCIO is enabled. + As if OCIO is disabled the whole validation is skipped + and this repair action won't show up. + """ + from ayon_core.hosts.houdini.api.lib import set_review_color_space + + # Fall to the default value if cls.review_color_space is empty. + if not cls.review_color_space: + # cls.review_color_space is an empty string + # when the imageio/workfile setting is disabled or + # when the Review colorspace setting is empty. + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + cls.review_color_space = get_default_display_view_colorspace() + + rop_node = hou.node(instance.data["instance_node"]) + set_review_color_space(rop_node, + cls.review_color_space, + cls.log) From e5eec7f558e20e138fa532c3a881c27fa89d39d7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 22:53:09 +0800 Subject: [PATCH 279/423] update the settings and code tweaks for creating project --- .../hosts/substancepainter/api/lib.py | 36 +++++++ .../plugins/load/load_mesh.py | 79 ++++---------- .../server/settings/load_plugins.py | 102 ++++++++++++++++++ .../substancepainter/server/settings/main.py | 81 +------------- 4 files changed, 160 insertions(+), 138 deletions(-) create mode 100644 server_addon/substancepainter/server/settings/load_plugins.py diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 64c39943ce..e344076222 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -640,3 +640,39 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh + + +def convert_substance_object_to_python(subst_proj_option="default"): + if subst_proj_option == "default": + return substance_painter.project.ProjectWorkflow.Default + elif subst_proj_option == "uvTile": + return substance_painter.project.ProjectWorkflow.UVTile + elif subst_proj_option == "textureSetPerUVTile": + return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile + elif subst_proj_option == "PerFragment": + return substance_painter.project.TangentSpace.PerFragment + elif subst_proj_option == "PerVertex": + return substance_painter.project.TangentSpace.PerVertex + elif subst_proj_option == "DirectX": + return substance_painter.project.NormalMapFormat.DirectX + elif subst_proj_option == "OpenGL": + return substance_painter.project.NormalMapFormat.OpenGL + else: + raise ValueError( + f"Unsupported Substance Objects: {subst_proj_option}") + + +def parse_substance_attributes_setting(template_name, project_templates): + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + attributes_data.update(template) + attributes_data["normal_map_format"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["normal_map_format"]) + attributes_data["project_workflow"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["project_workflow"]) + attributes_data["tangent_space_mode"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["tangent_space_mode"]) + attributes_data.pop("name") + attributes_data.pop("preserve_strokes") + return attributes_data diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 03f47eb451..563d6eb6e1 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -9,20 +9,14 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh +from ayon_core.hosts.substancepainter.api.lib import ( + prompt_new_file_with_mesh, + parse_substance_attributes_setting +) import substance_painter.project -def get_uv_workflow(uv_option="default"): - if uv_option == "default": - return substance_painter.project.ProjectWorkflow.Default - elif uv_option == "uvTile": - return substance_painter.project.ProjectWorkflow.UVTile - else: - return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile - - class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -33,74 +27,37 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): order = -10 icon = "code-fork" color = "orange" + project_templates = [] @classmethod def get_options(cls, contexts): - project_uv_workflow_items = { - substance_painter.project.ProjectWorkflow.Default: "default", - substance_painter.project.ProjectWorkflow.UVTile: "uvTile", - substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa - } + template_enum = [template["name"] for template in cls.project_templates] return [ - BoolDef("preserve_strokes", - default=True, - label="Preserve Strokes", - tooltip=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)")), - BoolDef("import_cameras", - default=True, - label="Import Cameras", - tooltip="Import cameras from the mesh file." - ), - EnumDef("texture_resolution", - items=[128, 256, 512, 1024, 2048, 4096], - default=1024, - label="Texture Resolution", - tooltip="Set texture resolution when creating new project"), - EnumDef("project_uv_workflow", - items=["default", "uvTile", "textureSetPerUVTile"], + EnumDef("project_template", + items=template_enum, default="default", - label="UV Workflow", - tooltip="Set UV workflow when creating new project") + label="Project Template") ] def load(self, context, name, namespace, options=None): # Get user inputs - import_cameras = options.get("import_cameras", True) - preserve_strokes = options.get("preserve_strokes", True) - texture_resolution = options.get("texture_resolution", 1024) - uv_option = options.get("project_uv_workflow", "default") - uv_workflow = get_uv_workflow(uv_option=uv_option) - sp_settings = substance_painter.project.Settings( - default_texture_resolution=texture_resolution, - import_cameras=import_cameras, - project_workflow=uv_workflow - ) + template_name = options.get("project_template", "default") + template_settings = parse_substance_attributes_setting(template_name, self.project_templates) + sp_settings = substance_painter.project.Settings(**template_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) - # TODO: improve the prompt dialog function to not - # only works for simple polygon scene - result = prompt_new_file_with_mesh(mesh_filepath=path) - if not result: - if not substance_painter.project.is_open(): - self.log.info("User cancelled new project prompt." - "Creating new project directly from" - " Substance Painter API Instead.") - settings = substance_painter.project.create( - mesh_file_path=path, settings=sp_settings - ) - else: - self.log.info("The project is already created after " - "the new project prompt action") + settings = substance_painter.project.create( + mesh_file_path=path, settings=sp_settings + ) else: # Reload the mesh + # TODO: fix the hardcoded when the preset setting in SP addon. settings = substance_painter.project.MeshReloadingSettings( - import_cameras=import_cameras, - preserve_strokes=preserve_strokes + import_cameras=True, + preserve_strokes=True ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py new file mode 100644 index 0000000000..4d3e64f0b6 --- /dev/null +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -0,0 +1,102 @@ +from ayon_server.settings import BaseSettingsModel, SettingsField + + +def normal_map_format_enum(): + return [ + {"label": "DirectX", "value": "DirectX"}, + {"label": "OpenGL", "value": "OpenGL"}, + ] + + +def tangent_space_enum(): + return [ + {"label": "PerFragment", "value": "PerFragment"}, + {"label": "PerVertex", "value": "PerVertex"}, + ] + + +def uv_workflow_enum(): + return [ + {"label": "Default", "value": "default"}, + {"label": "UV Tile", "value": "uvTile"}, + {"label": "Texture Set Per UV Tile", + "value": "textureSetPerUVTile"} + ] + + +def document_resolution_enum(): + return [ + {"label": "128", "value": 128}, + {"label": "256", "value": 256}, + {"label": "512", "value": 512}, + {"label": "1024", "value": 1024}, + {"label": "2048", "value": 2048}, + {"label": "4096", "value": 4096} + ] + + +class ProjectTemplatesModel(BaseSettingsModel): + _layout = "expanded" + name: str = SettingsField("default", title="Template Name") + default_texture_resolution: int = SettingsField( + 1024, enum_resolver=document_resolution_enum, + title="Document Resolution", + description=("Set texture resolution when " + "creating new project.") + ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") + normal_map_format: str = SettingsField( + "DirectX", enum_resolver=normal_map_format_enum, + title="Normal Map Format", + description=("Set normal map format when " + "creating new project.") + ) + project_workflow: str = SettingsField( + "default", enum_resolver=uv_workflow_enum, + title="UV Tile Settings", + description=("Set UV workflow when " + "creating new project.") + ) + tangent_space_mode: str = SettingsField( + "PerFragment", enum_resolver=tangent_space_enum, + title="Tangent Space", + description=("An option to compute tangent space " + "when creating new project.") + ) + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) + + +class ProjectTemplateSettingModel(BaseSettingsModel): + project_templates: list[ProjectTemplatesModel] = SettingsField( + default_factory=ProjectTemplatesModel, + title="Project Templates" +) + + +class LoadersModel(BaseSettingsModel): + SubstanceLoadProjectMesh: ProjectTemplateSettingModel = SettingsField( + default_factory=ProjectTemplateSettingModel, + title="Load Mesh" + ) + + +DEFAULT_LOADER_SETTINGS = { + "SubstanceLoadProjectMesh":{ + "project_templates": [{ + "name": "default", + "default_texture_resolution": 1024, + "import_cameras": True, + "normal_map_format": "DirectX", + "project_workflow": "default", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }] + } +} diff --git a/server_addon/substancepainter/server/settings/main.py b/server_addon/substancepainter/server/settings/main.py index 20cf6d77b2..93523fd650 100644 --- a/server_addon/substancepainter/server/settings/main.py +++ b/server_addon/substancepainter/server/settings/main.py @@ -1,77 +1,6 @@ from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS - - -def normal_map_format_enum(): - return [ - {"label": "DirectX", "value": "DirectX"}, - {"label": "OpenGL", "value": "OpenGL"}, - ] - - -def tangent_space_enum(): - return [ - {"label": "PerFragment", "value": "PerFragment"}, - {"label": "PerVertex", "value": "PerVertex"}, - ] - - -def uv_workflow_enum(): - return [ - {"label": "Default", "value": "default"}, - {"label": "UV Tile", "value": "uvTile"}, - {"label": "Texture Set Per UV Tile", - "value": "textureSetPerUVTile"} - ] - - -def document_resolution_enum(): - return [ - {"label": "128", "value": 128}, - {"label": "256", "value": 256}, - {"label": "512", "value": 512}, - {"label": "1024", "value": 1024}, - {"label": "2048", "value": 2048}, - {"label": "4096", "value": 4096} - ] - - -class ProjectTemplatesModel(BaseSettingsModel): - _layout = "expanded" - name: str = SettingsField(title="Template Name") - document_resolution: int = SettingsField( - 1024, enum_resolver=document_resolution_enum, - title="Document Resolution", - description=("Set texture resolution when " - "creating new project.") - ) - normal_map_format: str = SettingsField( - "DirectX", enum_resolver=normal_map_format_enum, - title="Normal Map Format", - description=("Set normal map format when " - "creating new project.") - ) - tangent_space: str = SettingsField( - "PerFragment", enum_resolver=tangent_space_enum, - title="Tangent Space", - description=("An option to compute tangent space " - "when creating new project.") - ) - uv_workflow: str = SettingsField( - "default", enum_resolver=uv_workflow_enum, - title="UV Tile Settings", - description=("Set UV workflow when " - "creating new project.") - ) - import_cameras: bool = SettingsField( - True, title="Import Cameras", - description="Import cameras from the mesh file.") - preserve_strokes: bool = SettingsField( - True, title="Preserve Strokes", - description=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)") - ) +from .load_plugins import LoadersModel, DEFAULT_LOADER_SETTINGS class ShelvesSettingsModel(BaseSettingsModel): @@ -89,14 +18,12 @@ class SubstancePainterSettings(BaseSettingsModel): default_factory=list, title="Shelves" ) - project_templates: list[ProjectTemplatesModel] = SettingsField( - default_factory=ProjectTemplatesModel, - title="Project Templates" - ) + load: LoadersModel = SettingsField( + default_factory=DEFAULT_LOADER_SETTINGS, title="Loaders") DEFAULT_SPAINTER_SETTINGS = { "imageio": DEFAULT_IMAGEIO_SETTINGS, "shelves": [], - "project_templates": [], + "load": DEFAULT_LOADER_SETTINGS, } From 9aea94bf1fcfa90f8bc539e138ee8e75b42f8380 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 22:55:29 +0800 Subject: [PATCH 280/423] add todo to remember to fix the hard code --- .../hosts/substancepainter/plugins/load/load_mesh.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 563d6eb6e1..631af88eb5 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,17 +2,15 @@ from ayon_core.pipeline import ( load, get_representation_path, ) -from ayon_core.lib import BoolDef, EnumDef +from ayon_core.lib import EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import ( - prompt_new_file_with_mesh, - parse_substance_attributes_setting -) +from ayon_core.hosts.substancepainter.api.lib import parse_substance_attributes_setting + import substance_painter.project @@ -83,8 +81,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # We want store some options for updating to keep consistent behavior # from the user's original choice. We don't store 'preserve_strokes' # as we always preserve strokes on updates. + # TODO: update the code container["options"] = { - "import_cameras": import_cameras, + "import_cameras": True, } set_container_metadata(project_mesh_object_name, container) From 878fc9cf2c3fb7a0c5639590993176add3563c41 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 23:28:05 +0800 Subject: [PATCH 281/423] update the attributes options for reloading mesh --- .../hosts/substancepainter/api/lib.py | 47 +++++++++++++++++++ .../plugins/load/load_mesh.py | 17 ++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index e344076222..f95e47f99d 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -643,6 +643,21 @@ def prompt_new_file_with_mesh(mesh_filepath): def convert_substance_object_to_python(subst_proj_option="default"): + """Function to convert substance C++ objects to python instance. + It is made to avoid any possible ValueError when C++ objects casting + as python instance. + + Args: + subst_proj_option (str, optional): Substance project option. + Defaults to "default". + + Raises: + ValueError: Raise Error when unsupported Substance + Project was detected + + Returns: + python instance: converted python instance of the C++ objects. + """ if subst_proj_option == "default": return substance_painter.project.ProjectWorkflow.Default elif subst_proj_option == "uvTile": @@ -663,6 +678,16 @@ def convert_substance_object_to_python(subst_proj_option="default"): def parse_substance_attributes_setting(template_name, project_templates): + """Function to parse the dictionary from the AYON setting to be used + as the attributes for Substance Project Creation + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for Substance Project Creation + """ attributes_data = {} for template in project_templates: if template["name"] == template_name: @@ -676,3 +701,25 @@ def parse_substance_attributes_setting(template_name, project_templates): attributes_data.pop("name") attributes_data.pop("preserve_strokes") return attributes_data + + +def parse_subst_attrs_reloading_mesh(template_name, project_templates): + """Function to parse the substances attributes ('import_cameras' + and 'preserve_strokes') for reloading mesh + with the existing projects. + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for reloading mesh with the + existing project + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + for key, value in template.items(): + if isinstance(value, bool): + attributes_data.update({key: value}) + return attributes_data diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 631af88eb5..49f11251c9 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -9,7 +9,10 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import parse_substance_attributes_setting +from ayon_core.hosts.substancepainter.api.lib import ( + parse_substance_attributes_setting, + parse_subst_attrs_reloading_mesh +) import substance_painter.project @@ -41,7 +44,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs template_name = options.get("project_template", "default") - template_settings = parse_substance_attributes_setting(template_name, self.project_templates) + template_settings = parse_substance_attributes_setting( + template_name, self.project_templates) sp_settings = substance_painter.project.Settings(**template_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project @@ -52,11 +56,10 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh + mesh_settings = parse_subst_attrs_reloading_mesh( + template_name, self.project_templates) # TODO: fix the hardcoded when the preset setting in SP addon. - settings = substance_painter.project.MeshReloadingSettings( - import_cameras=True, - preserve_strokes=True - ) + settings = substance_painter.project.MeshReloadingSettings(**mesh_settings) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -83,7 +86,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": True, + "import_cameras": template_settings["import_cameras"], } set_container_metadata(project_mesh_object_name, container) From 95a69a1d8d994c5345c78d1354e32a333983eef2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 13:31:13 +0800 Subject: [PATCH 282/423] make sure deleting old version should remove the 'right' folder --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index f432829860..62302e7123 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -238,9 +238,12 @@ class DeleteOldVersions(load.ProductLoaderPlugin): versions_by_parent[ent["productId"]].append(ent) # Filter already deleted versions + versions_to_pop = [] for version in versions: if "deleted" in version["tags"]: - continue + versions_to_pop.append(version) + + for version in versions_to_pop: msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], product_entity["name"], 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 283/423] 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 284/423] 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 285/423] 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 286/423] 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 287/423] 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 d4fdf8530605ca4f03957415f48d99eef292d11b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 22:45:47 +0800 Subject: [PATCH 288/423] Add Qt dialog to support users to choose their templates for project creation --- .../hosts/substancepainter/api/lib.py | 83 ----------- .../plugins/load/load_mesh.py | 136 +++++++++++++++--- .../server/settings/load_plugins.py | 36 +++-- 3 files changed, 145 insertions(+), 110 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index f95e47f99d..64c39943ce 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -640,86 +640,3 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh - - -def convert_substance_object_to_python(subst_proj_option="default"): - """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects casting - as python instance. - - Args: - subst_proj_option (str, optional): Substance project option. - Defaults to "default". - - Raises: - ValueError: Raise Error when unsupported Substance - Project was detected - - Returns: - python instance: converted python instance of the C++ objects. - """ - if subst_proj_option == "default": - return substance_painter.project.ProjectWorkflow.Default - elif subst_proj_option == "uvTile": - return substance_painter.project.ProjectWorkflow.UVTile - elif subst_proj_option == "textureSetPerUVTile": - return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile - elif subst_proj_option == "PerFragment": - return substance_painter.project.TangentSpace.PerFragment - elif subst_proj_option == "PerVertex": - return substance_painter.project.TangentSpace.PerVertex - elif subst_proj_option == "DirectX": - return substance_painter.project.NormalMapFormat.DirectX - elif subst_proj_option == "OpenGL": - return substance_painter.project.NormalMapFormat.OpenGL - else: - raise ValueError( - f"Unsupported Substance Objects: {subst_proj_option}") - - -def parse_substance_attributes_setting(template_name, project_templates): - """Function to parse the dictionary from the AYON setting to be used - as the attributes for Substance Project Creation - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for Substance Project Creation - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - attributes_data.update(template) - attributes_data["normal_map_format"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["normal_map_format"]) - attributes_data["project_workflow"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["project_workflow"]) - attributes_data["tangent_space_mode"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["tangent_space_mode"]) - attributes_data.pop("name") - attributes_data.pop("preserve_strokes") - return attributes_data - - -def parse_subst_attrs_reloading_mesh(template_name, project_templates): - """Function to parse the substances attributes ('import_cameras' - and 'preserve_strokes') for reloading mesh - with the existing projects. - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for reloading mesh with the - existing project - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - for key, value in template.items(): - if isinstance(value, bool): - attributes_data.update({key: value}) - return attributes_data diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 49f11251c9..b377cf9a1d 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -1,23 +1,130 @@ +from qtpy import QtWidgets, QtCore from ayon_core.pipeline import ( load, get_representation_path, ) -from ayon_core.lib import EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import ( - parse_substance_attributes_setting, - parse_subst_attrs_reloading_mesh -) - import substance_painter.project +def _convert(subst_attr): + """Function to convert substance C++ objects to python instance. + It is made to avoid any possible ValueError when C++ objects casting + as python instance. + + Args: + subst_attr (str): Substance attributes + + Raises: + ValueError: Raise Error when unsupported Substance + Project was detected + + Returns: + python instance: converted python instance of the C++ objects. + """ + if subst_attr in {"Default", "UVTile", "TextureSetPerUVTile"}: + return getattr(substance_painter.project.ProjectWorkflow, subst_attr) + elif subst_attr in {"PerFragment", "PerVertex"}: + return getattr(substance_painter.project.TangentSpace, subst_attr) + elif subst_attr in {"DirectX", "OpenGL"}: + return getattr(substance_painter.project.NormalMapFormat, subst_attr) + else: + raise ValueError( + f"Unsupported Substance Objects: {subst_attr}") + + +def parse_substance_attributes_setting(template_name, project_templates): + """Function to parse the dictionary from the AYON setting to be used + as the attributes for Substance Project Creation + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for Substance Project Creation + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + attributes_data.update(template) + attributes_data["normal_map_format"] = _convert( + attributes_data["normal_map_format"]) + attributes_data["project_workflow"] = _convert( + attributes_data["project_workflow"]) + attributes_data["tangent_space_mode"] = _convert( + attributes_data["tangent_space_mode"]) + attributes_data.pop("name") + attributes_data.pop("preserve_strokes") + return attributes_data + + +def parse_subst_attrs_reloading_mesh(template_name, project_templates): + """Function to parse the substances attributes ('import_cameras' + and 'preserve_strokes') for reloading mesh + with the existing projects. + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for reloading mesh with the + existing project + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + for key, value in template.items(): + if isinstance(value, bool): + attributes_data.update({key: value}) + return attributes_data + + +class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): + """The pop-up dialog allows users to choose material + duplicate options for importing Max objects when updating + or switching assets. + """ + def __init__(self, project_templates): + super(SubstanceProjectConfigurationWindow, self).__init__() + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + + self.template_name = None + self.project_templates = project_templates + + self.widgets = { + "label": QtWidgets.QLabel("Project Configuration"), + "template_options": QtWidgets.QComboBox(), + "buttons": QtWidgets.QWidget(), + "okButton": QtWidgets.QPushButton("Ok"), + } + for template in project_templates: + self.widgets["template_options"].addItem(template) + # Build buttons. + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout.addWidget(self.widgets["template_options"]) + layout.addWidget(self.widgets["okButton"]) + # Build layout. + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.widgets["label"]) + layout.addWidget(self.widgets["buttons"]) + + self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + + def on_ok_pressed(self): + self.template_name = ( + self.widgets["template_options"].currentText() + ) + self.close() + + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -30,20 +137,13 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): color = "orange" project_templates = [] - @classmethod - def get_options(cls, contexts): - template_enum = [template["name"] for template in cls.project_templates] - return [ - EnumDef("project_template", - items=template_enum, - default="default", - label="Project Template") - ] - def load(self, context, name, namespace, options=None): - # Get user inputs - template_name = options.get("project_template", "default") + template_enum = [template["name"] for template in self.project_templates] + window = SubstanceProjectConfigurationWindow(template_enum) + window.exec_() + template_name = window.template_name + template_settings = parse_substance_attributes_setting( template_name, self.project_templates) sp_settings = substance_painter.project.Settings(**template_settings) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index 4d3e64f0b6..294ecfcef6 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -10,17 +10,17 @@ def normal_map_format_enum(): def tangent_space_enum(): return [ - {"label": "PerFragment", "value": "PerFragment"}, - {"label": "PerVertex", "value": "PerVertex"}, + {"label": "Per Fragment", "value": "PerFragment"}, + {"label": "Per Vertex", "value": "PerVertex"}, ] def uv_workflow_enum(): return [ - {"label": "Default", "value": "default"}, - {"label": "UV Tile", "value": "uvTile"}, + {"label": "Default", "value": "Default"}, + {"label": "UV Tile", "value": "UVTile"}, {"label": "Texture Set Per UV Tile", - "value": "textureSetPerUVTile"} + "value": "TextureSetPerUVTile"} ] @@ -54,7 +54,7 @@ class ProjectTemplatesModel(BaseSettingsModel): "creating new project.") ) project_workflow: str = SettingsField( - "default", enum_resolver=uv_workflow_enum, + "Default", enum_resolver=uv_workflow_enum, title="UV Tile Settings", description=("Set UV workflow when " "creating new project.") @@ -90,11 +90,29 @@ class LoadersModel(BaseSettingsModel): DEFAULT_LOADER_SETTINGS = { "SubstanceLoadProjectMesh":{ "project_templates": [{ - "name": "default", - "default_texture_resolution": 1024, + "name": "2K(Default)", + "default_texture_resolution": 2048, "import_cameras": True, "normal_map_format": "DirectX", - "project_workflow": "default", + "project_workflow": "Default", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }, + { + "name": "2K(UV tile)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "DirectX", + "project_workflow": "UVTile", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }, + { + "name": "4K(Custom)", + "default_texture_resolution": 4096, + "import_cameras": True, + "normal_map_format": "OpenGL", + "project_workflow": "UVTile", "tangent_space_mode": "PerFragment", "preserve_strokes": True }] From 81bdaf9915b51e1b6f41cb95cbab703def72c19e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 00:06:33 +0800 Subject: [PATCH 289/423] optimize the codes and the settings for loading mesh more smoothly --- .../plugins/load/load_mesh.py | 95 ++++++------------- .../server/settings/load_plugins.py | 14 +-- 2 files changed, 35 insertions(+), 74 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index b377cf9a1d..8f23600216 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -13,10 +13,10 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( import substance_painter.project -def _convert(subst_attr): +def _convert(substance_attr): """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects casting - as python instance. + It is made to avoid any possible ValueError when C++ objects is + converted to the Substance Painter Python API equivalent objects. Args: subst_attr (str): Substance attributes @@ -28,63 +28,21 @@ def _convert(subst_attr): Returns: python instance: converted python instance of the C++ objects. """ - if subst_attr in {"Default", "UVTile", "TextureSetPerUVTile"}: - return getattr(substance_painter.project.ProjectWorkflow, subst_attr) - elif subst_attr in {"PerFragment", "PerVertex"}: - return getattr(substance_painter.project.TangentSpace, subst_attr) - elif subst_attr in {"DirectX", "OpenGL"}: - return getattr(substance_painter.project.NormalMapFormat, subst_attr) - else: - raise ValueError( - f"Unsupported Substance Objects: {subst_attr}") + root = substance_painter.project + for attr in substance_attr.split("."): + root = getattr(root, attr, None) + if root is None: + raise ValueError( + f"Substance Painter project attribute does not exist: {substance_attr}") + + return root -def parse_substance_attributes_setting(template_name, project_templates): - """Function to parse the dictionary from the AYON setting to be used - as the attributes for Substance Project Creation - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for Substance Project Creation - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - attributes_data.update(template) - attributes_data["normal_map_format"] = _convert( - attributes_data["normal_map_format"]) - attributes_data["project_workflow"] = _convert( - attributes_data["project_workflow"]) - attributes_data["tangent_space_mode"] = _convert( - attributes_data["tangent_space_mode"]) - attributes_data.pop("name") - attributes_data.pop("preserve_strokes") - return attributes_data - - -def parse_subst_attrs_reloading_mesh(template_name, project_templates): - """Function to parse the substances attributes ('import_cameras' - and 'preserve_strokes') for reloading mesh - with the existing projects. - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for reloading mesh with the - existing project - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - for key, value in template.items(): - if isinstance(value, bool): - attributes_data.update({key: value}) - return attributes_data +def get_template_by_name(name: str, templates: list[dict]) -> dict: + return next( + template for template in templates + if template["name"] == name + ) class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): @@ -139,14 +97,18 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs + print(self.project_templates) template_enum = [template["name"] for template in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name - - template_settings = parse_substance_attributes_setting( - template_name, self.project_templates) - sp_settings = substance_painter.project.Settings(**template_settings) + template = get_template_by_name(template_name, self.project_templates) + sp_settings = substance_painter.project.Settings( + normal_map_format=_convert(template["normal_map_format"]), + project_workflow=_convert(template["project_workflow"]), + tangent_space_mode=_convert(template["tangent_space_mode"]), + default_texture_resolution=template["default_texture_resolution"] + ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) @@ -156,10 +118,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - mesh_settings = parse_subst_attrs_reloading_mesh( - template_name, self.project_templates) - # TODO: fix the hardcoded when the preset setting in SP addon. - settings = substance_painter.project.MeshReloadingSettings(**mesh_settings) + settings = substance_painter.project.MeshReloadingSettings( + import_cameras=template["import_cameras"], + preserve_strokes=template["preserve_strokes"]) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -186,7 +147,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": template_settings["import_cameras"], + "import_cameras": template["import_cameras"], } set_container_metadata(project_mesh_object_name, container) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index 294ecfcef6..b404ad4316 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -3,24 +3,24 @@ from ayon_server.settings import BaseSettingsModel, SettingsField def normal_map_format_enum(): return [ - {"label": "DirectX", "value": "DirectX"}, - {"label": "OpenGL", "value": "OpenGL"}, + {"label": "DirectX", "value": "NormalMapFormat.DirectX"}, + {"label": "OpenGL", "value": "NormalMapFormat.OpenGL"}, ] def tangent_space_enum(): return [ - {"label": "Per Fragment", "value": "PerFragment"}, - {"label": "Per Vertex", "value": "PerVertex"}, + {"label": "Per Fragment", "value": "TangentSpace.PerFragment"}, + {"label": "Per Vertex", "value": "TangentSpace.PerVertex"}, ] def uv_workflow_enum(): return [ - {"label": "Default", "value": "Default"}, - {"label": "UV Tile", "value": "UVTile"}, + {"label": "Default", "value": "ProjectWorkflow.Default"}, + {"label": "UV Tile", "value": "ProjectWorkflow.UVTile"}, {"label": "Texture Set Per UV Tile", - "value": "TextureSetPerUVTile"} + "value": "ProjectWorkflow.TextureSetPerUVTile"} ] From 5b7511ed84ac348f3a0065b8e77fb2be9e44cdc9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 23:28:54 +0200 Subject: [PATCH 290/423] 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 291/423] 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 292/423] 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 6873f02e6b08fec57cec6cd65a3ac840759e3ed2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:06:51 +0200 Subject: [PATCH 293/423] Fix typos --- .../workfile/workfile_template_builder.py | 22 +++++++++---------- .../tools/workfile_template_build/lib.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..012251cd91 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -329,7 +329,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_name' @@ -375,7 +375,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -395,7 +395,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -466,7 +466,7 @@ class AbstractTemplateBuilder(object): return list(sorted( placeholders, - key=lambda i: i.order + key=lambda placeholder: placeholder.order )) def build_template( @@ -685,7 +685,7 @@ class AbstractTemplateBuilder(object): for placeholder in placeholders } all_processed = len(placeholders) == 0 - # Counter is checked at the ned of a loop so the loop happens at least + # Counter is checked at the end of a loop so the loop happens at least # once. iter_counter = 0 while not all_processed: @@ -1045,7 +1045,7 @@ class PlaceholderPlugin(object): Using shared data from builder but stored under plugin identifier. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -1085,7 +1085,7 @@ class PlaceholderPlugin(object): Using shared data from builder but stored under plugin identifier. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -1107,10 +1107,10 @@ class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. Items are always created and updated by their plugins. Each plugin can use - modified class of 'PlacehoderItem' but only to add more options instead of + modified class of 'PlaceholderItem' but only to add more options instead of new other. - Scene identifier is used to avoid processing of the palceholder item + Scene identifier is used to avoid processing of the placeholder item multiple times so must be unique across whole workfile builder. Args: @@ -1162,7 +1162,7 @@ class PlaceholderItem(object): """Placeholder data which can modify how placeholder is processed. Possible general keys - - order: Can define the order in which is palceholder processed. + - order: Can define the order in which is placeholder processed. Lower == earlier. Other keys are defined by placeholder and should validate them on item @@ -1264,7 +1264,7 @@ class PlaceholderLoadMixin(object): """Unified attribute definitions for load placeholder. Common function for placeholder plugins used for loading of - repsentations. Use it in 'get_placeholder_options'. + representations. Use it in 'get_placeholder_options'. Args: plugin (PlaceholderPlugin): Plugin used for loading of diff --git a/client/ayon_core/tools/workfile_template_build/lib.py b/client/ayon_core/tools/workfile_template_build/lib.py index de3a0d0084..ffd6fefc38 100644 --- a/client/ayon_core/tools/workfile_template_build/lib.py +++ b/client/ayon_core/tools/workfile_template_build/lib.py @@ -8,12 +8,12 @@ from ayon_core.tools.utils.dialogs import show_message_dialog def open_template_ui(builder, main_window): """Open template from `builder` - Asks user about overwriting current scene and feedsback exceptions. + Asks user about overwriting current scene and feedback exceptions. """ result = QtWidgets.QMessageBox.question( main_window, "Opening template", - "Caution! You will loose unsaved changes.\nDo you want to continue?", + "Caution! You will lose unsaved changes.\nDo you want to continue?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No ) if result == QtWidgets.QMessageBox.Yes: From a59c54d1aa8807659cac331af9a0ee9a2019a6c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:09:29 +0200 Subject: [PATCH 294/423] Remove non-existing arguments from docstring --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 ---- 1 file changed, 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 012251cd91..1c094e1d52 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1267,8 +1267,6 @@ class PlaceholderLoadMixin(object): representations. Use it in 'get_placeholder_options'. Args: - plugin (PlaceholderPlugin): Plugin used for loading of - representations. options (Dict[str, Any]): Already available options which are used as defaults for attributes. @@ -1695,8 +1693,6 @@ class PlaceholderCreateMixin(object): publishable instances. Use it with 'get_placeholder_options'. Args: - plugin (PlaceholderPlugin): Plugin used for creating of - publish instances. options (Dict[str, Any]): Already available options which are used as defaults for attributes. From 2b6889a6ea55dd8a5916f5584b3e39ced8c4d913 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:12:42 +0200 Subject: [PATCH 295/423] Also delete placeholder if Keep Placeholder is not enabled and no representations were loaded --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 ++ 1 file changed, 2 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..56a7a5f33a 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1599,6 +1599,8 @@ class PlaceholderLoadMixin(object): self.log.info(( "There's no representation for this placeholder: {}" ).format(placeholder.scene_identifier)) + if not placeholder.data.get("keep_placeholder", True): + self.delete_placeholder(placeholder) return repre_load_contexts = get_representation_contexts( From 425f7cbd89c3ac695b39373b9283df3b83de410b Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:13:55 +0800 Subject: [PATCH 296/423] Update server_addon/substancepainter/server/settings/load_plugins.py Co-authored-by: Roy Nieterau --- .../server/settings/load_plugins.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index b404ad4316..ed9b6f0d64 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -93,27 +93,27 @@ DEFAULT_LOADER_SETTINGS = { "name": "2K(Default)", "default_texture_resolution": 2048, "import_cameras": True, - "normal_map_format": "DirectX", - "project_workflow": "Default", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.Default", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, "import_cameras": True, - "normal_map_format": "DirectX", - "project_workflow": "UVTile", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }, { "name": "4K(Custom)", "default_texture_resolution": 4096, "import_cameras": True, - "normal_map_format": "OpenGL", - "project_workflow": "UVTile", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.OpenGL", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }] } From 53627d34d3762ca2ec2396f315ff7515b93a1993 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:14:15 +0800 Subject: [PATCH 297/423] Update client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py Co-authored-by: Roy Nieterau --- .../plugins/load/load_mesh.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 8f23600216..7af0d71a1d 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -14,19 +14,22 @@ import substance_painter.project def _convert(substance_attr): - """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects is - converted to the Substance Painter Python API equivalent objects. + """Return Substance Painter Python API Project attribute from string. + + This converts a string like "ProjectWorkflow.Default" to for example + the Substance Painter Python API equivalent object, like: + `substance_painter.project.ProjectWorkflow.Default` Args: - subst_attr (str): Substance attributes - - Raises: - ValueError: Raise Error when unsupported Substance - Project was detected + substance_attr (str): The `substance_painter.project` attribute, + for example "ProjectWorkflow.Default" Returns: - python instance: converted python instance of the C++ objects. + Any: Substance Python API object of the project attribute. + + Raises: + ValueError: If attribute does not exist on the + `substance_painter.project` python api. """ root = substance_painter.project for attr in substance_attr.split("."): From ca442c52cd18b0594a80af53c129ca5c567e440b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 14:00:14 +0800 Subject: [PATCH 298/423] remove print function --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 7af0d71a1d..16a525b279 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -99,8 +99,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): project_templates = [] def load(self, context, name, namespace, options=None): + # Get user inputs - print(self.project_templates) template_enum = [template["name"] for template in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() From 5bb61efab28c09fdaa51a2965e019e58e07c20e6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 14:50:56 +0800 Subject: [PATCH 299/423] cosmetic fix --- .../plugins/load/load_mesh.py | 6 +- .../server/settings/load_plugins.py | 62 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 16a525b279..b3f0109942 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -36,7 +36,8 @@ def _convert(substance_attr): root = getattr(root, attr, None) if root is None: raise ValueError( - f"Substance Painter project attribute does not exist: {substance_attr}") + "Substance Painter project attribute" + f" does not exist: {substance_attr}") return root @@ -101,7 +102,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - template_enum = [template["name"] for template in self.project_templates] + template_enum = [template["name"] for template + in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index ed9b6f0d64..e6b2fd86c3 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -74,10 +74,10 @@ class ProjectTemplatesModel(BaseSettingsModel): class ProjectTemplateSettingModel(BaseSettingsModel): - project_templates: list[ProjectTemplatesModel] = SettingsField( + project_templates: list[ProjectTemplatesModel] = SettingsField( default_factory=ProjectTemplatesModel, title="Project Templates" -) + ) class LoadersModel(BaseSettingsModel): @@ -88,33 +88,35 @@ class LoadersModel(BaseSettingsModel): DEFAULT_LOADER_SETTINGS = { - "SubstanceLoadProjectMesh":{ - "project_templates": [{ - "name": "2K(Default)", - "default_texture_resolution": 2048, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.DirectX", - "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }, - { - "name": "2K(UV tile)", - "default_texture_resolution": 2048, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.DirectX", - "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }, - { - "name": "4K(Custom)", - "default_texture_resolution": 4096, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.OpenGL", - "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }] + "SubstanceLoadProjectMesh": { + "project_templates": [ + { + "name": "2K(Default)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.Default", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + }, + { + "name": "2K(UV tile)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + }, + { + "name": "4K(Custom)", + "default_texture_resolution": 4096, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.OpenGL", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + } + ] } } From bf0d3477d9b7ac5f7b24392404efe5dd0222721c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:24:14 +0200 Subject: [PATCH 300/423] fix expading of values Co-authored-by: Roy Nieterau --- 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 c3b1267466..ed7a2b8475 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1582,7 +1582,7 @@ class PlaceholderLoadMixin(object): self.project_name, placeholder_representations ) filtered_repre_contexts = self._reduce_last_version_repre_entities( - repre_load_contexts + repre_load_contexts.values() ) if not filtered_repre_contexts: self.log.info(( From 646c0df474e100b307d33c1c896cd46b6c610580 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:25:22 +0200 Subject: [PATCH 301/423] fix version entity manipulation --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index ed7a2b8475..7b8d29ee37 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1539,11 +1539,11 @@ class PlaceholderLoadMixin(object): version_mapping = version_mapping_by_product_id.setdefault( product_id, {} ) - version_mapping[version].append(repre_context) + version_mapping.setdefault(version, []).append(repre_context) output = [] for version_mapping in version_mapping_by_product_id.values(): - last_version = tuple(sorted(version_mapping.keys()))[-1] + last_version = max(version_mapping.keys()) output.extend(version_mapping[last_version]) return output From 3d496be7c65a28fc1567cc4e62e6caadc6d45a4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:26:20 +0200 Subject: [PATCH 302/423] fix product type key access --- .../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 7b8d29ee37..013a8fb37f 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1468,7 +1468,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.get("family") builder_type = placeholder.data["builder_type"] folder_ids = [] From d5a3296d4c09086d5805c3d62e617f48c1fd2b1a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:48:12 +0200 Subject: [PATCH 303/423] Crash if product type is not available. --- 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 013a8fb37f..525a7396e7 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1470,7 +1470,7 @@ class PlaceholderLoadMixin(object): product_name_regex = re.compile(product_name_regex_value) product_type = placeholder.data.get("product_type") if product_type is None: - product_type = placeholder.data.get("family") + product_type = placeholder.data["family"] builder_type = placeholder.data["builder_type"] folder_ids = [] From edee279f15fa16b549db68e3adb6aba14fc2f541 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 12:04:24 +0200 Subject: [PATCH 304/423] 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 305/423] 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 306/423] 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 f7c8a23d98729dd462bf60026682bfa3f2e09ae6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 21:23:23 +0800 Subject: [PATCH 307/423] add import cameras and perserve strokes into the project configuration dialog --- .../plugins/load/load_mesh.py | 29 +++++++++++++++---- .../server/settings/load_plugins.py | 21 ++------------ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index b3f0109942..2560bd96ae 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -58,29 +58,44 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): super(SubstanceProjectConfigurationWindow, self).__init__() self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + self.import_cameras = False + self.preserve_strokes = False self.template_name = None self.project_templates = project_templates self.widgets = { "label": QtWidgets.QLabel("Project Configuration"), "template_options": QtWidgets.QComboBox(), - "buttons": QtWidgets.QWidget(), + "import_cameras": QtWidgets.QCheckBox("Improve Cameras"), + "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), + "clickbox": QtWidgets.QWidget(), + "combobox": QtWidgets.QWidget(), "okButton": QtWidgets.QPushButton("Ok"), } for template in project_templates: self.widgets["template_options"].addItem(template) + + # Build clickboxes + layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) + layout.addWidget(self.widgets["import_cameras"]) + layout.addWidget(self.widgets["preserve_strokes"]) # Build buttons. - layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) layout.addWidget(self.widgets["okButton"]) # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["buttons"]) + layout.addWidget(self.widgets["clickbox"]) + layout.addWidget(self.widgets["combobox"]) self.widgets["okButton"].pressed.connect(self.on_ok_pressed) def on_ok_pressed(self): + if self.widgets["import_cameras"].isChecked(): + self.import_cameras = True + if self.widgets["preserve_strokes"].isChecked(): + self.preserve_strokes = True self.template_name = ( self.widgets["template_options"].currentText() ) @@ -107,6 +122,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name + import_cameras = window.import_cameras + preserve_strokes = window.preserve_strokes template = get_template_by_name(template_name, self.project_templates) sp_settings = substance_painter.project.Settings( normal_map_format=_convert(template["normal_map_format"]), @@ -124,8 +141,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): else: # Reload the mesh settings = substance_painter.project.MeshReloadingSettings( - import_cameras=template["import_cameras"], - preserve_strokes=template["preserve_strokes"]) + import_cameras=import_cameras, + preserve_strokes=preserve_strokes) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -152,7 +169,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": template["import_cameras"], + "import_cameras": import_cameras, } set_container_metadata(project_mesh_object_name, container) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index e6b2fd86c3..e5519c9773 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -44,9 +44,6 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("Set texture resolution when " "creating new project.") ) - import_cameras: bool = SettingsField( - True, title="Import Cameras", - description="Import cameras from the mesh file.") normal_map_format: str = SettingsField( "DirectX", enum_resolver=normal_map_format_enum, title="Normal Map Format", @@ -65,12 +62,6 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("An option to compute tangent space " "when creating new project.") ) - preserve_strokes: bool = SettingsField( - True, title="Preserve Strokes", - description=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)") - ) class ProjectTemplateSettingModel(BaseSettingsModel): @@ -93,29 +84,23 @@ DEFAULT_LOADER_SETTINGS = { { "name": "2K(Default)", "default_texture_resolution": 2048, - "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, - "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" }, { "name": "4K(Custom)", "default_texture_resolution": 4096, - "import_cameras": True, "normal_map_format": "NormalMapFormat.OpenGL", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" } ] } From 5875016e5f3b9094de3c24a80fd7c8f650e059f6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 22:06:38 +0800 Subject: [PATCH 308/423] import cameras instead of improve camertas --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 2560bd96ae..8536914095 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -66,7 +66,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets = { "label": QtWidgets.QLabel("Project Configuration"), "template_options": QtWidgets.QComboBox(), - "import_cameras": QtWidgets.QCheckBox("Improve Cameras"), + "import_cameras": QtWidgets.QCheckBox("Import Cameras"), "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), From 276b293577ad9c8396584d4dab63f55b8d21f05d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 17:09:02 +0200 Subject: [PATCH 309/423] Fix `apply_settings` signature --- .../hosts/maya/plugins/publish/collect_file_dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py b/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py index 93b46c511b..60853bd1ee 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py @@ -12,7 +12,7 @@ class CollectFileDependencies(pyblish.api.ContextPlugin): families = ["renderlayer"] @classmethod - def apply_settings(cls, project_settings, system_settings): + def apply_settings(cls, project_settings): # Disable plug-in if not used for deadline submission anyway settings = project_settings["deadline"]["publish"]["MayaSubmitDeadline"] # noqa cls.enabled = settings.get("asset_dependencies", True) From fe77e0a578aa6286298b4966c25d1a8b7729b33a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 17:22:44 +0200 Subject: [PATCH 310/423] Fix refactor of imports to `ayon_core` --- client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index d1fea33597..7f076a8273 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -5,8 +5,8 @@ import maya.cmds as cmds from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api import lib -from openpype.pipeline import registered_host -from openpype.pipeline.create import CreateContext +from ayon_core.pipeline import registered_host +from ayon_core.pipeline.create import CreateContext class YetiRigLoader(plugin.ReferenceLoader): From d5b0f2274837f9cec8c9d74433e3cc57bf513d1f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:07:54 +0800 Subject: [PATCH 311/423] redesign the dialog with ok & cancel button and links the boolean options to the AYON settings --- .../plugins/load/load_mesh.py | 65 ++++++++++++++++--- .../server/settings/load_plugins.py | 21 +++++- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 8536914095..42a3e5b5b2 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -61,35 +61,70 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.import_cameras = False self.preserve_strokes = False self.template_name = None + self.template_names = [template["name"] for template + in project_templates] self.project_templates = project_templates self.widgets = { - "label": QtWidgets.QLabel("Project Configuration"), + "label": QtWidgets.QLabel( + "Select your template for project configuration"), "template_options": QtWidgets.QComboBox(), "import_cameras": QtWidgets.QCheckBox("Import Cameras"), "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), + "buttons": QtWidgets.QWidget(), "okButton": QtWidgets.QPushButton("Ok"), + "cancelButton": QtWidgets.QPushButton("Cancel") } - for template in project_templates: + for template in self.template_names: self.widgets["template_options"].addItem(template) + template_name = self.widgets["template_options"].currentText() + + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == template_name) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == template_name) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) layout.addWidget(self.widgets["preserve_strokes"]) - # Build buttons. + # Build combobox layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) + + # Build buttons + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) layout.addWidget(self.widgets["okButton"]) + layout.addWidget(self.widgets["cancelButton"]) + # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["combobox"]) + layout.addWidget(self.widgets["buttons"]) + self.widgets["template_options"].currentTextChanged.connect( + self.on_options_changed) self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) + + def on_options_changed(self, value): + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == value) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == value) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) def on_ok_pressed(self): if self.widgets["import_cameras"].isChecked(): @@ -101,6 +136,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): ) self.close() + def on_cancel_pressed(self): + self.template_name = None + self.close() + + @classmethod + def prompt(cls, templates): + dialog = cls(templates) + dialog.exec_() + return dialog + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -117,13 +162,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - template_enum = [template["name"] for template - in self.project_templates] - window = SubstanceProjectConfigurationWindow(template_enum) - window.exec_() - template_name = window.template_name - import_cameras = window.import_cameras - preserve_strokes = window.preserve_strokes + result = SubstanceProjectConfigurationWindow.prompt( + self.project_templates) + template_name = result.template_name + if template_name is None: + return + import_cameras = result.import_cameras template = get_template_by_name(template_name, self.project_templates) sp_settings = substance_painter.project.Settings( normal_map_format=_convert(template["normal_map_format"]), @@ -140,6 +184,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh + preserve_strokes = result.preserve_strokes settings = substance_painter.project.MeshReloadingSettings( import_cameras=import_cameras, preserve_strokes=preserve_strokes) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index e5519c9773..e6b2fd86c3 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -44,6 +44,9 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("Set texture resolution when " "creating new project.") ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") normal_map_format: str = SettingsField( "DirectX", enum_resolver=normal_map_format_enum, title="Normal Map Format", @@ -62,6 +65,12 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("An option to compute tangent space " "when creating new project.") ) + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) class ProjectTemplateSettingModel(BaseSettingsModel): @@ -84,23 +93,29 @@ DEFAULT_LOADER_SETTINGS = { { "name": "2K(Default)", "default_texture_resolution": 2048, + "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, + "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True }, { "name": "4K(Custom)", "default_texture_resolution": 4096, + "import_cameras": True, "normal_map_format": "NormalMapFormat.OpenGL", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True } ] } From e362b11184923a0404fb315771be4a7a768d3b7a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:15:22 +0800 Subject: [PATCH 312/423] refactor the repetitive code into a function --- .../plugins/load/load_mesh.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 42a3e5b5b2..f6abfabaf9 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -81,16 +81,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["template_options"].addItem(template) template_name = self.widgets["template_options"].currentText() - - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == template_name) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == template_name) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) - + self.get_boolean_setting(template_name) # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) @@ -117,14 +108,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) def on_options_changed(self, value): - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == value) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == value) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + self.get_boolean_setting(value) def on_ok_pressed(self): if self.widgets["import_cameras"].isChecked(): @@ -140,6 +124,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.template_name = None self.close() + def get_boolean_setting(self, template_name): + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == template_name) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == template_name) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + @classmethod def prompt(cls, templates): dialog = cls(templates) From e4b5da7850f9036c74308b7cae227ef8798a06eb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:26:41 +0800 Subject: [PATCH 313/423] move combobox before the checkboxes in the popup dialog --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index f6abfabaf9..2ba5b10034 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -98,8 +98,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["combobox"]) + layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["buttons"]) self.widgets["template_options"].currentTextChanged.connect( From c11c8a3cad019428e8f2201025b18608da177988 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 10:51:37 +0200 Subject: [PATCH 314/423] Implement `create_cache_instance_on_load` setting for Yeti Rig loader --- .../hosts/maya/plugins/load/load_yeti_rig.py | 10 +++++++--- server_addon/maya/server/settings/loaders.py | 20 ++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 7f076a8273..7444566ee1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -20,6 +20,9 @@ class YetiRigLoader(plugin.ReferenceLoader): icon = "code-fork" color = "orange" + # From settings + create_cache_instance_on_load = True + def process_reference( self, context, name=None, namespace=None, options=None ): @@ -54,9 +57,10 @@ class YetiRigLoader(plugin.ReferenceLoader): ) self[:] = nodes - # Automatically create in instance to allow publishing the loaded - # yeti rig into a yeti cache - self._create_yeti_cache_instance(nodes, variant=namespace) + if self.create_cache_instance_on_load: + # Automatically create in instance to allow publishing the loaded + # yeti rig into a yeti cache + self._create_yeti_cache_instance(nodes, variant=namespace) return nodes diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index f59711b1e6..2f104d2858 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -103,6 +103,17 @@ class ImportLoaderModel(BaseSettingsModel): group_name: str = SettingsField(title="Group name") +class YetiRigLoaderModel(LoaderEnabledModel): + create_cache_instance_on_load: bool = SettingsField( + title="Create Yeti Cache instance on load", + description=( + "When enabled, upon loading a Yeti Rig product a new Yeti cache " + "instance is automatically created as preparation to publishing " + "the output directly." + ) + ) + + class LoadersModel(BaseSettingsModel): colors: ColorsSetting = SettingsField( default_factory=ColorsSetting, @@ -195,8 +206,8 @@ class LoadersModel(BaseSettingsModel): default_factory=LoaderEnabledModel, title="Yeti Cache Loader" ) - YetiRigLoader: LoaderEnabledModel = SettingsField( - default_factory=LoaderEnabledModel, + YetiRigLoader: YetiRigLoaderModel = SettingsField( + default_factory=YetiRigLoaderModel, title="Yeti Rig Loader" ) @@ -266,5 +277,8 @@ DEFAULT_LOADERS_SETTING = { "VRaySceneLoader": {"enabled": True}, "XgenLoader": {"enabled": True}, "YetiCacheLoader": {"enabled": True}, - "YetiRigLoader": {"enabled": True}, + "YetiRigLoader": { + "enabled": True, + "create_cache_instance_on_load": True + }, } From 4d7a781517ade7df3fe052c03beab9aa0f3d8400 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 10:51:57 +0200 Subject: [PATCH 315/423] Bump maya server addon version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 289d21250d3ef63a2dc2e39466b07af1f10ccef3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:24:42 +0800 Subject: [PATCH 316/423] updating the dialog functions --- .../plugins/load/load_mesh.py | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 2ba5b10034..601e723f1f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -73,9 +73,9 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), - "buttons": QtWidgets.QWidget(), - "okButton": QtWidgets.QPushButton("Ok"), - "cancelButton": QtWidgets.QPushButton("Cancel") + "buttons": QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.Ok + | QtWidgets.QDialogButtonBox.Cancel) } for template in self.template_names: self.widgets["template_options"].addItem(template) @@ -92,9 +92,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build buttons layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) - layout.addWidget(self.widgets["okButton"]) - layout.addWidget(self.widgets["cancelButton"]) - # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) @@ -104,8 +101,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["template_options"].currentTextChanged.connect( self.on_options_changed) - self.widgets["okButton"].pressed.connect(self.on_ok_pressed) - self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) + self.widgets["buttons"].accepted.connect(self.on_ok_pressed) + self.widgets["buttons"].rejected.connect(self.on_cancel_pressed) def on_options_changed(self, value): self.get_boolean_setting(value) @@ -134,10 +131,24 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["import_cameras"].setChecked(self.import_cameras) self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + + def get_result(self): + import copy + templates = self.project_templates + name = self.template_name + if not name: + return None + template = get_template_by_name(name, templates) + template = copy.deepcopy(template) # do not edit the original + template["import_cameras"] = self.widgets["import_cameras"].isChecked() + template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + return template + @classmethod def prompt(cls, templates): dialog = cls(templates) dialog.exec_() + return dialog @@ -157,17 +168,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( - self.project_templates) - template_name = result.template_name - if template_name is None: + self.project_templates).get_result() + if result is None: return - import_cameras = result.import_cameras - template = get_template_by_name(template_name, self.project_templates) + import_cameras = result["import_cameras"] sp_settings = substance_painter.project.Settings( - normal_map_format=_convert(template["normal_map_format"]), - project_workflow=_convert(template["project_workflow"]), - tangent_space_mode=_convert(template["tangent_space_mode"]), - default_texture_resolution=template["default_texture_resolution"] + normal_map_format=_convert(result["normal_map_format"]), + import_cameras=result["import_cameras"], + project_workflow=_convert(result["project_workflow"]), + tangent_space_mode=_convert(result["tangent_space_mode"]), + default_texture_resolution=result["default_texture_resolution"] ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project @@ -178,7 +188,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - preserve_strokes = result.preserve_strokes + preserve_strokes = result["preserve_cameras"] settings = substance_painter.project.MeshReloadingSettings( import_cameras=import_cameras, preserve_strokes=preserve_strokes) From 70b11f9a50dcea4571d6155fc98483d77a289222 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:26:04 +0800 Subject: [PATCH 317/423] cosmetic fix --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 601e723f1f..b74c6ca08b 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -131,7 +131,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["import_cameras"].setChecked(self.import_cameras) self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) - def get_result(self): import copy templates = self.project_templates From cdfe59fd18270951d1ba6ac9a4ca5afe40ea5573 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:27:34 +0800 Subject: [PATCH 318/423] add comment to explain the action --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index b74c6ca08b..4148c8ab8a 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -136,6 +136,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): templates = self.project_templates name = self.template_name if not name: + # if user close the dialog, + # template name would be None return None template = get_template_by_name(name, templates) template = copy.deepcopy(template) # do not edit the original From 41e4af06da3d7e9cb77d273ce112c168046dd870 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 18:04:57 +0800 Subject: [PATCH 319/423] updating the code with big roy's feedback --- .../plugins/load/load_mesh.py | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 4148c8ab8a..de99dcbc95 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -1,3 +1,4 @@ +import copy from qtpy import QtWidgets, QtCore from ayon_core.pipeline import ( load, @@ -77,11 +78,11 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) } - for template in self.template_names: - self.widgets["template_options"].addItem(template) + + self.widgets["template_options"].addItem(self.template_names) template_name = self.widgets["template_options"].currentText() - self.get_boolean_setting(template_name) + self._update_to_match_template(template_name) # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) @@ -100,14 +101,14 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): layout.addWidget(self.widgets["buttons"]) self.widgets["template_options"].currentTextChanged.connect( - self.on_options_changed) - self.widgets["buttons"].accepted.connect(self.on_ok_pressed) - self.widgets["buttons"].rejected.connect(self.on_cancel_pressed) + self._update_to_match_template) + self.widgets["buttons"].accepted.connect(self.on_accept) + self.widgets["buttons"].rejected.connect(self.on_reject) def on_options_changed(self, value): self.get_boolean_setting(value) - def on_ok_pressed(self): + def on_accept(self): if self.widgets["import_cameras"].isChecked(): self.import_cameras = True if self.widgets["preserve_strokes"].isChecked(): @@ -117,32 +118,28 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): ) self.close() - def on_cancel_pressed(self): - self.template_name = None + def on_reject(self): self.close() - def get_boolean_setting(self, template_name): - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == template_name) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == template_name) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + def _update_to_match_template(self, template_name): + template = get_template_by_name(template_name, self.project_templates) + self.widgets["import_cameras"].setChecked(template["import_cameras"]) + self.widgets["preserve_strokes"].setChecked( + template["preserve_strokes"]) - def get_result(self): - import copy + def get_project_configuration(self): templates = self.project_templates - name = self.template_name - if not name: - # if user close the dialog, - # template name would be None + if not self.template_name: return None - template = get_template_by_name(name, templates) + template = get_template_by_name(self.template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + for key in template.keys(): + if key in ["normal_map_format", + "project_workflow", + "tangent_space_mode"]: + template[key] = _convert(template[key]) return template @classmethod @@ -150,7 +147,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): dialog = cls(templates) dialog.exec_() - return dialog + return dialog.get_project_configuration() class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -169,15 +166,14 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( - self.project_templates).get_result() + self.project_templates) if result is None: return - import_cameras = result["import_cameras"] sp_settings = substance_painter.project.Settings( - normal_map_format=_convert(result["normal_map_format"]), + normal_map_format=result["normal_map_format"], import_cameras=result["import_cameras"], - project_workflow=_convert(result["project_workflow"]), - tangent_space_mode=_convert(result["tangent_space_mode"]), + project_workflow=result["project_workflow"], + tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) if not substance_painter.project.is_open(): @@ -189,10 +185,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - preserve_strokes = result["preserve_cameras"] settings = substance_painter.project.MeshReloadingSettings( - import_cameras=import_cameras, - preserve_strokes=preserve_strokes) + import_cameras=result["import_cameras"], + preserve_strokes=result["preserve_strokes"]) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -219,7 +214,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": import_cameras, + "import_cameras": result["import_cameras"], } set_container_metadata(project_mesh_object_name, container) From bb225a3f66841d2487be8168a7b412ea335ef7f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 18:33:03 +0800 Subject: [PATCH 320/423] clean up the dialog code --- .../plugins/load/load_mesh.py | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index de99dcbc95..623f5a175f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -59,9 +59,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): super(SubstanceProjectConfigurationWindow, self).__init__() self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) - self.import_cameras = False - self.preserve_strokes = False - self.template_name = None + self.configuration = None self.template_names = [template["name"] for template in project_templates] self.project_templates = project_templates @@ -79,7 +77,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): | QtWidgets.QDialogButtonBox.Cancel) } - self.widgets["template_options"].addItem(self.template_names) + self.widgets["template_options"].addItems(self.template_names) template_name = self.widgets["template_options"].currentText() self._update_to_match_template(template_name) @@ -90,7 +88,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build combobox layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) - # Build buttons layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) # Build layout. @@ -105,17 +102,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["buttons"].accepted.connect(self.on_accept) self.widgets["buttons"].rejected.connect(self.on_reject) - def on_options_changed(self, value): - self.get_boolean_setting(value) - def on_accept(self): - if self.widgets["import_cameras"].isChecked(): - self.import_cameras = True - if self.widgets["preserve_strokes"].isChecked(): - self.preserve_strokes = True - self.template_name = ( - self.widgets["template_options"].currentText() - ) + self.configuration = self.get_project_configuration() self.close() def on_reject(self): @@ -129,9 +117,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): def get_project_configuration(self): templates = self.project_templates - if not self.template_name: - return None - template = get_template_by_name(self.template_name, templates) + template_name = self.widgets["template_options"].currentText() + template = get_template_by_name(template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() @@ -147,7 +134,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): dialog = cls(templates) dialog.exec_() - return dialog.get_project_configuration() + return dialog.configuration class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -167,15 +154,14 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) - if result is None: - return sp_settings = substance_painter.project.Settings( - normal_map_format=result["normal_map_format"], import_cameras=result["import_cameras"], + normal_map_format=result["normal_map_format"], project_workflow=result["project_workflow"], tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) + print(sp_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) From a46489edc38f9cd2ff9adf3ab8101a6cf76f1e79 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:15:46 +0800 Subject: [PATCH 321/423] add deleteLater() after dialog.exec_ --- .../substancepainter/plugins/load/load_mesh.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 623f5a175f..b103ef8e8f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -16,7 +16,7 @@ import substance_painter.project def _convert(substance_attr): """Return Substance Painter Python API Project attribute from string. - + This converts a string like "ProjectWorkflow.Default" to for example the Substance Painter Python API equivalent object, like: `substance_painter.project.ProjectWorkflow.Default` @@ -122,16 +122,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() - for key in template.keys(): - if key in ["normal_map_format", - "project_workflow", - "tangent_space_mode"]: - template[key] = _convert(template[key]) + for key in ["normal_map_format", + "project_workflow", + "tangent_space_mode"]: + template[key] = _convert(template[key]) return template @classmethod def prompt(cls, templates): dialog = cls(templates) + dialog.deleteLater() dialog.exec_() return dialog.configuration @@ -147,6 +147,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): order = -10 icon = "code-fork" color = "orange" + + # Defined via settings project_templates = [] def load(self, context, name, namespace, options=None): @@ -161,7 +163,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) - print(sp_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) From 7b68bb84983888e492e03c7fcd8ee2a881b8acd4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:17:46 +0800 Subject: [PATCH 322/423] ensure to have no error after pressing cancel button --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index b103ef8e8f..8e61a8c4e5 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -131,8 +131,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): @classmethod def prompt(cls, templates): dialog = cls(templates) - dialog.deleteLater() dialog.exec_() + dialog.deleteLater() return dialog.configuration @@ -156,6 +156,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) + if not result: + return sp_settings = substance_painter.project.Settings( import_cameras=result["import_cameras"], normal_map_format=result["normal_map_format"], From dd11cf95caa2eb0faec7d3f2348ed2a00adf216c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:18:23 +0800 Subject: [PATCH 323/423] add comment on the condition on checking result variable --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 8e61a8c4e5..a6d8aef3c0 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -157,6 +157,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) if not result: + # cancelling loader action return sp_settings = substance_painter.project.Settings( import_cameras=result["import_cameras"], From 369321b18c30f93e46b044bd57cf7623cba4f44a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:43:40 +0800 Subject: [PATCH 324/423] cosmetic fix --- .../hosts/substancepainter/plugins/load/load_mesh.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index a6d8aef3c0..0764789b66 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -121,10 +121,12 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): template = get_template_by_name(template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() - template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + template["preserve_strokes"] = ( + self.widgets["preserve_strokes"].isChecked() + ) for key in ["normal_map_format", - "project_workflow", - "tangent_space_mode"]: + "project_workflow", + "tangent_space_mode"]: template[key] = _convert(template[key]) return template From 9173e77e3066232e97a3335f2577de7180148f69 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:08 +0800 Subject: [PATCH 325/423] Update client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py Co-authored-by: Roy Nieterau --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 0764789b66..1a5ca1aec3 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -134,9 +134,9 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): def prompt(cls, templates): dialog = cls(templates) dialog.exec_() + configuration = dialog.configuration dialog.deleteLater() - - return dialog.configuration + return configuration class SubstanceLoadProjectMesh(load.LoaderPlugin): From 7ebdeeae26230e164f6c7451f8e56657fac1c05d Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:19 +0800 Subject: [PATCH 326/423] cosmetic Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 1a5ca1aec3..e5cfa469ed 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -119,7 +119,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): templates = self.project_templates template_name = self.widgets["template_options"].currentText() template = get_template_by_name(template_name, templates) - template = copy.deepcopy(template) # do not edit the original + template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = ( self.widgets["preserve_strokes"].isChecked() From 0ec9d1e99365e009d82c2ecc28fb78210a3300a3 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:36 +0800 Subject: [PATCH 327/423] Remove unnecessary comment Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index e5cfa469ed..6a67f5c686 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -204,7 +204,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # We want store some options for updating to keep consistent behavior # from the user's original choice. We don't store 'preserve_strokes' # as we always preserve strokes on updates. - # TODO: update the code container["options"] = { "import_cameras": result["import_cameras"], } From efd0e0774deaa7a76c45cc9be039f71bea95dbfb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 21:43:47 +0800 Subject: [PATCH 328/423] move sp_setting into if condition --- .../substancepainter/plugins/load/load_mesh.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index 6a67f5c686..d5aac1191c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -161,17 +161,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if not result: # cancelling loader action return - sp_settings = substance_painter.project.Settings( - import_cameras=result["import_cameras"], - normal_map_format=result["normal_map_format"], - project_workflow=result["project_workflow"], - tangent_space_mode=result["tangent_space_mode"], - default_texture_resolution=result["default_texture_resolution"] - ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) - + sp_settings = substance_painter.project.Settings( + import_cameras=result["import_cameras"], + normal_map_format=result["normal_map_format"], + project_workflow=result["project_workflow"], + tangent_space_mode=result["tangent_space_mode"], + default_texture_resolution=result["default_texture_resolution"] + ) settings = substance_painter.project.create( mesh_file_path=path, settings=sp_settings ) From c260996522872a8bf56f74b52a598f61f9ba3b0e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:07:02 +0200 Subject: [PATCH 329/423] Implement `Reorder` helper for Maya --- client/ayon_core/hosts/maya/api/lib.py | 252 +++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 321bcbc0b5..e30070c796 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -6,6 +6,7 @@ from pprint import pformat import sys import uuid import re +import operator import json import logging @@ -4403,3 +4404,254 @@ def create_rig_animation_instance( variant=namespace, pre_create_data={"use_selection": True} ) + + +class Reorder(object): + """Helper functions for reordering in Maya outliner""" + + @staticmethod + def group_by_parent(nodes): + """Groups the given input list of nodes by parent. + + This is a convenience function for the Reorder functionality. + This function assumes the nodes are in the `long/fullPath` format. + """ + nodes = cmds.ls(nodes, long=True) + nodes_by_parent = defaultdict(list) + for node in nodes: + parent = node.rsplit("|", 1)[0] + nodes_by_parent[parent].append(node) + return nodes_by_parent + + @staticmethod + def get_children_with_index(parent): + """Get children under parent with their indices""" + def node_to_index(nodes): + return {node: index for index, node in enumerate(nodes)} + + if not parent: + return node_to_index(cmds.ls(assemblies=True, long=True)) + else: + return node_to_index( + cmds.listRelatives(parent, + children=True, + fullPath=True) or [] + ) + + @staticmethod + def get_index(node): + node = cmds.ls(node, long=True)[0] # enforce long names + parent = node.rsplit("|", 1)[0] + if not parent: + return cmds.ls(assemblies=True, long=True).index(node) + else: + return cmds.listRelatives(parent, + children=True, + fullPath=True).index(node) + + @staticmethod + def get_indices(nodes): + """Returns a dictionary with node, index pairs. + + This is preferred over get_index method for larger number of nodes, + because it is more optimal in performance. + + eg: + { + '|side': 3, + '|top': 1, + '|pSphere1': 4, + '|persp': 0, + '|front': 2 + } + + Returns: + dict: index by node + """ + nodes = cmds.ls(nodes, long=True) # enforce long names + node_indices = dict() + cached_children = dict() + for node in nodes: + parent = node.rsplit("|", 1)[0] + if parent not in cached_children: + cached_children[parent] = Reorder.get_children_with_index(parent) # noqa: E501 + + node_indices[node] = cached_children[parent][node] + return node_indices + + @staticmethod + def set_index(node, index): + if not node: + return + cmds.reorder(node, front=True) + cmds.reorder(node, r=index) + + @staticmethod + def set_indices(node_indices): + """Set node order by node to index dict. + + Args: + node_indices (dict): Node name to index dictionary + + """ + if not isinstance(node_indices, dict): + raise TypeError( + "Reorder.set_indices() requires a dictionary with " + "(node, index) pairs as input. " + "`{0}` is an invalid input type.".format( + type(node_indices).__name__) + ) + + if not node_indices: + return + + # force nodes to the back to not influence each other during reorder + cmds.reorder(node_indices.keys(), back=True) + + for node, index in sorted(node_indices.items(), + key=operator.itemgetter(1)): + Reorder.set_index(node, index) + + @staticmethod + def sort(nodes, key=lambda x: x.rsplit("|", 1)[-1], reverse=False): + """Sorts the node in scene by the key function. + + Default sorting key is alphabetically by using the object's short name. + """ + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + + for child_nodes in parents.values(): + + node_indices = Reorder.get_indices(child_nodes) + indices = sorted(node_indices.values()) + + new_indices = { + node: indices[i] for i, node in + enumerate(sorted(child_nodes, key=key, reverse=reverse)) + } + Reorder.set_indices(new_indices) + + @staticmethod + def reverse(nodes): + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + + for child_nodes in parents.values(): + + node_indices = Reorder.get_indices(child_nodes) + indices = sorted(node_indices.values(), reverse=False) + + iterable = enumerate(sorted(node_indices.items(), + key=operator.itemgetter(1), + reverse=True)) + new_indices = { + node: indices[i] for i, (node, _old_index) in iterable + } + Reorder.set_indices(new_indices) + + @staticmethod + def align_bottom(nodes): + """Reorder to the lowest (most back) of node in `nodes`.""" + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + for child_nodes in parents.values(): + + # Reorder.set_index forces to front and then moves all objects + # together (so they will be stacked together). Then it applies the + # index as relative offset, we can use that here to our advantage. + # And it is a lot faster than Reorder.set_indices in that scenario. + index_per_node = Reorder.get_indices(child_nodes) + back_index = max(index_per_node.values()) + new_front_index = back_index - len(child_nodes) + 1 + Reorder.set_index(child_nodes, new_front_index) + + @staticmethod + def align_top(nodes): + """Reorder to the highest (most front) of node in `nodes`.""" + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Group by parent to sort nodes per parent + parents = Reorder.group_by_parent(nodes) + for childNodes in parents.values(): + + # Reorder.set_index forces to front and then moves all objects + # together (so they will be stacked together). Then it applies the + # index as relative offset, we can use that here to our advantage. + # And it is a lot faster than Reorder.set_indices in that scenario. + index_per_node = Reorder.get_indices(childNodes) + front_index = min(index_per_node.values()) + Reorder.set_index(childNodes, front_index) + + @staticmethod + def move(nodes, relative, wrap=True): + """ Reorder by the given relative amount. """ + # TODO: Implement the disabling of wrapping around when at bottom. + if not nodes: + return + cmds.reorder(nodes, r=relative) + + @staticmethod + def to_bottom(nodes): + """Reorder to all the way to the bottom.""" + if not nodes: + return + cmds.reorder(nodes, back=True) + + @staticmethod + def to_top(nodes): + """Reorder to all the way to the top.""" + if not nodes: + return + cmds.reorder(nodes, front=True) + + @staticmethod + def order_to(nodes): + """Reorder the nodes to the order of the input list. + + Tip: + If you pass this your current selection list it will reorder + the nodes to the order of your selection. + + """ + nodes = cmds.ls(nodes, long=True) # ensure long paths + if not nodes: + return + + # Make a dictionary of the input order so we can optimize the look-up + # of the index in the order of the input `nodes`. + selected_order = {node: i for i, node in enumerate(nodes)} + + # Group by parent since we want to sort nodes under its current parent + parents = Reorder.group_by_parent(nodes) + for child_nodes in parents.values(): + + # Get the current indices + node_indices = Reorder.get_indices(child_nodes) + + # We get the original indices so we can position to those same + # positions, albeit with the new ordering of the nodes. + orig_indices = sorted(node_indices.values()) + + # Order the nodes by current selection (input list) and then apply + # the list of indices from `nodeIndices` in low-to-high order. + new_indices = dict( + zip(sorted(node_indices.keys(), + key=lambda x: selected_order[x]), + orig_indices) + ) + Reorder.set_indices(new_indices) From 3db28b74f18441d4db8fd60e3681b34041d7de37 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:10:58 +0200 Subject: [PATCH 330/423] Refactor Maya load placeholder, fixes: - Implement fixes for outliner order of loaded placeholders (always place after placeholder) - Allow storing more complex data, e.g. EnumDef with multiselection=True - Change in behavior: This may fix a bug where previously placeholders may sometimes load only one subset when it should have loaded more. It could thus influence the loading behavior on existing templates - Implements a PlaceholderPlugin base class to be used by others potential plug-ins --- .../maya/api/workfile_template_builder.py | 159 +++++++++++- .../maya/plugins/template/load_placeholder.py | 234 ++++-------------- 2 files changed, 208 insertions(+), 185 deletions(-) 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 cfd416b708..d518d3933c 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -1,3 +1,5 @@ +import json + from maya import cmds from ayon_core.pipeline import ( @@ -8,13 +10,15 @@ from ayon_core.pipeline import ( ) from ayon_core.pipeline.workfile.workfile_template_builder import ( TemplateAlreadyImported, - AbstractTemplateBuilder + AbstractTemplateBuilder, + PlaceholderPlugin, + PlaceholderItem, ) from ayon_core.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, ) -from .lib import get_main_window +from .lib import read, imprint, get_main_window PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -86,6 +90,157 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): return True +class MayaPlaceholderPlugin(PlaceholderPlugin): + """Base Placeholder Plugin for Maya with one unified cache. + + Creates a locator as placeholder node, which during populate provide + all of its attributes defined on the locator's transform in + `placeholder.data` and where `placeholder.scene_identifier` is the + full path to the node. + + Inherited classes must still implement `populate_placeholder` + + """ + + use_selection_as_parent = True + item_class = PlaceholderItem + + def _create_placeholder_name(self, placeholder_data): + return self.identifier.replace(".", "_") + + def _collect_scene_placeholders(self): + nodes_by_identifier = self.builder.get_shared_populate_data( + "placeholder_nodes" + ) + if nodes_by_identifier is None: + # Cache placeholder data to shared data + nodes = cmds.ls("*.plugin_identifier", long=True, objectsOnly=True) + + nodes_by_identifier = {} + for node in nodes: + identifier = cmds.getAttr("{}.plugin_identifier".format(node)) + nodes_by_identifier.setdefault(identifier, []).append(node) + + # Set the cache + self.builder.set_shared_populate_data( + "placeholder_nodes", nodes_by_identifier + ) + + return nodes_by_identifier + + def create_placeholder(self, placeholder_data): + + parent = None + if self.use_selection_as_parent: + selection = cmds.ls(selection=True) + if len(selection) > 1: + raise ValueError( + "More than one node is selected. " + "Please select only one to define the parent." + ) + 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] + + self.imprint(placeholder, placeholder_data) + + def update_placeholder(self, placeholder_item, placeholder_data): + node_name = placeholder_item.scene_identifier + + changed_values = {} + for key, value in placeholder_data.items(): + if value != placeholder_item.data.get(key): + changed_values[key] = value + + # Delete attributes to ensure we imprint new data with correct type + for key in changed_values.keys(): + placeholder_item.data[key] = value + if cmds.attributeQuery(key, node=node_name, exists=True): + attribute = "{}.{}".format(node_name, key) + cmds.deleteAttr(attribute) + + self.imprint(node_name, changed_values) + + def collect_placeholders(self): + placeholders = [] + nodes_by_identifier = self._collect_scene_placeholders() + for node in nodes_by_identifier.get(self.identifier, []): + # TODO do data validations and maybe upgrades if they are invalid + placeholder_data = self.read(node) + placeholders.append( + self.item_class(scene_identifier=node, + data=placeholder_data, + plugin=self) + ) + + return placeholders + + def post_placeholder_process(self, placeholder, failed): + """Cleanup placeholder after load of its corresponding representations. + + Hide placeholder, add them to placeholder set. + Used only by PlaceholderCreateMixin and PlaceholderLoadMixin + + 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("{}.hiddenInOutliner".format(node), True) + + def delete_placeholder(self, placeholder): + """Remove placeholder if building was successful + + Used only by PlaceholderCreateMixin and PlaceholderLoadMixin. + """ + node = placeholder.scene_identifier + + # To avoid that deleting a placeholder node will have Maya delete + # any objectSets the node was a member of we will first remove it + # from any sets it was a member of. This way the `PLACEHOLDERS_SET` + # will survive long enough + sets = cmds.listSets(o=node) or [] + for object_set in sets: + cmds.sets(node, remove=object_set) + + cmds.delete(node) + + def imprint(self, node, data): + """Imprint call for placeholder node""" + + # Complicated data that can't be represented as flat maya attributes + # we write to json strings, e.g. multiselection EnumDef + for key, value in data.items(): + if isinstance(value, (list, tuple, dict)): + data[key] = "JSON::{}".format(json.dumps(value)) + + imprint(node, data) + + def read(self, node): + """Read call for placeholder node""" + + data = read(node) + + # Complicated data that can't be represented as flat maya attributes + # we read from json strings, e.g. multiselection EnumDef + for key, value in data.items(): + if isinstance(value, str) and value.startswith("JSON::"): + value = value[len("JSON::"):] # strip of JSON:: prefix + data[key] = json.loads(value) + + return data + + 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 index 5bfaae6500..2de4594f47 100644 --- a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py @@ -1,87 +1,48 @@ -import json - from maya import cmds from ayon_core.pipeline.workfile.workfile_template_builder import ( - PlaceholderPlugin, - LoadPlaceholderItem, PlaceholderLoadMixin, + LoadPlaceholderItem ) from ayon_core.hosts.maya.api.lib import ( - read, - imprint, - get_reference_node + get_container_transforms, + get_node_parent, + Reorder +) +from ayon_core.hosts.maya.api.workfile_template_builder import ( + MayaPlaceholderPlugin, ) -from ayon_core.hosts.maya.api.workfile_template_builder import PLACEHOLDER_SET -class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): +class MayaPlaceholderLoadPlugin(MayaPlaceholderPlugin, 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 + item_class = LoadPlaceholderItem def _create_placeholder_name(self, placeholder_data): - placeholder_name_parts = placeholder_data["builder_type"].split("_") - pos = 1 + # Split builder type: context_assets, linked_assets, all_assets + prefix, suffix = placeholder_data["builder_type"].split("_", 1) + parts = [prefix] + + # add family if any 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 + parts.append(placeholder_product_type) # 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 + loader_args = eval(loader_args) + for value in loader_args.values(): + parts.append(str(value)) - placeholder_name = "_".join(placeholder_name_parts) + parts.append(suffix) + placeholder_name = "_".join(parts) return placeholder_name.capitalize() @@ -104,68 +65,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): ) 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) @@ -176,25 +75,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): 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) @@ -210,55 +90,43 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): 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]) + # TODO: This currently returns only a single root but a loaded scene + # could technically load more than a single root + container_root = get_container_transforms(container, root=True) - 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) + # Bugfix: The get_container_transforms does not recognize the load + # reference group currently + # TODO: Remove this when it does + parent = get_node_parent(container_root) + if parent: + container_root = parent + roots = [container_root] - elif not cmds.sets(root, q=True): - return + # Add the loaded roots to the holding sets if they exist + holding_sets = cmds.listSets(object=placeholder.scene_identifier) or [] + for holding_set in holding_sets: + cmds.sets(roots, forceElement=holding_set) - # Move loaded nodes to correct index in outliner hierarchy + # Parent the roots to the place of the placeholder locator and match + # its matrix placeholder_form = cmds.xform( placeholder.scene_identifier, - q=True, + query=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) + scene_parent = get_node_parent(placeholder.scene_identifier) + for node in set(roots): + cmds.xform(node, matrix=placeholder_form, worldSpace=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) + if scene_parent != get_node_parent(node): + if scene_parent: + node = cmds.parent(node, scene_parent)[0] + else: + node = cmds.parent(node, world=True)[0] + + # Move loaded nodes in index order next to their placeholder node + cmds.reorder(node, back=True) + index = Reorder.get_index(placeholder.scene_identifier) + cmds.reorder(node, front=True) + cmds.reorder(node, relative=index + 1) From 93dda6110add3ceb9d1956eec87e1fdbaf6cb9f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:15:56 +0200 Subject: [PATCH 331/423] Remove bloated `Reorder` class in favor of the only used function --- client/ayon_core/hosts/maya/api/lib.py | 261 +----------------- .../maya/plugins/template/load_placeholder.py | 4 +- 2 files changed, 17 insertions(+), 248 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index e30070c796..2e77fe6c64 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -4406,252 +4406,21 @@ def create_rig_animation_instance( ) -class Reorder(object): - """Helper functions for reordering in Maya outliner""" +def get_node_index_under_parent(node: str) -> int: + """Return the index of a DAG node under its parent. - @staticmethod - def group_by_parent(nodes): - """Groups the given input list of nodes by parent. + Arguments: + node (str): A DAG Node path. - This is a convenience function for the Reorder functionality. - This function assumes the nodes are in the `long/fullPath` format. - """ - nodes = cmds.ls(nodes, long=True) - nodes_by_parent = defaultdict(list) - for node in nodes: - parent = node.rsplit("|", 1)[0] - nodes_by_parent[parent].append(node) - return nodes_by_parent + Returns: + int: The DAG node's index under its parents or world - @staticmethod - def get_children_with_index(parent): - """Get children under parent with their indices""" - def node_to_index(nodes): - return {node: index for index, node in enumerate(nodes)} - - if not parent: - return node_to_index(cmds.ls(assemblies=True, long=True)) - else: - return node_to_index( - cmds.listRelatives(parent, - children=True, - fullPath=True) or [] - ) - - @staticmethod - def get_index(node): - node = cmds.ls(node, long=True)[0] # enforce long names - parent = node.rsplit("|", 1)[0] - if not parent: - return cmds.ls(assemblies=True, long=True).index(node) - else: - return cmds.listRelatives(parent, - children=True, - fullPath=True).index(node) - - @staticmethod - def get_indices(nodes): - """Returns a dictionary with node, index pairs. - - This is preferred over get_index method for larger number of nodes, - because it is more optimal in performance. - - eg: - { - '|side': 3, - '|top': 1, - '|pSphere1': 4, - '|persp': 0, - '|front': 2 - } - - Returns: - dict: index by node - """ - nodes = cmds.ls(nodes, long=True) # enforce long names - node_indices = dict() - cached_children = dict() - for node in nodes: - parent = node.rsplit("|", 1)[0] - if parent not in cached_children: - cached_children[parent] = Reorder.get_children_with_index(parent) # noqa: E501 - - node_indices[node] = cached_children[parent][node] - return node_indices - - @staticmethod - def set_index(node, index): - if not node: - return - cmds.reorder(node, front=True) - cmds.reorder(node, r=index) - - @staticmethod - def set_indices(node_indices): - """Set node order by node to index dict. - - Args: - node_indices (dict): Node name to index dictionary - - """ - if not isinstance(node_indices, dict): - raise TypeError( - "Reorder.set_indices() requires a dictionary with " - "(node, index) pairs as input. " - "`{0}` is an invalid input type.".format( - type(node_indices).__name__) - ) - - if not node_indices: - return - - # force nodes to the back to not influence each other during reorder - cmds.reorder(node_indices.keys(), back=True) - - for node, index in sorted(node_indices.items(), - key=operator.itemgetter(1)): - Reorder.set_index(node, index) - - @staticmethod - def sort(nodes, key=lambda x: x.rsplit("|", 1)[-1], reverse=False): - """Sorts the node in scene by the key function. - - Default sorting key is alphabetically by using the object's short name. - """ - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - - for child_nodes in parents.values(): - - node_indices = Reorder.get_indices(child_nodes) - indices = sorted(node_indices.values()) - - new_indices = { - node: indices[i] for i, node in - enumerate(sorted(child_nodes, key=key, reverse=reverse)) - } - Reorder.set_indices(new_indices) - - @staticmethod - def reverse(nodes): - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - - for child_nodes in parents.values(): - - node_indices = Reorder.get_indices(child_nodes) - indices = sorted(node_indices.values(), reverse=False) - - iterable = enumerate(sorted(node_indices.items(), - key=operator.itemgetter(1), - reverse=True)) - new_indices = { - node: indices[i] for i, (node, _old_index) in iterable - } - Reorder.set_indices(new_indices) - - @staticmethod - def align_bottom(nodes): - """Reorder to the lowest (most back) of node in `nodes`.""" - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - for child_nodes in parents.values(): - - # Reorder.set_index forces to front and then moves all objects - # together (so they will be stacked together). Then it applies the - # index as relative offset, we can use that here to our advantage. - # And it is a lot faster than Reorder.set_indices in that scenario. - index_per_node = Reorder.get_indices(child_nodes) - back_index = max(index_per_node.values()) - new_front_index = back_index - len(child_nodes) + 1 - Reorder.set_index(child_nodes, new_front_index) - - @staticmethod - def align_top(nodes): - """Reorder to the highest (most front) of node in `nodes`.""" - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Group by parent to sort nodes per parent - parents = Reorder.group_by_parent(nodes) - for childNodes in parents.values(): - - # Reorder.set_index forces to front and then moves all objects - # together (so they will be stacked together). Then it applies the - # index as relative offset, we can use that here to our advantage. - # And it is a lot faster than Reorder.set_indices in that scenario. - index_per_node = Reorder.get_indices(childNodes) - front_index = min(index_per_node.values()) - Reorder.set_index(childNodes, front_index) - - @staticmethod - def move(nodes, relative, wrap=True): - """ Reorder by the given relative amount. """ - # TODO: Implement the disabling of wrapping around when at bottom. - if not nodes: - return - cmds.reorder(nodes, r=relative) - - @staticmethod - def to_bottom(nodes): - """Reorder to all the way to the bottom.""" - if not nodes: - return - cmds.reorder(nodes, back=True) - - @staticmethod - def to_top(nodes): - """Reorder to all the way to the top.""" - if not nodes: - return - cmds.reorder(nodes, front=True) - - @staticmethod - def order_to(nodes): - """Reorder the nodes to the order of the input list. - - Tip: - If you pass this your current selection list it will reorder - the nodes to the order of your selection. - - """ - nodes = cmds.ls(nodes, long=True) # ensure long paths - if not nodes: - return - - # Make a dictionary of the input order so we can optimize the look-up - # of the index in the order of the input `nodes`. - selected_order = {node: i for i, node in enumerate(nodes)} - - # Group by parent since we want to sort nodes under its current parent - parents = Reorder.group_by_parent(nodes) - for child_nodes in parents.values(): - - # Get the current indices - node_indices = Reorder.get_indices(child_nodes) - - # We get the original indices so we can position to those same - # positions, albeit with the new ordering of the nodes. - orig_indices = sorted(node_indices.values()) - - # Order the nodes by current selection (input list) and then apply - # the list of indices from `nodeIndices` in low-to-high order. - new_indices = dict( - zip(sorted(node_indices.keys(), - key=lambda x: selected_order[x]), - orig_indices) - ) - Reorder.set_indices(new_indices) + """ + node = cmds.ls(node, long=True)[0] # enforce long names + parent = node.rsplit("|", 1)[0] + if not parent: + return cmds.ls(assemblies=True, long=True).index(node) + else: + return cmds.listRelatives(parent, + children=True, + fullPath=True).index(node) diff --git a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py index 2de4594f47..b07c7e9a70 100644 --- a/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/template/load_placeholder.py @@ -7,7 +7,7 @@ from ayon_core.pipeline.workfile.workfile_template_builder import ( from ayon_core.hosts.maya.api.lib import ( get_container_transforms, get_node_parent, - Reorder + get_node_index_under_parent ) from ayon_core.hosts.maya.api.workfile_template_builder import ( MayaPlaceholderPlugin, @@ -127,6 +127,6 @@ class MayaPlaceholderLoadPlugin(MayaPlaceholderPlugin, PlaceholderLoadMixin): # Move loaded nodes in index order next to their placeholder node cmds.reorder(node, back=True) - index = Reorder.get_index(placeholder.scene_identifier) + index = get_node_index_under_parent(placeholder.scene_identifier) cmds.reorder(node, front=True) cmds.reorder(node, relative=index + 1) From da5abf836773283ccb4d1296a35258cc9106e443 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:24:44 +0200 Subject: [PATCH 332/423] 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 333/423] 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 334/423] 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 335/423] 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 814220e64a665ac38f051cfe5f09e5c9cf3e9295 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:23 +0200 Subject: [PATCH 336/423] unreal uses package.py --- server_addon/unreal/package.py | 3 +++ server_addon/unreal/server/__init__.py | 6 ------ server_addon/unreal/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/unreal/package.py delete mode 100644 server_addon/unreal/server/version.py diff --git a/server_addon/unreal/package.py b/server_addon/unreal/package.py new file mode 100644 index 0000000000..cab89ca873 --- /dev/null +++ b/server_addon/unreal/package.py @@ -0,0 +1,3 @@ +name = "unreal" +title = "Unreal" +version = "0.1.0" diff --git a/server_addon/unreal/server/__init__.py b/server_addon/unreal/server/__init__.py index a5f3e9597d..751560b623 100644 --- a/server_addon/unreal/server/__init__.py +++ b/server_addon/unreal/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import UnrealSettings, DEFAULT_VALUES class UnrealAddon(BaseServerAddon): - name = "unreal" - title = "Unreal" - version = __version__ settings_model: Type[UnrealSettings] = UnrealSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/unreal/server/version.py b/server_addon/unreal/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/unreal/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 36c4bc8667632772b4802dd8c6962bacfd5310c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:34 +0200 Subject: [PATCH 337/423] tvpaint uses package.py --- server_addon/tvpaint/package.py | 3 +++ server_addon/tvpaint/server/__init__.py | 4 ---- server_addon/tvpaint/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/tvpaint/package.py delete mode 100644 server_addon/tvpaint/server/version.py diff --git a/server_addon/tvpaint/package.py b/server_addon/tvpaint/package.py new file mode 100644 index 0000000000..2be3164f4a --- /dev/null +++ b/server_addon/tvpaint/package.py @@ -0,0 +1,3 @@ +name = "tvpaint" +title = "TVPaint" +version = "0.1.2" diff --git a/server_addon/tvpaint/server/__init__.py b/server_addon/tvpaint/server/__init__.py index 033d7d3792..658dcf0bb6 100644 --- a/server_addon/tvpaint/server/__init__.py +++ b/server_addon/tvpaint/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TvpaintSettings, DEFAULT_VALUES class TvpaintAddon(BaseServerAddon): - name = "tvpaint" - title = "TVPaint" - version = __version__ settings_model: Type[TvpaintSettings] = TvpaintSettings async def get_default_settings(self): diff --git a/server_addon/tvpaint/server/version.py b/server_addon/tvpaint/server/version.py deleted file mode 100644 index b3f4756216..0000000000 --- a/server_addon/tvpaint/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.2" From 7184ee5c5ca8c2d7dc1b2784b094afe6e1d7632b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:51 +0200 Subject: [PATCH 338/423] traypublisher uses package.py --- server_addon/traypublisher/package.py | 3 +++ server_addon/traypublisher/server/__init__.py | 5 ----- server_addon/traypublisher/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/traypublisher/package.py delete mode 100644 server_addon/traypublisher/server/version.py diff --git a/server_addon/traypublisher/package.py b/server_addon/traypublisher/package.py new file mode 100644 index 0000000000..4ca8ae9fd3 --- /dev/null +++ b/server_addon/traypublisher/package.py @@ -0,0 +1,3 @@ +name = "traypublisher" +title = "TrayPublisher" +version = "0.1.4" diff --git a/server_addon/traypublisher/server/__init__.py b/server_addon/traypublisher/server/__init__.py index e6f079609f..830f325ac0 100644 --- a/server_addon/traypublisher/server/__init__.py +++ b/server_addon/traypublisher/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TraypublisherSettings, DEFAULT_TRAYPUBLISHER_SETTING class Traypublisher(BaseServerAddon): - name = "traypublisher" - title = "TrayPublisher" - version = __version__ - settings_model = TraypublisherSettings async def get_default_settings(self): diff --git a/server_addon/traypublisher/server/version.py b/server_addon/traypublisher/server/version.py deleted file mode 100644 index de699158fd..0000000000 --- a/server_addon/traypublisher/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.4" From 76482b3bfbde769232f89383b9d071e11dfe6a89 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:02 +0200 Subject: [PATCH 339/423] timers manager uses package.py --- server_addon/timers_manager/package.py | 3 +++ server_addon/timers_manager/server/__init__.py | 4 ---- server_addon/timers_manager/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/timers_manager/package.py delete mode 100644 server_addon/timers_manager/server/version.py diff --git a/server_addon/timers_manager/package.py b/server_addon/timers_manager/package.py new file mode 100644 index 0000000000..bd6b81b4b7 --- /dev/null +++ b/server_addon/timers_manager/package.py @@ -0,0 +1,3 @@ +name = "timers_manager" +title = "Timers Manager" +version = "0.1.1" diff --git a/server_addon/timers_manager/server/__init__.py b/server_addon/timers_manager/server/__init__.py index 29f9d47370..32e83d295c 100644 --- a/server_addon/timers_manager/server/__init__.py +++ b/server_addon/timers_manager/server/__init__.py @@ -2,12 +2,8 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TimersManagerSettings class TimersManagerAddon(BaseServerAddon): - name = "timers_manager" - version = __version__ - title = "Timers Manager" settings_model: Type[TimersManagerSettings] = TimersManagerSettings diff --git a/server_addon/timers_manager/server/version.py b/server_addon/timers_manager/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/timers_manager/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From 41b95ff1869f2abdc1b5f702eda898e2e566c6c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:12 +0200 Subject: [PATCH 340/423] substancepainter uses package.py --- server_addon/substancepainter/package.py | 3 +++ server_addon/substancepainter/server/__init__.py | 4 ---- server_addon/substancepainter/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/substancepainter/package.py delete mode 100644 server_addon/substancepainter/server/version.py diff --git a/server_addon/substancepainter/package.py b/server_addon/substancepainter/package.py new file mode 100644 index 0000000000..a064d80fd7 --- /dev/null +++ b/server_addon/substancepainter/package.py @@ -0,0 +1,3 @@ +name = "substancepainter" +title = "Substance Painter" +version = "0.1.0" diff --git a/server_addon/substancepainter/server/__init__.py b/server_addon/substancepainter/server/__init__.py index 2bf808d508..f6cd51e610 100644 --- a/server_addon/substancepainter/server/__init__.py +++ b/server_addon/substancepainter/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import SubstancePainterSettings, DEFAULT_SPAINTER_SETTINGS class SubstancePainterAddon(BaseServerAddon): - name = "substancepainter" - title = "Substance Painter" - version = __version__ settings_model: Type[SubstancePainterSettings] = SubstancePainterSettings async def get_default_settings(self): diff --git a/server_addon/substancepainter/server/version.py b/server_addon/substancepainter/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/substancepainter/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 10823b40a9f7ae35f27e60ead1301b7612edfb88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:33 +0200 Subject: [PATCH 341/423] resolve uses package.py --- server_addon/resolve/package.py | 3 +++ server_addon/resolve/server/__init__.py | 6 ------ server_addon/resolve/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/resolve/package.py delete mode 100644 server_addon/resolve/server/version.py diff --git a/server_addon/resolve/package.py b/server_addon/resolve/package.py new file mode 100644 index 0000000000..cf92413bce --- /dev/null +++ b/server_addon/resolve/package.py @@ -0,0 +1,3 @@ +name = "resolve" +title = "DaVinci Resolve" +version = "0.1.0" diff --git a/server_addon/resolve/server/__init__.py b/server_addon/resolve/server/__init__.py index a84180d0f5..35d2db19e4 100644 --- a/server_addon/resolve/server/__init__.py +++ b/server_addon/resolve/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import ResolveSettings, DEFAULT_VALUES class ResolveAddon(BaseServerAddon): - name = "resolve" - title = "DaVinci Resolve" - version = __version__ settings_model: Type[ResolveSettings] = ResolveSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/resolve/server/version.py b/server_addon/resolve/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/resolve/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 8749bdea3c7d34006b536c1c944ef05b5ab23271 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:42 +0200 Subject: [PATCH 342/423] photoshop uses package.py --- server_addon/photoshop/package.py | 3 +++ server_addon/photoshop/server/__init__.py | 5 ----- server_addon/photoshop/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/photoshop/package.py delete mode 100644 server_addon/photoshop/server/version.py diff --git a/server_addon/photoshop/package.py b/server_addon/photoshop/package.py new file mode 100644 index 0000000000..25615529d1 --- /dev/null +++ b/server_addon/photoshop/package.py @@ -0,0 +1,3 @@ +name = "photoshop" +title = "Photoshop" +version = "0.1.2" diff --git a/server_addon/photoshop/server/__init__.py b/server_addon/photoshop/server/__init__.py index 3a45f7a809..86d1025a2d 100644 --- a/server_addon/photoshop/server/__init__.py +++ b/server_addon/photoshop/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import PhotoshopSettings, DEFAULT_PHOTOSHOP_SETTING -from .version import __version__ class Photoshop(BaseServerAddon): - name = "photoshop" - title = "Photoshop" - version = __version__ - settings_model = PhotoshopSettings async def get_default_settings(self): diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py deleted file mode 100644 index df0c92f1e2..0000000000 --- a/server_addon/photoshop/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.2" From 8c6c5deca201e3875c809eb05f6225b65136a61c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:51 +0200 Subject: [PATCH 343/423] nuke uses package.py --- server_addon/nuke/package.py | 3 +++ server_addon/nuke/server/__init__.py | 4 ---- server_addon/nuke/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/nuke/package.py delete mode 100644 server_addon/nuke/server/version.py diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py new file mode 100644 index 0000000000..9630c370bc --- /dev/null +++ b/server_addon/nuke/package.py @@ -0,0 +1,3 @@ +name = "nuke" +title = "Nuke" +version = "0.1.10" diff --git a/server_addon/nuke/server/__init__.py b/server_addon/nuke/server/__init__.py index 032ceea5fb..aeb5e36675 100644 --- a/server_addon/nuke/server/__init__.py +++ b/server_addon/nuke/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import NukeSettings, DEFAULT_VALUES class NukeAddon(BaseServerAddon): - name = "nuke" - title = "Nuke" - version = __version__ settings_model: Type[NukeSettings] = NukeSettings async def get_default_settings(self): diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py deleted file mode 100644 index 569b1212f7..0000000000 --- a/server_addon/nuke/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.10" From d5100a1cc56ac6667d885b1c5f2586c4d17bc6e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:01 +0200 Subject: [PATCH 344/423] maya uses package.py --- server_addon/maya/package.py | 3 +++ server_addon/maya/server/__init__.py | 4 ---- server_addon/maya/server/version.py | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/maya/package.py delete mode 100644 server_addon/maya/server/version.py diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py new file mode 100644 index 0000000000..00f28d901e --- /dev/null +++ b/server_addon/maya/package.py @@ -0,0 +1,3 @@ +name = "maya" +title = "Maya" +version = "0.1.16" diff --git a/server_addon/maya/server/__init__.py b/server_addon/maya/server/__init__.py index 8784427dcf..6dda2cdd77 100644 --- a/server_addon/maya/server/__init__.py +++ b/server_addon/maya/server/__init__.py @@ -2,13 +2,9 @@ from ayon_server.addons import BaseServerAddon from .settings.main import MayaSettings, DEFAULT_MAYA_SETTING -from .version import __version__ class MayaAddon(BaseServerAddon): - name = "maya" - title = "Maya" - version = __version__ settings_model = MayaSettings async def get_default_settings(self): diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py deleted file mode 100644 index c1b7ff9d79..0000000000 --- a/server_addon/maya/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.16" From 73782cf148d03e85fb5ee9a870296065e4cd0cdc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:10 +0200 Subject: [PATCH 345/423] 3dsmax uses package.py --- server_addon/max/package.py | 3 +++ server_addon/max/server/__init__.py | 4 ---- server_addon/max/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/max/package.py delete mode 100644 server_addon/max/server/version.py diff --git a/server_addon/max/package.py b/server_addon/max/package.py new file mode 100644 index 0000000000..fb1f1b3050 --- /dev/null +++ b/server_addon/max/package.py @@ -0,0 +1,3 @@ +name = "max" +title = "Max" +version = "0.1.7" diff --git a/server_addon/max/server/__init__.py b/server_addon/max/server/__init__.py index 31c694a084..d03b29d249 100644 --- a/server_addon/max/server/__init__.py +++ b/server_addon/max/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import MaxSettings, DEFAULT_VALUES class MaxAddon(BaseServerAddon): - name = "max" - title = "Max" - version = __version__ settings_model: Type[MaxSettings] = MaxSettings async def get_default_settings(self): diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py deleted file mode 100644 index f1380eede2..0000000000 --- a/server_addon/max/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.7" From 0e7c112d8d9628fd66ca91a93be2771fc23f3b84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:24 +0200 Subject: [PATCH 346/423] houdini uses package.py --- server_addon/houdini/package.py | 3 +++ server_addon/houdini/server/__init__.py | 4 ---- server_addon/houdini/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/houdini/package.py delete mode 100644 server_addon/houdini/server/version.py diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py new file mode 100644 index 0000000000..4b72af2a89 --- /dev/null +++ b/server_addon/houdini/package.py @@ -0,0 +1,3 @@ +name = "houdini" +title = "Houdini" +version = "0.2.12" diff --git a/server_addon/houdini/server/__init__.py b/server_addon/houdini/server/__init__.py index 870ec2d0b7..8c1ffcb0b3 100644 --- a/server_addon/houdini/server/__init__.py +++ b/server_addon/houdini/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import HoudiniSettings, DEFAULT_VALUES class Houdini(BaseServerAddon): - name = "houdini" - title = "Houdini" - version = __version__ settings_model: Type[HoudiniSettings] = HoudiniSettings async def get_default_settings(self): diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py deleted file mode 100644 index b5c9b6cb71..0000000000 --- a/server_addon/houdini/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.2.12" From 85fe7f0de73708b5e113d6b37e4a560526b43800 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:31 +0200 Subject: [PATCH 347/423] hiero uses package.py --- server_addon/hiero/package.py | 3 +++ server_addon/hiero/server/__init__.py | 6 ------ server_addon/hiero/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/hiero/package.py delete mode 100644 server_addon/hiero/server/version.py diff --git a/server_addon/hiero/package.py b/server_addon/hiero/package.py new file mode 100644 index 0000000000..cabe68eb68 --- /dev/null +++ b/server_addon/hiero/package.py @@ -0,0 +1,3 @@ +name = "hiero" +title = "Hiero" +version = "0.1.2" diff --git a/server_addon/hiero/server/__init__.py b/server_addon/hiero/server/__init__.py index d0f9bcefc3..3db78eafd7 100644 --- a/server_addon/hiero/server/__init__.py +++ b/server_addon/hiero/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import HieroSettings, DEFAULT_VALUES class HieroAddon(BaseServerAddon): - name = "hiero" - title = "Hiero" - version = __version__ settings_model: Type[HieroSettings] = HieroSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py deleted file mode 100644 index b3f4756216..0000000000 --- a/server_addon/hiero/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.2" From 2b1e67c88f1a01aa39b3b1ef20f7109991b45b43 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:38 +0200 Subject: [PATCH 348/423] harmony uses package.py --- server_addon/harmony/package.py | 3 +++ server_addon/harmony/server/__init__.py | 5 ----- server_addon/harmony/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/harmony/package.py delete mode 100644 server_addon/harmony/server/version.py diff --git a/server_addon/harmony/package.py b/server_addon/harmony/package.py new file mode 100644 index 0000000000..83e88e7d57 --- /dev/null +++ b/server_addon/harmony/package.py @@ -0,0 +1,3 @@ +name = "harmony" +title = "Harmony" +version = "0.1.2" diff --git a/server_addon/harmony/server/__init__.py b/server_addon/harmony/server/__init__.py index 4ecda1989e..154618241e 100644 --- a/server_addon/harmony/server/__init__.py +++ b/server_addon/harmony/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import HarmonySettings, DEFAULT_HARMONY_SETTING -from .version import __version__ class Harmony(BaseServerAddon): - name = "harmony" - title = "Harmony" - version = __version__ - settings_model = HarmonySettings async def get_default_settings(self): diff --git a/server_addon/harmony/server/version.py b/server_addon/harmony/server/version.py deleted file mode 100644 index df0c92f1e2..0000000000 --- a/server_addon/harmony/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.2" From a1c3e57709862c015f568f37a92cadc026c083ef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:46 +0200 Subject: [PATCH 349/423] fusion uses package.py --- server_addon/fusion/package.py | 3 +++ server_addon/fusion/server/__init__.py | 6 ------ server_addon/fusion/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/fusion/package.py delete mode 100644 server_addon/fusion/server/version.py diff --git a/server_addon/fusion/package.py b/server_addon/fusion/package.py new file mode 100644 index 0000000000..9e7a46df2c --- /dev/null +++ b/server_addon/fusion/package.py @@ -0,0 +1,3 @@ +name = "fusion" +title = "Fusion" +version = "0.1.5" diff --git a/server_addon/fusion/server/__init__.py b/server_addon/fusion/server/__init__.py index 4d43f28812..0456cfd5ee 100644 --- a/server_addon/fusion/server/__init__.py +++ b/server_addon/fusion/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import FusionSettings, DEFAULT_VALUES class FusionAddon(BaseServerAddon): - name = "fusion" - title = "Fusion" - version = __version__ settings_model: Type[FusionSettings] = FusionSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py deleted file mode 100644 index 1276d0254f..0000000000 --- a/server_addon/fusion/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.5" From 78e1c08fac15da24f41d8109db52ebf6a13e5f10 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:52 +0200 Subject: [PATCH 350/423] flame uses package.py --- server_addon/flame/package.py | 3 +++ server_addon/flame/server/__init__.py | 6 ------ server_addon/flame/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/flame/package.py delete mode 100644 server_addon/flame/server/version.py diff --git a/server_addon/flame/package.py b/server_addon/flame/package.py new file mode 100644 index 0000000000..8c077ed91d --- /dev/null +++ b/server_addon/flame/package.py @@ -0,0 +1,3 @@ +name = "flame" +title = "Flame" +version = "0.1.0" diff --git a/server_addon/flame/server/__init__.py b/server_addon/flame/server/__init__.py index 7d5eb3960f..4aa46617ee 100644 --- a/server_addon/flame/server/__init__.py +++ b/server_addon/flame/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import FlameSettings, DEFAULT_VALUES class FlameAddon(BaseServerAddon): - name = "flame" - title = "Flame" - version = __version__ settings_model: Type[FlameSettings] = FlameSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/flame/server/version.py b/server_addon/flame/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/flame/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 286156d1ff0ec5f47600b4040442576130d09aef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:01 +0200 Subject: [PATCH 351/423] deadline uses package.py --- server_addon/deadline/package.py | 3 +++ server_addon/deadline/server/__init__.py | 4 ---- server_addon/deadline/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/deadline/package.py delete mode 100644 server_addon/deadline/server/version.py diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py new file mode 100644 index 0000000000..944797fea6 --- /dev/null +++ b/server_addon/deadline/package.py @@ -0,0 +1,3 @@ +name = "deadline" +title = "Deadline" +version = "0.1.10" diff --git a/server_addon/deadline/server/__init__.py b/server_addon/deadline/server/__init__.py index 36d04189a9..e7dcb7d347 100644 --- a/server_addon/deadline/server/__init__.py +++ b/server_addon/deadline/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import DeadlineSettings, DEFAULT_VALUES class Deadline(BaseServerAddon): - name = "deadline" - title = "Deadline" - version = __version__ settings_model: Type[DeadlineSettings] = DeadlineSettings async def get_default_settings(self): diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py deleted file mode 100644 index 569b1212f7..0000000000 --- a/server_addon/deadline/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.10" From 35c4176c3d9b23a430e0182e04bc5be1c0feadd8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:13 +0200 Subject: [PATCH 352/423] clockify uses package.py --- server_addon/clockify/package.py | 3 +++ server_addon/clockify/server/__init__.py | 6 ------ server_addon/clockify/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/clockify/package.py delete mode 100644 server_addon/clockify/server/version.py diff --git a/server_addon/clockify/package.py b/server_addon/clockify/package.py new file mode 100644 index 0000000000..bcf9425b3f --- /dev/null +++ b/server_addon/clockify/package.py @@ -0,0 +1,3 @@ +name = "clockify" +title = "Clockify" +version = "0.1.1" diff --git a/server_addon/clockify/server/__init__.py b/server_addon/clockify/server/__init__.py index 0fa453fdf4..11bbfed261 100644 --- a/server_addon/clockify/server/__init__.py +++ b/server_addon/clockify/server/__init__.py @@ -2,14 +2,8 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import ClockifySettings class ClockifyAddon(BaseServerAddon): - name = "clockify" - title = "Clockify" - version = __version__ settings_model: Type[ClockifySettings] = ClockifySettings - frontend_scopes = {} - services = {} diff --git a/server_addon/clockify/server/version.py b/server_addon/clockify/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/clockify/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From 2b957195198ebf2ccef6747b43e0af2fbcbffd0d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:22 +0200 Subject: [PATCH 353/423] celaction uses package.py --- server_addon/celaction/package.py | 3 +++ server_addon/celaction/server/__init__.py | 6 ------ server_addon/celaction/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/celaction/package.py delete mode 100644 server_addon/celaction/server/version.py diff --git a/server_addon/celaction/package.py b/server_addon/celaction/package.py new file mode 100644 index 0000000000..2b11a8630f --- /dev/null +++ b/server_addon/celaction/package.py @@ -0,0 +1,3 @@ +name = "celaction" +title = "CelAction" +version = "0.1.0" diff --git a/server_addon/celaction/server/__init__.py b/server_addon/celaction/server/__init__.py index 90d3dbaa01..e3769a4b7f 100644 --- a/server_addon/celaction/server/__init__.py +++ b/server_addon/celaction/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import CelActionSettings, DEFAULT_VALUES class CelActionAddon(BaseServerAddon): - name = "celaction" - title = "CelAction" - version = __version__ settings_model: Type[CelActionSettings] = CelActionSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/celaction/server/version.py b/server_addon/celaction/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/celaction/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From ce42a71555a1d3f3a399a668f9b4bb7eeb489699 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:32 +0200 Subject: [PATCH 354/423] blender uses package.py --- server_addon/blender/package.py | 3 +++ server_addon/blender/server/__init__.py | 6 ------ server_addon/blender/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/blender/package.py delete mode 100644 server_addon/blender/server/version.py diff --git a/server_addon/blender/package.py b/server_addon/blender/package.py new file mode 100644 index 0000000000..667076e533 --- /dev/null +++ b/server_addon/blender/package.py @@ -0,0 +1,3 @@ +name = "blender" +title = "Blender" +version = "0.1.8" diff --git a/server_addon/blender/server/__init__.py b/server_addon/blender/server/__init__.py index a7d6cb4400..b274e3bc29 100644 --- a/server_addon/blender/server/__init__.py +++ b/server_addon/blender/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import BlenderSettings, DEFAULT_VALUES class BlenderAddon(BaseServerAddon): - name = "blender" - title = "Blender" - version = __version__ settings_model: Type[BlenderSettings] = BlenderSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py deleted file mode 100644 index 9cb17e7976..0000000000 --- a/server_addon/blender/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.8" From acaafa9b66b16fe243ee8e49f8afe1eacc9d8221 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:42 +0200 Subject: [PATCH 355/423] aftereffects uses package.py --- server_addon/aftereffects/package.py | 3 +++ server_addon/aftereffects/server/__init__.py | 5 ----- server_addon/aftereffects/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/aftereffects/package.py delete mode 100644 server_addon/aftereffects/server/version.py diff --git a/server_addon/aftereffects/package.py b/server_addon/aftereffects/package.py new file mode 100644 index 0000000000..a680b37602 --- /dev/null +++ b/server_addon/aftereffects/package.py @@ -0,0 +1,3 @@ +name = "aftereffects" +title = "AfterEffects" +version = "0.1.3" diff --git a/server_addon/aftereffects/server/__init__.py b/server_addon/aftereffects/server/__init__.py index e14e76e9db..76e6d5b2eb 100644 --- a/server_addon/aftereffects/server/__init__.py +++ b/server_addon/aftereffects/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import AfterEffectsSettings, DEFAULT_AFTEREFFECTS_SETTING -from .version import __version__ class AfterEffects(BaseServerAddon): - name = "aftereffects" - title = "AfterEffects" - version = __version__ - settings_model = AfterEffectsSettings async def get_default_settings(self): diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py deleted file mode 100644 index e57ad00718..0000000000 --- a/server_addon/aftereffects/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.3" From bc9a0dc59801b1a86b48d2b00fc7419e9ed548f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:51 +0200 Subject: [PATCH 356/423] royal render uses package.py --- server_addon/royal_render/package.py | 3 +++ server_addon/royal_render/server/__init__.py | 4 ---- server_addon/royal_render/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/royal_render/package.py delete mode 100644 server_addon/royal_render/server/version.py diff --git a/server_addon/royal_render/package.py b/server_addon/royal_render/package.py new file mode 100644 index 0000000000..1fdea4abbb --- /dev/null +++ b/server_addon/royal_render/package.py @@ -0,0 +1,3 @@ +name = "royalrender" +title = "Royal Render" +version = "0.1.1" diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royal_render/server/__init__.py index c5f0aafa00..5b10678136 100644 --- a/server_addon/royal_render/server/__init__.py +++ b/server_addon/royal_render/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import RoyalRenderSettings, DEFAULT_VALUES class RoyalRenderAddon(BaseServerAddon): - name = "royalrender" - version = __version__ - title = "Royal Render" settings_model: Type[RoyalRenderSettings] = RoyalRenderSettings async def get_default_settings(self): diff --git a/server_addon/royal_render/server/version.py b/server_addon/royal_render/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/royal_render/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From e669563a2ef62d71ece87d8d2640c710406de047 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:28:11 +0200 Subject: [PATCH 357/423] renamed royal_render folder to royalrender --- server_addon/{royal_render => royalrender}/package.py | 0 server_addon/{royal_render => royalrender}/server/__init__.py | 0 server_addon/{royal_render => royalrender}/server/settings.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename server_addon/{royal_render => royalrender}/package.py (100%) rename server_addon/{royal_render => royalrender}/server/__init__.py (100%) rename server_addon/{royal_render => royalrender}/server/settings.py (100%) diff --git a/server_addon/royal_render/package.py b/server_addon/royalrender/package.py similarity index 100% rename from server_addon/royal_render/package.py rename to server_addon/royalrender/package.py diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royalrender/server/__init__.py similarity index 100% rename from server_addon/royal_render/server/__init__.py rename to server_addon/royalrender/server/__init__.py diff --git a/server_addon/royal_render/server/settings.py b/server_addon/royalrender/server/settings.py similarity index 100% rename from server_addon/royal_render/server/settings.py rename to server_addon/royalrender/server/settings.py From 233794df9752b09e5f1823d91078354db5391263 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:29:52 +0200 Subject: [PATCH 358/423] simplified package creation --- server_addon/create_ayon_addons.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index bfd601af07..79b9aa5450 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -245,12 +245,8 @@ def create_addon_package( keep_source: bool, ): src_package_py = addon_dir / "package.py" - package = None - if src_package_py.exists(): - package = import_filepath(src_package_py) - addon_version = package.version - else: - addon_version = get_addon_version(addon_dir) + package = import_filepath(src_package_py) + addon_version = package.version addon_output_dir = output_dir / addon_dir.name / addon_version if addon_output_dir.exists(): @@ -259,18 +255,7 @@ def create_addon_package( # Copy server content dst_package_py = addon_output_dir / "package.py" - if package is not None: - shutil.copy(src_package_py, dst_package_py) - else: - addon_name = addon_dir.name - if addon_name == "royal_render": - addon_name = "royalrender" - package_py_content = PACKAGE_PY_TEMPLATE.format( - addon_name=addon_name, addon_version=addon_version - ) - - with open(dst_package_py, "w+") as pkg_py: - pkg_py.write(package_py_content) + shutil.copy(src_package_py, dst_package_py) server_dir = addon_dir / "server" shutil.copytree( From 6f1228ad42779bd654716a1cf4d6bac52a26dea0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Apr 2024 15:01:08 +0200 Subject: [PATCH 359/423] Fix #397: Always refresh workfiles tool on show Note: this also refreshes when the window is minimized and then brought up again --- .../ayon_core/tools/workfiles/widgets/window.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 8a2617d270..1cfae7ec90 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -118,11 +118,11 @@ class WorkfilesToolWindow(QtWidgets.QWidget): overlay_invalid_host = InvalidHostOverlay(self) overlay_invalid_host.setVisible(False) - first_show_timer = QtCore.QTimer() - first_show_timer.setSingleShot(True) - first_show_timer.setInterval(50) + show_timer = QtCore.QTimer() + show_timer.setSingleShot(True) + show_timer.setInterval(50) - first_show_timer.timeout.connect(self._on_first_show) + show_timer.timeout.connect(self._on_show) controller.register_event_callback( "save_as.finished", @@ -159,7 +159,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._tasks_widget = tasks_widget self._side_panel = side_panel - self._first_show_timer = first_show_timer + self._show_timer = show_timer self._post_init() @@ -287,9 +287,9 @@ class WorkfilesToolWindow(QtWidgets.QWidget): def showEvent(self, event): super(WorkfilesToolWindow, self).showEvent(event) + self._show_timer.start() if self._first_show: self._first_show = False - self._first_show_timer.start() self.setStyleSheet(style.load_stylesheet()) def keyPressEvent(self, event): @@ -303,9 +303,8 @@ class WorkfilesToolWindow(QtWidgets.QWidget): pass - def _on_first_show(self): - if not self._controller_refreshed: - self.refresh() + def _on_show(self): + self.refresh() def _on_file_text_filter_change(self, text): self._files_widget.set_text_filter(text) From 17390f839a2ce4a72cb347966f7174aab2b10424 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:26:09 +0200 Subject: [PATCH 360/423] select latest workfile after model refresh --- .../widgets/files_widget_workarea.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 6a1572deb2..47b04d36fe 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -20,6 +20,8 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): controller (AbstractWorkfilesFrontend): The control object. """ + refreshed = QtCore.Signal() + def __init__(self, controller): super(WorkAreaFilesModel, self).__init__() @@ -163,6 +165,12 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): self._fill_items() def _fill_items(self): + try: + self._fill_items_impl() + finally: + self.refreshed.emit() + + def _fill_items_impl(self): folder_id = self._selected_folder_id task_id = self._selected_task_id if not folder_id or not task_id: @@ -285,6 +293,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): selection_model.selectionChanged.connect(self._on_selection_change) view.double_clicked.connect(self._on_mouse_double_click) view.customContextMenuRequested.connect(self._on_context_menu) + model.refreshed.connect(self._on_model_refresh) controller.register_event_callback( "expected_selection_changed", @@ -298,6 +307,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._controller = controller self._published_mode = False + self._change_selection_on_refresh = True def set_published_mode(self, published_mode): """Set the published mode. @@ -379,7 +389,9 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): if not workfile_info["current"]: return + self._change_selection_on_refresh = False self._model.refresh() + self._change_selection_on_refresh = True workfile_name = workfile_info["name"] if ( @@ -394,3 +406,24 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._controller.expected_workfile_selected( event["folder"]["id"], event["task"]["name"], workfile_name ) + + def _on_model_refresh(self): + if ( + not self._change_selection_on_refresh + or self._proxy_model.rowCount() < 1 + ): + return + + first_index = self._proxy_model.index(0, 0) + last_index = self._proxy_model.index( + 0, self._proxy_model.columnCount() - 1 + ) + selection = QtCore.QItemSelection(first_index, last_index) + seleciton_model = self._view.selectionModel() + seleciton_model.select( + selection, + ( + QtCore.QItemSelectionModel.ClearAndSelect + | QtCore.QItemSelectionModel.Current + ) + ) From 7cd9ec8a8dedf9a508b109df783ebaccb6f16a05 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Apr 2024 00:30:46 +0200 Subject: [PATCH 361/423] Select index by latest date modified --- .../workfiles/widgets/files_widget_workarea.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 47b04d36fe..39abbfe739 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -414,16 +414,20 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): ): return - first_index = self._proxy_model.index(0, 0) - last_index = self._proxy_model.index( - 0, self._proxy_model.columnCount() - 1 + # Find the row with latest date modified + latest_index = max( + (self._proxy_model.index(i, 0) for + i in range(self._proxy_model.rowCount())), + key=lambda model_index: model_index.date(DATE_MODIFIED_ROLE) ) - selection = QtCore.QItemSelection(first_index, last_index) - seleciton_model = self._view.selectionModel() - seleciton_model.select( - selection, + + # Select row of latest modified + selection_model = self._view.selectionModel() + selection_model.select( + latest_index, ( QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.Current + | QtCore.QItemSelectionModel.Rows ) ) From f6411be0c26623502efe1e59ba66c2b894c27d71 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Apr 2024 01:36:42 +0200 Subject: [PATCH 362/423] Update client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../tools/workfiles/widgets/files_widget_workarea.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 39abbfe739..fe6abee951 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -416,9 +416,11 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): # Find the row with latest date modified latest_index = max( - (self._proxy_model.index(i, 0) for - i in range(self._proxy_model.rowCount())), - key=lambda model_index: model_index.date(DATE_MODIFIED_ROLE) + ( + self._proxy_model.index(idx, 0) + for idx in range(self._proxy_model.rowCount()) + ), + key=lambda model_index: model_index.data(DATE_MODIFIED_ROLE) ) # Select row of latest modified From 6b2b28d7d235c1da4d684e638c863305ed3b2d35 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Apr 2024 14:57:58 +0800 Subject: [PATCH 363/423] make sure the collect render layer is just collecting beauty render with the global aov mode disabled in Arnold renderer --- .../ayon_core/hosts/maya/api/lib_renderproducts.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 832d1c21c2..b949845f1d 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -588,6 +588,20 @@ class RenderProductsArnold(ARenderProducts): "Unrecognized arnold driver format " "for AOV - {}".format(aov_name) ) + global_aov_enabled = bool( + self._get_attr( + "defaultArnoldRenderOptions.aovMode", as_string=False) + ) + if not global_aov_enabled: + for camera in cameras: + products.insert(0, + RenderProduct(productName="", + ext=ext, + driver=ai_driver, + multipart=self.multipart, + camera=camera, + colorspace=colorspace)) + return products # If aov RGBA is selected, arnold will translate it to `beauty` name = aov_name From ba1242316e228ffe98d022fc27ad81202469c3b4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Apr 2024 15:12:04 +0800 Subject: [PATCH 364/423] add asstring argment in the existing aovs_enabled --- .../hosts/maya/api/lib_renderproducts.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index b949845f1d..52c282c6de 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -588,20 +588,6 @@ class RenderProductsArnold(ARenderProducts): "Unrecognized arnold driver format " "for AOV - {}".format(aov_name) ) - global_aov_enabled = bool( - self._get_attr( - "defaultArnoldRenderOptions.aovMode", as_string=False) - ) - if not global_aov_enabled: - for camera in cameras: - products.insert(0, - RenderProduct(productName="", - ext=ext, - driver=ai_driver, - multipart=self.multipart, - camera=camera, - colorspace=colorspace)) - return products # If aov RGBA is selected, arnold will translate it to `beauty` name = aov_name @@ -734,7 +720,8 @@ class RenderProductsArnold(ARenderProducts): # AOVs > Legacy > Maya Render View > Mode aovs_enabled = bool( - self._get_attr("defaultArnoldRenderOptions.aovMode") + self._get_attr( + "defaultArnoldRenderOptions.aovMode", as_string=False) ) if not aovs_enabled: return beauty_products From 7ef499e5a56c670faf3f9d7857089f5a2d81c207 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:09:18 +0200 Subject: [PATCH 365/423] change substance painter version --- server_addon/substancepainter/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/substancepainter/package.py b/server_addon/substancepainter/package.py index a064d80fd7..d445b0059f 100644 --- a/server_addon/substancepainter/package.py +++ b/server_addon/substancepainter/package.py @@ -1,3 +1,3 @@ name = "substancepainter" title = "Substance Painter" -version = "0.1.0" +version = "0.1.1" From e38ef81bf21ca716f0b4c666145b7134fad9e830 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 25 Apr 2024 11:00:36 +0200 Subject: [PATCH 366/423] add WorkfileImageIO settings --- server_addon/houdini/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py index 4b72af2a89..4e441c76ae 100644 --- a/server_addon/houdini/package.py +++ b/server_addon/houdini/package.py @@ -1,3 +1,3 @@ name = "houdini" title = "Houdini" -version = "0.2.12" +version = "0.2.13" From 129070aefec7a6b855f80b95933845298fb2465e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Apr 2024 17:53:29 +0800 Subject: [PATCH 367/423] make sure dict for Alembic Extractors having the attributes for enabling it --- .../hosts/max/plugins/publish/extract_alembic.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py b/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py index 67b5174200..67cec23ecc 100644 --- a/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py +++ b/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py @@ -53,6 +53,7 @@ class ExtractAlembic(publish.Extractor, hosts = ["max"] families = ["pointcache"] optional = True + active = True def process(self, instance): if not self.is_active(instance.data): @@ -102,24 +103,27 @@ class ExtractAlembic(publish.Extractor, @classmethod def get_attribute_defs(cls): - return [ + defs = super(ExtractAlembic, cls).get_attribute_defs() + defs.extend([ BoolDef("custom_attrs", label="Custom Attributes", default=False), - ] + ]) + return defs class ExtractCameraAlembic(ExtractAlembic): """Extract Camera with AlembicExport.""" - label = "Extract Alembic Camera" families = ["camera"] + optional = True -class ExtractModel(ExtractAlembic): +class ExtractModelAlembic(ExtractAlembic): """Extract Geometry in Alembic Format""" label = "Extract Geometry (Alembic)" families = ["model"] + optional = True def _set_abc_attributes(self, instance): attr_values = self.get_attr_values_from_data(instance.data) 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 368/423] :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 369/423] :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 8c2bc797e654873e50964e25a23bb2a1b85b22d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:38:57 +0200 Subject: [PATCH 370/423] fix import in create ayon addons --- server_addon/create_ayon_addons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index 79b9aa5450..f0a36d4740 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -5,7 +5,7 @@ import shutil import argparse import zipfile import types -import importlib +import importlib.machinery import platform import collections from pathlib import Path From 561021195d2e686a7a1f9667ce0244a0d5a9b969 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 Apr 2024 18:31:58 +0800 Subject: [PATCH 371/423] 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 372/423] 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 373/423] 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 374/423] 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 375/423] 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 376/423] 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 377/423] 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 378/423] 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 379/423] 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 380/423] 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 381/423] 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 382/423] 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 383/423] 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 384/423] 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 385/423] 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 386/423] 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 387/423] 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 388/423] 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 389/423] 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 390/423] 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 391/423] 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 392/423] 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 393/423] 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 394/423] 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 395/423] 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 396/423] 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 397/423] 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 398/423] 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 399/423] 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 400/423] 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 401/423] 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 402/423] 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 403/423] 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 404/423] 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 2d0c1062387aec013de1ca7d0e34747e67c90f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:55:12 +0200 Subject: [PATCH 405/423] :art: add 3dequalizer support to applications addon --- client/ayon_core/resources/app_icons/3de4.png | Bin 0 -> 15879 bytes server_addon/applications/package.py | 2 +- .../applications/server/applications.json | 22 ++++++++++++++++++ server_addon/applications/server/settings.py | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/resources/app_icons/3de4.png diff --git a/client/ayon_core/resources/app_icons/3de4.png b/client/ayon_core/resources/app_icons/3de4.png new file mode 100644 index 0000000000000000000000000000000000000000..bd0fe40d37a3ca96444f37644de01fc45ab22106 GIT binary patch literal 15879 zcmbum2{_bm-#$EvC_=JKAwtMHcE*<7$dF|$F=;TEEMpn_)?&$;B}Adns3Ag;y%Lg8 zCVRGo#+rTSz4W{9-~BxQ`+nZz|GrNL9p-yo-)p}<%Xyv?bHzySZ z1ad^|@R#lwcoGe7rbERX$5OBn;`Pg>}K{2YF-7f{e@^f?OSx9QoAMPN)RHK?Cks0){uh z-Oa-X9-zwir(HPs`|z-Cyf>*`c(Fty2T80O)*b6X@R5{~kdpkb z^|1lCe~re&=O4=j7Ebc81XPFpx9XlQcmm$X1@HNHFaPbGzl;CvMFP(0f9U71Q)MLb|Gm1q`+u8;4?)KdEZjdf;lEAcpB?y^2YO;9O|d?BUvCGjjvrV( z{yz)n=m2-Zd%I)6`#5)uGgi{m!}(9a;ex@n@NRf-GjPVRs(e!adVB>JfOWId#(_=n z0lTNlCj*21AEnm+q4e+d{Ht9g-Vrn_^XJ8X6(O{=u6W~}aBiT?$5c;~7onpiFQp_e zFD@+m`tRdFJnc(uT?*CO{iF5q70%LC+m^S9{y!~@Fe_l9T6*u3*$0!dR zSO@7pBl=Th^{+BjJ_TvX|1tpU|5yZe8m{Gy#SpOC;1_hG1d|nqNr4}8m=qia13#jF z{`k|!-?j1H=6JlDDxb#T_2#|u&tu7}rNt`^lak{VMq(Ur9*1Z3;@>YK!A*6<5HNpr zBn^|65r-*>OG%qc$--q|a5;HVm^A3_U-j{hIH$n>%i2gJTpRD;dw9zb+NykDpc0Na zN4Ts5#vZGvASW)PgvE->%1gn-m88J+cEZ45PKt`)v#i`dI|NN2JbVZk4+rc&o51~l zZvt!@uxuE&|KFoIG&j5|U;uQV^Xj{mrjU@(8Z^4DPg=cD+K!Kg?c zn)`n)y^7?&ZO&g6{)>MAllbcy@H@bPN&fA@z=ywmAl3soK5yXRhT8(BfET@m1E%EP zOb7%boa~$2C8}-0+NypmfrtMDRw^#zrDl9qzg7IMh**nRn7dO>pv;Z1x)Dx9VGI9WQl7G*PA!}-2J%NAmED3=i=WFet)>p>ltA4 z260(CJ;FFr8F}%_X7AU0iAF^(|E+e6*-55bg&I~d9A2;PzK+ObNzWjs85b<8IiQ!a1#*u+Lgj0`gjPBoE+)Bvh<1fpaJLdX8?6ih7c50dSk+otRFjjvJ9Og&yYdxQOZ&g7y}1FbK*VdjM2 z#$&DBS&nds!CU8n@z2jkK&W!uQ{UPL0--;B_G2uHO(^sT1DfKg!#on;) zIF-UXZB{A`mJOIl95hmM~sEu2M?Z^mwmq+{C|n&`|?C>n0YXfqSm zjj}GKY@<2v>Uc-#%y-a=ch|4{2+Xm2eFOEAqj59RupBhO;=9tJP|L)lwmV6H*I!L3 zU?j`R`9!eloG}z&G4}WM9o}r>+5e(dlL)!V_bOz|+fwCRqhU$`ibUEsL-j~B!M-K? zT`6>cMny?2q+qvD=~$kaW_ylQ4_e5jTQ}U@UpPXiyb&p3MZ%wwva`)9S#>DR*6SI! zd2K8G<_EpI-KinG#U&+)vAX&9GTFcX7+@$QQ(D<2uQ605LgM8^Lql^@uz?jsoERlX zm?H}Fl0<4?x!2lwmN9xfS{!0vEx@9bJH55$r=I$W7KO60O5bw|4&Jfqo<9M3X(f_& zRi`GkEQUQn_uvj;hYBK=WGGnr0a}%^fSQYvI>$_n!J3(o6}#OTAs@e{i`&$RweNS! zBC`q3DaJJ=LeQu!oWK7Db@vVgk`!54S$P(batz^rE&{g{up3>lK?50+O`!9zA~YZ7Cdtp35SdRAL`U9hL;%c#{d$fb7<&?%~JAu$*- zeu?(v$&(V@t#`P`(wVJVD1`J~nfvx*1bCNCg;R&k(|+8$J&BjvV1_zog+`$q?=r5x z`yxBW%05=9S%`Zl`4MpB=CfP3ZUs_z zDt+(nUS<D1>!;%4CfCyX zDTAk`9C!0J-w<*6Q>4o&1t%>k`5`n+4&PIvecInb75sdCdj`FMchw|R>d82D48 zgHB}_JiXqGgTaff%JK$fkxdJ!2Fww4_s$Q#cI^{Dq!@&q3qq0~MACDlf1g!`glO|6 zZ`KbB-4~7x?{5rOPfqg-3JMAVSJMLBm+#k?Du6-_UA3_}DliDWS=V3fQ#dM%?C3m= zNT1xu`|6TJK<{jC6Tt-XvVAXzXB$bLAMS+o3uYSj4h2$CYSi>=5`|{x=5|RKXn$wW z>4Mjlm7RRUEdw|Gk85;tF}89kw;LCd)ypb&#~;YM4?0C*)WqADYYQs+Z{|p24NUIc zq#e7jZL(W|wp+^+_TWc1k}0#Z^cQ2xdteMbCGxxGN-e zh(1ZqJmC>%OU@+1&yUH7f}wQ+IITYmii%tl+b2nt`EDkx{&OSs!HwKln~v#~J-&1I z?CvmkBb>`jk;smF`p%HI(%?SJD>n?BY)Tplm`KR8X?p`+k0Rn!{azhEA7p81sY>yq zdCL-oQE_dxzAZJPRpZh0I{aY&_|sJ*zLrgFw|kHXum#?ExIt~@Pife|RPM>CCK?V~ zSpU#a@N*JLPv5zW3Q1l82cb(S`{>P6WZ4%zGk5~Jui5fiAeMnrlkly^clLH{-dt5p zsD6^b^Xk{HU;lCzrpf1O>0jo!gF=Z978Rw2_TzMlJ@{FG;ijUjQ(y02sNWb#;ubpA z1heBuD~RgwB@fh;wEMk6sm0`Nicl&+6*wHuKIml+?#7vdStqb9m6(=Xt?zQsM z?d3YEd?PT1^;@*(ekX$p-rm#M{%}L`tKGfojB7y@+iu?qGFbEa6oL7dHN(p>*a7vV z!+Pa7vS2&Un{C3#8A1^HWdSSt0%bOl6=`m z&i{IODP1_WJS6H^6o&CC+UdNjtLu(&M3OG3^TxI$P+VM`Bc<~QXe+jBx}vV>1T|Ik z3yF%Pj%?+O*@cA?1qLm?Y^);DFw>ip!te!is~AKDUKSYx{r#IcU|coN%?i)8KshFh z^ktFJ2xkbdXcqBB)n>Gi92eugPm-VITzjeGR(rS|nq%224M*s1K6SLNBs>>QoXFDe zixG&zuv-u0Y^4|q9%z>j>rN_geat4}(mRjMECmD=i4) zdB2O4aw-m2@hAnxC-K6tAB3U3E^y^p$3ul^b5u54jJBF+`|?_h_K((@5_L(0=|f!B zb{lO7Uox-SjhZW-T>k2})SR_hVm@#S494=6nIze@wkUTzkZO_&+ufY#%A?kR?XQ)F zVDYaO;TUlnfDw-zTNBBG{6UNuLglx*zm zs|>5x2VK=|tgQnXyH*@v zEz_HO`!&nU%kYS#m8Ni`W^KBQBoYxCg_B3FxbQMVmxJo5H0d&lfucGl)0CP{L6#WH zy|D0%JmGA~y5H~B72Nv2)VlVSq+H#5K&`=iHAKzs>*teBSyQx((GGqMRrK5zrviQ^ z>xGOS>~E;YF=t)7b}j6@j>*I$qiO9X7!?uM3l1tT-z!NZ`I}UWS0OpG*H@L|sTif0 z=$en){ykgTxW52q6fE!Y-Rc#Y^z-uN%k71oV6Xg5k{_i;VSaO<2_|E2Zygn+h}}$@ zY|l`Z$^2{%%x)L#vwIX~*P6AtX{uG_s0e!&5lgKRpqgo@_Gx8zZ7HBGj7&tH?{-Ju zHAl@0)HiyUId|n$7Ys4{v{vLI0^_~fW-hX(jP9f9ro}XPa#cMd3~`)A_vH%J+j%D& z68U|R8g1_IV71k^g7#V6e^lpvXs-R7+u7#gbe z=zOF!Um!D(fF0lz6%ol1%I3EbYY&rxGBg;$X+LV%*bG6_v5kW5%h$R`11=bH=y>6Q ztGy?TB%mPk02UqK`K~2XcMsUO(h`1XCOpm_mqI%v1sDusuXpXx~ny6Xn zRL-7)jdTcHV2M$=RMYYH%CN@Vq)TDs#}**LCl{;U{4CaR=SZjI5@Zg z!rR)rg!I$%gRhIy2vM4{HRLgF zoE;x5bI{|VFbtTYeX^mTDme+!i48FWn70@Q%&X z)d{`j3zpVd^T`#5&`?z_<-(W28w>ecgBERz)F!M|7M7Ny;BpCHlSLW_E!J-&3Z}N5 z*&3XRj#FlBmYHg3XdpCHqRP1~#Mq0Hv4K@!rPPrb%5v z0`q%(!xr&DX+ql1v}mz*$v~ByaIgyZ1yLwFb)^7ymXd3Ar0<+X?Qjh z)><&PS0V1D1tl-D9zJXb#y0MY^$}%|PtwRZciP%d%L^bx((MFmm#fQCW?l*!K6oj{ zOcxSKV7#&o9M(A{@1Gx0era|n)Y_TaPn9WM)0cnx1YaOy5GkuJAbraqFv5Zib5sI- z6}c)Wxw?Jpc}H+6sR1hcr6C@rzm$uC!M)+>`8%j;sGd6QmyFVt2QFDTHSR-|C-HSl zQQVDFY&y|wbkxh+DSlsb$5I5)rWO|K@)WM?t3LwweL&h7Tr7eImh$vs!_Goj(WeSC z*|YTl-`{uz+Wg8FZV7y3U}tBSO{RRm1lN<2MxOTJ>I30QB0RfOTRL-2hwh}gxw+@H zQp;ok#JFOj4AQLl*|RHZ3!zdu)5B31#f!1aU|XXJ=nWXdX>Gb5fW07PkTM0rpP(7- zay=7E-}oX7yvwMvh&xD?WhewriPS-q}dvwp2FPH9dE3aD@lC4=Y zIt5&^(({CGTp_a4d-^e8XO<6FIdO)cB1|mMJYM@#xcU-yXZeBtj}GYIM*k zQTfvM!baf|T-$`1dOR@iawMy8&h$evWwlX#^^4ZT?CfmZLi)jgbFOOi^wtjWQp!Z! z*@7V%8|Xxb#W(ddgAO5dhFi0JnL_b1ABot7o4RyQ3&o_Qsek?UOwmzg|AqUET#89P!~{--?b0_tV&NI>sKVK8l`JC1{j{{u!8{Rb1M;gd zu;76OlfP9C3Pu+j+hYy=E(@^4-Eegc*MjS5CPOCE|6g1>hwY*7Q?c{r_Ur3hZh7*MGrI#ML z9R?tAv#Z5y)r{Qm)r*M}`xviE45E-+q-V;JGi{ktqE%^*x=>CsHc4m#xY0(ptZFfS ziqG-H?cegPUI+L?nBdc=Pohp;Il~B%&o=maEQP)#Ht)WTN$1=^#i-3Im#?T~{Q`+J zgW=m!=a%j)j)SmTAyiS^uybZ+=2>}pdFj>cHeH4#VJBgwKxsGi2= ziKTW#)mF^=aTqp>cmt(sogPX~d_>P7Q4kdsH6{>A#_%JJr1dSTFF}2j!!maH0 zvyK@LP__FZlNAO6ESdOLHPPdlhEvf_r}jQ%(>KA)0RY4q*F43r;m2o}_w|<_{jB@Y zH!-2O)`#DsMIAVB<@QeZy`_8MKYg}B4xPl6Q;(vBOi^o#1v0D!AksuseDI*r zG1>LCvl|uYc30+dcX#iz9$=TOU+pFo$*AqEl2KVibZhyXcl^>QLz80hEF6zrFl%#^ zwhrHN8z^rUkNwfU^${yIzqc`3S{c-cEqXk90rb9+$H$!zE{*O}2H*y}ai;WBm&WYg zQoF{LuX;SMi|fkErTr^&>-V>3DMckUpZt4JmMD~kMLNiUV!`Q>y3K1&mQ^Ls`*juI zw#_6mT)8yqQ0C@b2SXv^tIub*Tin!Z!9p$u){jUIWwtUhG`VHUI2)yWTc@v-0eD8P zve%Cfbx0eGkOuR${T79WrL-3|RWk(@A&<7VgG5+QR7-$4p3Nevdk2?;iG)S$I~RXR z=GTLjyYzx3X*e|D{t?X5NT2hXzif3(LTXBC6%&he=z4-wa@w9;aS_z~c<01sfDT<| zAKE|LdA9e3v#t57>S`qwpQ+E>TXa~dm3Oo}#UUUSkuIQOojdYa_*0xmRqeAvWqAgz zpMm>A6he?Y$T6>lvDJ=8aefC0BXT~?@|se9U0q!ic0hzI+uHZ-o1q*-Q{ik)7lmM< z#Ko9P#9brH?nz`6W#v6HL%Yvf+ImIqOlH{0Gi&OjdhWJQo^{m$xKpV=Jllp@^X~MP z-Ok>=j>Y}Uw~4qNv_xN|^Y(WDAP9xcnB{I~G>r?Vqr4KEpo>1W!yL(ePsO@l5t4ZzDx1J6axwlS>@Q0ASKAJ_Wi1vMSGt57y#b*Rx2WWXfaSNB9Gv0;?u%ebQlT$|6!M&(?%uE~9klr(TATW9UFrgkjSIAgRIR4hCMSgOe+@0F7dpT%t|=Uz$X zmD*HEJQFrhpcL~zeqmi9DA_o}o~AD#9k-fu<~Wkf=UqM>T2to@b;=|DWPl*6K5`hm zLF0ef3Z$Kek~jBxYDyF~*49F?AP&7Vu-^evlIJ^%jp6BC(>8O!G3uC@TWsLA1nAx` zairn0riw)}h!m2;!8HpDK^v5Z+H+D(V`Jk%wb|YQ7-?;Jxp(c*e5LPh#xO(E=4r4p zLg327S{Nw!RLpz5M6R|ijT<6qLH7y2WxmR~kC)XEacZuZuU$D;7S`C)A97?7v;CSM z{DoDu7x*OJ=cUVzln!>+NO(C6a0UNIM$TV{gUT!($U ziYg)Egc1o|LvttjLG~)&I2u0xd_Nop4H=&I;~4e|5&7!!>M?xecJ&3oMOA&3MSD#d zvooSl8vyt79SCc9{dx_c8Ozc0pB-tVo4i667Ft?b{OIG<)L7pxZWOVnyDFhj_W%wv zzZib7b8pWCWJc&0xT|&-eJubSyFaDSh-=r5({Tr3m649gHbUL@b0iVB=X^HUCl73S zk68B^Gvi0xs~~oyw?dVWNUeo37O2=3u}FO9y1vn`r_FOJ4%vk>A)N03oFmr+nsr?eaBmqN-7>eW5JR5J^t7 zt9I{y`L5iigDl0M1;Tl{mVixMFMXnytn&<*Xi2y#5gE~hnK2IECReq9JVyXY zP8Tz_c*d$Tz^Rjm}-?03kB@vkTZ!0AixZvH&h#3*GeBGAca1 z6QcHYV(A%)^hvD!1glQjtxK0Q=$_kDc_hpa*IHTudetbK9K$YQtADT&ez0Mh+tmjM zCTE7KNA~YwpN9)%9x*O4ib5H}azKiGChDs>2mzAYM?;oVb9ZadfksU*q**Jp@M7!_ zT$Tl3azWf(It!onRQ$knz-c7Fq6Zk7&Al*Uaj~I1((HSEus`~eD$4ppacIcwH6Y0@Fj zbMtc6l*DCcXDc<9T{AN?!|UakNij6JJ3FsExvw+%#n;!jBq!&b{T*FEN{94*h&kQ3 zxUtiyPhNeb`AEFUqo%|Mn7CJFKUq)w;2%EO^wM$N003R(Yd_*7J8{c&V@~rYIEtrN~&N`_S^TNPDWW+LH>bBUPGDyq+}>#6 z#DBg}-&EcV`@Gfk+(C?eNEcp<%K{8dc5I$oLbLrS*v;(^`s+3zLS~844kM9f&gz&{ zz7*R5IlA6wO#Jr3%uEeNz}o0(ZEdh0;7N-~Gj>uf;!&7qy$Y^gWLfu75~`^D90W~w zSFNcA#HsrUKi7R8s~_yHpqsX|s)qIiHSew}%ES^W&jBd$;>C-4;GNhn#&$boO>u&B z=VH4z@Uv$^e#8hJ%MF^jIAItGlCT9j&0iTp2OCU!O=Q4~;F{yl$b14QJyZoW47UG< zrcn6$*tB@up?|wYr1wQXLWunEAUzsicggBj`pFdT-IsQ@(0I!ITvH|7TT0pGS0 zZ|Pjf!WjMBIONA^tiou>%0Mni`J6ALw%q`nbV=9r!w9JoLba`fFPaG;YcbFC1=Yl~ z!F~XSKiF7x?OQJ3sO9JObWW5p&MJ>?C9Y&T=Omk);tDZVS^Dtd!sT?w`j;Qt4jDs$ z{xp9wq|qnKCS(ZU=+3ixRon6*$s{txTsU_7W6YD;8k;`fsyyLIx+h7XV*~Q4%b5a1 z>~rBy;pu5ekQHNst|7U`1c{H^UeriIj4g@a1oZ3Ee$T1x7*5quKyn z>j4p^6ordl!;PyK#KBw1v}KX{eQO`}+uuy4+l;X5cs*nbgg_aiFge^@T)Rx&O1iQA zZ0W#x@kLzKcUQL|;^dXN5?4BR+sGj8_9-eV+IVcT{)v{?80ZJKLK-5S_Z`Ri{UR?Q zOikiNM>q3~LO?weOa%Ia9IQ@#n23ynu5W9`3+L+N8_yH+;R?42W--!T?_ z>3dQ^2C(PZ=%^?U7=y#N$|EmxFi=}D_S)C=_4Uu#XF6!gY&1<+orCK~HoA5Z<-qBH zh|#IN_9pPt&XbdqOaSw=uO6hP0jPwU26GtnMC3Jr>}I3iU-mw`amMg49J4GieXcww z!V}L}Q$nAt|5N7ai1m5xmrdXt!k@cOe{^;pAF9G1H~^{5Cm2IZ=lsoXf%fIWJG4xR zxhjFncWD(ljzSfK01?Rb7`IJTTa^l=Bv62R&Jy5-1NGupb_wV%5E(O$6zHFoO z2KH#Gt*;VjKOO|lm9;uKfhG;fAGQW1gwFwbRpo3#m>Zq@Uhg{J+1tM7~sd|`K$Nb050pQ z@2vP0$Z_OBAZ|RNxZsCE9_ z10UwlUcSiB#u&|gc(SsHa;YBtNu2RZH+ktq0ix29tdvS%tjOko9-B@k zIEuX*@K(yJSFb{F0QsLqOIT`@T%=1RgY;={qV?XvNBdV}t9KiBi*bW@E%rM&(y>Kg ze5oLZZ`cN$lQ9s>(3guHoFSH86eE$&IYFm5fnPuUxI)Am=D}-wr5K5C`t|h zv^sG`r7?k^oXDpL__@I^U#|SVr+kSKm_2R^zbMc?cnA$!@W1vP*SvB4`gOimFH2B= zL>8Gqs7+@<_hHkqx$M-KcT*h?MfDf)*Y5xsi#VXv0KY;R!Z)femj3F04%l0^d#!j2 z)EW(ve08i+%DIL_%FZIX-_y;ndf)R+_;;@jKHGTvS$l8oVuSKSLht|%;KZmHZTn1I z77L|P!t=rOmiht^o_wI6yUkYtQt(0yRGPvM<7{&*7qH}08h9T`&%G27a<&O(3AmPM z9h2{_SyRvIx8Fnce9;H=tzM&dTPN0-R9CN@p@dS?u~1`ZDR-m~9qXl<7y8yUgy%9W zF`t2gWWyo8>H%>}twqr(i>MLQXay`aaOS5_DF5fAZ8tIYoslP6(M~;meNCmoW8uKx z=plHD2fq03K`50Xvg&&j<2?XpC>#cID=MRiAf8G6)70azCfGe5MbEMFhxmpw7Lp>K zG)o=t02>GJ9y0=hkEf?O9IFlik)QdH}#Myz$+qtN#)&GmB0c3H*~!ZTt3S zc!2`mC>KD;(LIR)_RWl0(|A6=$uq4#y@Ba=^K~xQi15(g0Hg6l4e)3X)%GAo1 z^ziU-8}gz2g>zw>z=GOnpU7(haGwvjJ#qGa9e`0PHOf`nD=qLzT3;m_TM~SIkKa>W z?|%tj^i(uFwk%iwqs>VDOM~Jwf*J)>tyhMhE z);GZz(pJUo0TA$grL4Oh-!;67lt1+K%i})0`{hml9J_nASa!Tiv z(*=k&t9i6<+l7n|G#V}fU>K}A)m?zPmjkY>D(IEpy9vckyl*C;;)iCWH7FOKG18xp zaI_xLhig>=CBddo?#_3b0-v8YNMyofQQh0!6<$u7J`CWg@~nz}L#u{D6(|f_Enc=? zqUxY--D$}0zCEJ`e&Q5mI3zQBJn{HP;^hX8X>@u1QzF)@yTMZP+CX*d=9IS%N5qYb%dLnl#hl zffJr}%}5{l$QkdIdUEuKUpEMyI?oS#`0Y@P!>@w->ft*fZr4yZ#zCSPyMLH-_XtG} zOJ^FkH8OF;Xip7&Aq)a^$KZwz-}QU9>n>>1JprOR^~Zc?w1W(Jnct|TwuxQHZZeX{ zm<=gvzmHWackPpS5Ww`vDg~@wGKt zP#8%w1ooD>Wz)Yv=I5tz&j=#&VPz>$dr?uu{2mL>oVLiIZ0zJ)WE?jz)9|;-G<4#i zFf{#q1jD@Nv3zkA6+7G4V~xHJxc#?>6Ric850Ub!FDkk&@QLszc(=RDQ-CUTO1oP{ zCrxje$Ous1f3r5**Eism@<*V-2Wq5&dqk@|C=7&Xx-}jUt78tk4ctNoIZ&?F&;$Z! z;vIo{KkMpV&UrD+Qf!FOZ3cOs)m>wc3|SrZP&itW0<10`N-3 z@BZ+>z;UKw4T@E@H?|s=mBl@}u$70q8Z=zv>!4pX=?TcETDKc#xfrAAoz?^)Vjyxi zsb}8vl0{wyjTxtSw2xmwy>3pFM?5v{N-g0OKBwb#`7X02kS<3OqLTA)aC0=ezP=y+ z0$lt+umQFYW5b(?-bZ-j0OmQjnck&1NY>azd%wm-?+ASF5U1 zA42;s*bOr<8k*OR&?Z>bfHf`(({LFGv->5^-7^MQbtOK`Q~(iFU07Ji>lFBFe*OhO z?D|?}&H@gtm>+EnSM=Dkd z_;mVwjaZveAd1n}y$KldTxaMpS7&a(02{FQ9=-VhXgshNN6wz(M|8i_=95y-EiK^! zY_Av!W#-qHFuhf@j1q#JIM!ZXG_O{zKcOfg*+`U=m3;$i;rW?w#L#5ep_dI276y3H z>A9@+f~4#56DnN?HLqX$mUGX)0f`ql8g+&MF0$W=WfP5^dJTh!^#ED(a}3l}%}BnR zfCiR601jwKb;7SJllns?VA)hs+rFoC8HEGk@2_dvqt~^mHTsiEPnX=H#ZGw|;A_O{3&b8cKUgf@oL9|yTx6lRq# z!ZZq_J?x>-3j2IBPZ$?*l9Bi9_E{;$eMMq`3&N@`+oMlHq|>V(oaK z9o}a$V{H~M(7O4Q>CBqwh+_LU5SmI^>JH9EIwVlTH{!*Hh>JT0M%)a){4mHVNQem%e(HN3DCP$CFM z-k4JV9!><9#8U$NB!AKI*wzWAi~v?Pb%m z`IpR^zaIH?;^u$-dH~e-$^{koPc*?;)3F%n(U;B77C^bn_9|ts)_-wd!8+ZJH~VRS zCb4Z}eZ5m1sCLHZsUUN`;Hc)}vUJs)$HXq=39A_f@le4?ARj6^TUzz7m_N zPMx5GBe0p(jg8zpQXaAlT0+C8SdUt#Zvg28^+*qOCFkp=v*kd4#&bMU^FRZxcixN@ z8*Xhq5D8>WUoqd*3F_H^w4(*uosf7hPLSs4rxc(;r~&U*fd3KRF~t;h0hw`ixR6ool4F;NOJ!6Bo2{M337E;aJcfBzOzhS2eX_9 zs*x!xki{0@&T7G#08Q((1VCmyU-nT*(jDNAE!r8tzw?lrWy-fm}JUy|Ysfyn=Q5B)UMELlh`M1Y5m0@PMQ-|7=MA z_L&S2^soU@iUPwO{^4hxqYQtZ?K(uTi5NCFfdLfa>3~)Gvq*^XMgkKZ~$kgSh zrg4YT%*!>Mka+4{b2Kl=MD$(&#k%*x_v(pm3IUkSwKoJfw={!;8xUj@B9biod_neL zghK6|yBA$3Y*2{1D#LKc_^BzHF`ECXwRIan1Rel&47X~KY&G~^%;v;Bbw=`~qyNkA e3H{^synS`uI{U`#L`L}G6NHYDc8TWo+y4)G9FKhf literal 0 HcmV?d00001 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" diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index e4b72fdff9..48a8a66161 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1271,6 +1271,28 @@ } ] }, + "equalizer": { + "enabled": true, + "label": "3DEqualizer", + "icon": "{}/app_icons/zbrush.png", + "host_name": "equalizer", + "environment": "{}", + "variants": [ + { + "name": "7-1v2", + "label": "7.1v2", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\3DE4_win64_r7.1v2\\bin\\3DE4.exe" + ], + "darwin": [], + "linux": [] + }, + + } + ] + }, "additional_apps": [] } } diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 5743e9f471..b77686cee0 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -190,6 +190,8 @@ class ApplicationsSettings(BaseSettingsModel): default_factory=AppGroupWithPython, title="OpenRV") zbrush: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Zbrush") + equalizer: AppGroup = SettingsField( + default_factory=AppGroupWithPython, title="3DEqualizer") additional_apps: list[AdditionalAppGroup] = SettingsField( default_factory=list, title="Additional Applications") From 678296c1dbab9b84f421504d3cb95bcab7b2bf9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:56:57 +0200 Subject: [PATCH 406/423] :bug: fix icon --- server_addon/applications/server/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 48a8a66161..ae3e82794d 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1274,7 +1274,7 @@ "equalizer": { "enabled": true, "label": "3DEqualizer", - "icon": "{}/app_icons/zbrush.png", + "icon": "{}/app_icons/3de4.png", "host_name": "equalizer", "environment": "{}", "variants": [ From 8e2f3235c6ab6704e744ffe2cd977b22940f24b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Apr 2024 12:58:08 +0200 Subject: [PATCH 407/423] Remove unused import --- 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 ff94459a31..e7361c6910 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -6,7 +6,6 @@ from pprint import pformat import sys import uuid import re -import operator import json import logging From 74bbf91e097c353b44c8e3adc72dcb621c39ec63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 30 Apr 2024 12:59:25 +0200 Subject: [PATCH 408/423] :bug: fix json --- server_addon/applications/server/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index ae3e82794d..84b7fa33cf 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1289,7 +1289,7 @@ "darwin": [], "linux": [] }, - + "environment": "{}" } ] }, From 8dd30e33efca0755a7241a1ea95e7b91bad1d2f1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Apr 2024 12:06:26 +0100 Subject: [PATCH 409/423] 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 410/423] :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 411/423] :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 412/423] 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 413/423] 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 14df54020b4feae55e995f0ecd396af9b86f842a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 00:02:19 +0200 Subject: [PATCH 414/423] Resolve merge conflict, implement change from `develop` --- 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 d518d3933c..f4f9a34983 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -194,6 +194,11 @@ class MayaPlaceholderPlugin(PlaceholderPlugin): # 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("{}.hiddenInOutliner".format(node), True) From 84dc455bbe6aadce32361d154c3014a95951f998 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 01:36:15 +0200 Subject: [PATCH 415/423] 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 416/423] :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 417/423] :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 418/423] 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 419/423] 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 420/423] 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 421/423] 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 422/423] :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 423/423] :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": [],