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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] 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/727] `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/727] `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/727] `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/727] `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/727] 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/727] 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/727] `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/727] 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/727] =?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/727] =?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/727] `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/727] =?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/727] =?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/727] =?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/727] =?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/727] 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/727] 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/727] 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/727] 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/727] `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/727] `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/727] 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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] `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/727] 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/727] 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/727] 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/727] 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/727] `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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] [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/727] 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/727] 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/727] 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/727] [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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] [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/727] 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/727] 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/727] 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/727] [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/727] 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/727] 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/727] 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/727] 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/727] [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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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/727] 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 3b70243adce054b904f4fe61dfcc5beef0f7f54b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 14:50:13 +0100 Subject: [PATCH 169/727] AY-745 - added Deadline credentials to Settings This provides Site Settings fields for Deadline user name and password. --- server_addon/deadline/server/settings/main.py | 23 +++++++++++++++++++ server_addon/deadline/server/version.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 9537d6d550..8213268bce 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -18,6 +18,16 @@ class ServerListSubmodel(BaseSettingsModel): value: str = SettingsField(title="Value") +class LocalSubmodel(BaseSettingsModel): + """Select your local and remote site""" + username: str = SettingsField("", + title="Username", + scope=["site"]) + password: str = SettingsField("", + title="Password", + scope=["site"]) + + async def defined_deadline_ws_name_enum_resolver( addon: "BaseServerAddon", settings_variant: str = "production", @@ -48,17 +58,30 @@ class DeadlineSettings(BaseSettingsModel): scope=["project"], enum_resolver=defined_deadline_ws_name_enum_resolver ) + require_authentication: bool = SettingsField( + False, + title="Require Authentication", + scope=["project"], + ) publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish Plugins", ) + local_settings: LocalSubmodel = SettingsField( + default_factory=LocalSubmodel, + title="Local setting", + scope=["site"], + description="This setting is only applicable for artist's site", + ) + @validator("deadline_urls") def validate_unique_names(cls, value): ensure_unique_names(value) return value + DEFAULT_VALUES = { "deadline_urls": [ { diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index c11f861afb..569b1212f7 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.9" +__version__ = "0.1.10" From 3137d8e7971cdc449a30f4efd77f368d2832fab4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 14:53:57 +0100 Subject: [PATCH 170/727] AY-745 - added collector for DL user credentials Collects credentials if Project Settings have deadline authentication required. --- .../publish/collect_user_credentials.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py new file mode 100644 index 0000000000..d523f693a2 --- /dev/null +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +"""Collect user credentials + +Requires: + context -> project_settings + +Provides: + context -> deadline_require_authentication (bool) + context -> deadline_auth (tuple (str, str)) - (username, password) or None +""" +import pyblish.api + + +class CollectUserCredentials(pyblish.api.ContextPlugin): + """Collects user name and password for artist if DL requires authentication + """ + + # Run before collect_deadline_server_instance. + order = pyblish.api.CollectorOrder + label = "Collect Deadline User Credentials" + + def process(self, context): + deadline_settings = context.data["project_settings"]["deadline"] + + context.data["deadline_require_authentication"] = ( + deadline_settings)["require_authentication"] + context.data["deadline_auth"] = None + + if not context.data["deadline_require_authentication"]: + return + + local_settings = deadline_settings["local_settings"] + context.data["deadline_auth"] = (local_settings["username"], + local_settings["password"]) From 20d47e54ca678a6046582ac6544fcb670a53f86b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 14:54:24 +0100 Subject: [PATCH 171/727] AY-745 - updated validator for DL connection --- .../help/validate_deadline_connection.xml | 17 +++++++++++++++++ .../publish/validate_deadline_connection.py | 18 ++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml diff --git a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml new file mode 100644 index 0000000000..cafcdb8928 --- /dev/null +++ b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml @@ -0,0 +1,17 @@ + + + + Deadline Authentication + +## Deadline authenticatin is required + +This project has set in Settings that Deadline requires authentication. + +### How to repair? + +Please go to Ayon Server Site settings and provide your Deadline username and + most likely password too. (Deadline could run in configuration that empty passwords are allowed. Ask your administrator for details.) + + + + \ No newline at end of file diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py index a7b300beff..b1503fb95b 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -1,5 +1,7 @@ import pyblish.api +from ayon_core.pipeline import PublishXmlValidationError + from openpype_modules.deadline.abstract_submit_deadline import requests_get @@ -15,8 +17,9 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): responses = {} def process(self, instance): + context = instance.context # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] + deadline_url = context.data["defaultDeadline"] # if custom one is set in instance, use that if instance.data.get("deadlineUrl"): deadline_url = instance.data.get("deadlineUrl") @@ -25,8 +28,19 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): ) assert deadline_url, "Requires Deadline Webservice URL" + kwargs = {} + if context.data["deadline_require_authentication"]: + kwargs["auth"] = context.data["deadline_auth"] + + if not context.data["deadline_auth"]: + raise PublishXmlValidationError( + self, + "Deadline requires authentication. " + "At least username is required to be set in " + "Site Settings.") + if deadline_url not in self.responses: - self.responses[deadline_url] = requests_get(deadline_url) + self.responses[deadline_url] = requests_get(deadline_url, **kwargs) response = self.responses[deadline_url] assert response.ok, "Response must be ok" From 5ad0d4af0080577cfa4928d36c152c5012ea2575 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 14:54:41 +0100 Subject: [PATCH 172/727] AY-745 - updated validator for DL pools --- client/ayon_core/modules/deadline/deadline_module.py | 7 +++++-- .../deadline/plugins/publish/validate_deadline_pools.py | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index d2f0e263d4..761c8a8e92 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -45,7 +45,7 @@ class DeadlineModule(AYONAddon, IPluginPaths): } @staticmethod - def get_deadline_pools(webservice, log=None): + def get_deadline_pools(webservice, auth=None, log=None): # type: (str) -> list """Get pools from Deadline. Args: @@ -64,7 +64,10 @@ class DeadlineModule(AYONAddon, IPluginPaths): argument = "{}/api/pools?NamesOnly=true".format(webservice) try: - response = requests_get(argument) + kwargs = {} + if auth: + kwargs["auth"] = auth + response = requests_get(argument, **kwargs) except requests.exceptions.ConnectionError as exc: msg = 'Cannot connect to DL web service {}'.format(webservice) log.error(msg) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py index 2feb044cf1..c54d187ccf 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -38,7 +38,8 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, return deadline_url = self.get_deadline_url(instance) - pools = self.get_pools(deadline_url) + pools = self.get_pools(deadline_url, + instance.context.data["deadline_auth"]) invalid_pools = {} primary_pool = instance.data.get("primaryPool") @@ -69,13 +70,14 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, deadline_url = instance.data.get("deadlineUrl") return deadline_url - def get_pools(self, deadline_url): + def get_pools(self, deadline_url, auth): if deadline_url not in self.pools_per_url: self.log.debug( "Querying available pools for Deadline url: {}".format( deadline_url) ) pools = DeadlineModule.get_deadline_pools(deadline_url, + auth=auth, log=self.log) self.log.info("Available pools: {}".format(pools)) self.pools_per_url[deadline_url] = pools From 615e6ae6f3ff13b817ebf1a5f0b7186aaef8016b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 15:02:14 +0100 Subject: [PATCH 173/727] AY-745 - updated validator for expected files --- .../plugins/publish/validate_expected_and_rendered_files.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index a666c5c2dc..0f20b5a644 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -208,7 +208,10 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): url = "{}/api/jobs?JobID={}".format(deadline_url, job_id) try: - response = requests_get(url) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_get(url, **kwargs) except requests.exceptions.ConnectionError: self.log.error("Deadline is not accessible at " "{}".format(deadline_url)) From 28e5834b4cbe6facec6baccb951c7b8e4f0cec11 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 15:04:17 +0100 Subject: [PATCH 174/727] AY-745 - added authentication credentials to all calls to DL Changed all calls to module's method which handle SSL --- .../deadline/abstract_submit_deadline.py | 19 +++++++++++++------ .../publish/submit_celaction_deadline.py | 8 ++++++-- .../plugins/publish/submit_fusion_deadline.py | 8 +++++--- .../plugins/publish/submit_maya_deadline.py | 14 +++++++++----- .../plugins/publish/submit_nuke_deadline.py | 8 ++++++-- .../publish/submit_publish_cache_job.py | 8 ++++++-- .../plugins/publish/submit_publish_job.py | 8 ++++++-- 7 files changed, 51 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index 2e0518ae20..293e981230 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -460,7 +460,9 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() - job_id = self.process_submission() + auth = context.data.get("deadline_auth") + self.log.info(f"auth::{auth}") + job_id = self.process_submission(auth) self.log.info("Submitted job to Deadline: {}.".format(job_id)) # TODO: Find a way that's more generic and not render type specific @@ -473,10 +475,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, job_info=render_job_info, plugin_info=render_plugin_info ) - render_job_id = self.submit(payload) + render_job_id = self.submit(payload, auth) self.log.info("Render job id: %s", render_job_id) - def process_submission(self): + def process_submission(self, auth=None): """Process data for submission. This takes Deadline JobInfo, PluginInfo, AuxFile, creates payload @@ -487,7 +489,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ payload = self.assemble_payload() - return self.submit(payload) + return self.submit(payload, auth) @abstractmethod def get_job_info(self): @@ -577,7 +579,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, "AuxFiles": aux_files or self.aux_files } - def submit(self, payload): + def submit(self, payload, auth): """Submit payload to Deadline API end-point. This takes payload in the form of JSON file and POST it to @@ -585,6 +587,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, Args: payload (dict): dict to become json in deadline submission. + auth (tuple): (username, password) Returns: str: resulting Deadline job id. @@ -594,7 +597,11 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ url = "{}/api/jobs".format(self._deadline_url) - response = requests_post(url, json=payload) + kwargs = {} + if auth: + kwargs["auth"] = auth + response = requests_post(url, json=payload, + **kwargs) if not response.ok: self.log.error("Submission failed!") self.log.error(response.status_code) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index bc3636da63..e3160988c8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -2,9 +2,10 @@ import os import re import json import getpass -import requests import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post + class CelactionSubmitDeadline(pyblish.api.InstancePlugin): """Submit CelAction2D scene to Deadline @@ -193,7 +194,10 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - response = requests.post(self.deadline_url, json=payload) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_post(self.deadline_url, json=payload, **kwargs) if not response.ok: self.log.error( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 837ed91c60..54ec6101d0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -2,10 +2,9 @@ import os import json import getpass -import requests - import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) @@ -251,7 +250,10 @@ class FusionSubmitDeadline( # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_post(url, json=payload, **kwargs) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py index 0e871eb90e..10e834e09a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -290,7 +290,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return plugin_payload - def process_submission(self): + def process_submission(self, auth=None): from maya import cmds instance = self._instance @@ -330,7 +330,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if "vrayscene" in instance.data["families"]: self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) - export_job = self.submit(vray_export_payload) + export_job = self.submit(vray_export_payload, + instance.context.data["deadline_auth"]) payload = self._get_vray_render_payload(payload_data) @@ -349,7 +350,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, else: # Submit main render job job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.context.data["deadline_auth"]) def _tile_render(self, payload): """Submit as tile render per frame with dependent assembly jobs.""" @@ -449,7 +451,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Submit frame tile jobs frame_tile_job_id = {} for frame, tile_job_payload in frame_payloads.items(): - job_id = self.submit(tile_job_payload) + job_id = self.submit(tile_job_payload, + instance.context.data["deadline_auth"]) frame_tile_job_id[frame] = job_id # Define assembly payloads @@ -557,7 +560,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "submitting assembly job {} of {}".format(i + 1, num_assemblies) ) - assembly_job_id = self.submit(payload) + assembly_job_id = self.submit(payload, + instance.context.data["deadline_auth"]) assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index a3111454b3..e80c56ee1f 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -4,9 +4,9 @@ import json import getpass from datetime import datetime -import requests import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) @@ -434,7 +434,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - response = requests.post(self.deadline_url, json=payload, timeout=10) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_post(self.deadline_url, json=payload, timeout=10, + **kwargs) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 0561e0f65c..86ac2201e6 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -5,10 +5,10 @@ import json import re from copy import deepcopy -import requests import ayon_api import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline import publish from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start @@ -209,7 +209,11 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_post(url, json=payload, timeout=10, + **kwargs) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 7a6abd5507..e9e76a112c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -5,11 +5,11 @@ import json import re from copy import deepcopy -import requests import clique import ayon_api import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline import publish from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start @@ -303,7 +303,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + kwargs = {} + if instance.context.data["deadline_auth"]: + kwargs["auth"] = instance.context.data["deadline_auth"] + response = requests_post(url, json=payload, timeout=10, + **kwargs) if not response.ok: raise Exception(response.text) From 0a9b88a7cf903b856821d273f63f14b442e44daa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 15:17:06 +0100 Subject: [PATCH 175/727] AY-745 - fix validation --- .../plugins/publish/validate_deadline_connection.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py index b1503fb95b..2c05e505c5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -32,7 +32,7 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): if context.data["deadline_require_authentication"]: kwargs["auth"] = context.data["deadline_auth"] - if not context.data["deadline_auth"]: + if not context.data["deadline_auth"][0]: raise PublishXmlValidationError( self, "Deadline requires authentication. " @@ -43,6 +43,12 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): self.responses[deadline_url] = requests_get(deadline_url, **kwargs) response = self.responses[deadline_url] + if response.status_code == 401: + raise PublishXmlValidationError( + self, + "Deadline requires authentication. " + "Provided credentials are not working. " + "Please change them in Site Settings") assert response.ok, "Response must be ok" assert response.text.startswith("Deadline Web Service "), ( "Web service did not respond with 'Deadline Web Service'" From 56a5f42a66582854e16753604c83f720a3adb848 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 16:11:52 +0100 Subject: [PATCH 176/727] Update client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml Co-authored-by: Roy Nieterau --- .../plugins/publish/help/validate_deadline_connection.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml index cafcdb8928..e9377d8baa 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml +++ b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml @@ -3,7 +3,7 @@ Deadline Authentication -## Deadline authenticatin is required +## Deadline authentication is required This project has set in Settings that Deadline requires authentication. From 543ffa902512e62602f53ce5de94df258dea5b08 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 16:11:59 +0100 Subject: [PATCH 177/727] Update client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py Co-authored-by: Roy Nieterau --- .../deadline/plugins/publish/collect_user_credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index d523f693a2..75836d9fca 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -11,7 +11,7 @@ Provides: import pyblish.api -class CollectUserCredentials(pyblish.api.ContextPlugin): +class CollectDeadlineUserCredentials(pyblish.api.ContextPlugin): """Collects user name and password for artist if DL requires authentication """ From 209cad619118393eb7a3c1442e67d3e6df9cb543 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 19 Mar 2024 16:13:08 +0100 Subject: [PATCH 178/727] AY-745 - remove logging --- client/ayon_core/modules/deadline/abstract_submit_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index 293e981230..cc565fdc1e 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -461,7 +461,6 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.aux_files = self.get_aux_files() auth = context.data.get("deadline_auth") - self.log.info(f"auth::{auth}") job_id = self.process_submission(auth) self.log.info("Submitted job to Deadline: {}.".format(job_id)) From 30e84371e0be60bfd31d912ae342b70f7f5383b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:16:32 +0100 Subject: [PATCH 179/727] 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 180/727] 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 c60bd1cb2df44735ed1ce719f88449928f8d4e4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:56:29 +0100 Subject: [PATCH 181/727] Fusion: Add Validate Instance in Context validator --- client/ayon_core/hosts/fusion/api/action.py | 52 ++++++++++++ .../publish/validate_instance_in_context.py | 80 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py diff --git a/client/ayon_core/hosts/fusion/api/action.py b/client/ayon_core/hosts/fusion/api/action.py index 1643f1ce03..a0c6aafcb5 100644 --- a/client/ayon_core/hosts/fusion/api/action.py +++ b/client/ayon_core/hosts/fusion/api/action.py @@ -58,3 +58,55 @@ class SelectInvalidAction(pyblish.api.Action): self.log.info( "Selecting invalid tools: %s" % ", ".join(sorted(names)) ) + + +class SelectToolAction(pyblish.api.Action): + """Select invalid output tool in Fusion when plug-in failed. + + """ + + label = "Select saver" + on = "failed" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context( + context, + plugin=plugin, + ) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes..") + tools = [] + for instance in errored_instances: + + tool = instance.data.get("tool") + if tool is not None: + tools.append(tool) + else: + self.log.warning( + "Plug-in returned to be invalid, " + f"but has no saver for instance {instance.name}." + ) + + if not tools: + # Assume relevant comp is current comp and clear selection + self.log.info("No invalid tools found.") + comp = get_current_comp() + flow = comp.CurrentFrame.FlowView + flow.Select() # No args equals clearing selection + return + + # Assume a single comp + first_tool = tools[0] + comp = first_tool.Comp() + flow = comp.CurrentFrame.FlowView + flow.Select() # No args equals clearing selection + names = set() + for tool in tools: + flow.Select(tool, True) + comp.SetActiveTool(tool) + names.add(tool.Name) + self.log.info( + "Selecting invalid tools: %s" % ", ".join(sorted(names)) + ) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..3aa6fb452f --- /dev/null +++ b/client/ayon_core/hosts/fusion/plugins/publish/validate_instance_in_context.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +"""Validate if instance context is the same as publish context.""" + +import pyblish.api +from ayon_core.hosts.fusion.api.action import SelectToolAction +from ayon_core.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) + + +class ValidateInstanceInContextFusion(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validator to check if instance context matches context of publish. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + """ + # Similar to maya and houdini-equivalent `ValidateInstanceInContext` + + order = ValidateContentsOrder + label = "Instance in same Context" + optional = True + hosts = ["fusion"] + actions = [SelectToolAction, RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + + instance_context = self.get_context(instance.data) + context = self.get_context(instance.context.data) + if instance_context != context: + context_label = "{} > {}".format(*context) + instance_label = "{} > {}".format(*instance_context) + + raise PublishValidationError( + message=( + "Instance '{}' publishes to different asset than current " + "context: {}. Current context: {}".format( + instance.name, instance_label, context_label + ) + ), + description=( + "## Publishing to a different asset\n" + "There are publish instances present which are publishing " + "into a different asset than your current context.\n\n" + "Usually this is not what you want but there can be cases " + "where you might want to publish into another asset or " + "shot. If that's the case you can disable the validation " + "on the instance to ignore it." + ) + ) + + @classmethod + def repair(cls, instance): + + create_context = instance.context.data["create_context"] + instance_id = instance.data.get("instance_id") + created_instance = create_context.get_instance_by_id( + instance_id + ) + if created_instance is None: + raise RuntimeError( + f"No CreatedInstances found with id '{instance_id} " + f"in {create_context.instances_by_id}" + ) + + context_asset, context_task = cls.get_context(instance.context.data) + created_instance["folderPath"] = context_asset + created_instance["task"] = context_task + create_context.save_changes() + + @staticmethod + def get_context(data): + """Return asset, task from publishing context data""" + return data["folderPath"], data["task"] From 7d9ff383096a262bb03b66009802945c974dad8a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:57:02 +0100 Subject: [PATCH 182/727] Correctly preserve the instance's task instead of forcing current context task --- client/ayon_core/hosts/fusion/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 36102d02cb..b1ecce728b 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -53,7 +53,7 @@ class CollectFusionRender( if product_type not in ["render", "image"]: continue - task_name = context.data["task"] + task_name = inst.data["task"] tool = inst.data["transientData"]["tool"] instance_families = inst.data.get("families", []) From 7fae6d1aaf40952c69189569f99b099578dae3c1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 25 Mar 2024 12:38:21 +0100 Subject: [PATCH 183/727] AY-745 - added new system wide Site settings Credentials for DL servers should be set only once, not for each project separately --- server_addon/deadline/server/__init__.py | 4 +- .../deadline/server/settings/__init__.py | 2 + server_addon/deadline/server/settings/main.py | 51 +++++++------------ .../deadline/server/settings/site_settings.py | 26 ++++++++++ 4 files changed, 48 insertions(+), 35 deletions(-) create mode 100644 server_addon/deadline/server/settings/site_settings.py diff --git a/server_addon/deadline/server/__init__.py b/server_addon/deadline/server/__init__.py index 36d04189a9..4a67b9741c 100644 --- a/server_addon/deadline/server/__init__.py +++ b/server_addon/deadline/server/__init__.py @@ -3,7 +3,7 @@ from typing import Type from ayon_server.addons import BaseServerAddon from .version import __version__ -from .settings import DeadlineSettings, DEFAULT_VALUES +from .settings import DeadlineSettings, DEFAULT_VALUES, DeadlineSiteSettings class Deadline(BaseServerAddon): @@ -11,6 +11,8 @@ class Deadline(BaseServerAddon): title = "Deadline" version = __version__ settings_model: Type[DeadlineSettings] = DeadlineSettings + site_settings_model: Type[DeadlineSiteSettings] = DeadlineSiteSettings + async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/deadline/server/settings/__init__.py b/server_addon/deadline/server/settings/__init__.py index 0307862afa..d25c0fb330 100644 --- a/server_addon/deadline/server/settings/__init__.py +++ b/server_addon/deadline/server/settings/__init__.py @@ -2,9 +2,11 @@ from .main import ( DeadlineSettings, DEFAULT_VALUES, ) +from .site_settings import DeadlineSiteSettings __all__ = ( "DeadlineSettings", + "DeadlineSiteSettings", "DEFAULT_VALUES", ) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 8213268bce..31a42a3e27 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -12,22 +12,6 @@ from .publish_plugins import ( ) -class ServerListSubmodel(BaseSettingsModel): - _layout = "compact" - name: str = SettingsField(title="Name") - value: str = SettingsField(title="Value") - - -class LocalSubmodel(BaseSettingsModel): - """Select your local and remote site""" - username: str = SettingsField("", - title="Username", - scope=["site"]) - password: str = SettingsField("", - title="Password", - scope=["site"]) - - async def defined_deadline_ws_name_enum_resolver( addon: "BaseServerAddon", settings_variant: str = "production", @@ -39,42 +23,39 @@ async def defined_deadline_ws_name_enum_resolver( settings = await addon.get_studio_settings(variant=settings_variant) - ws_urls = [] + ws_server_name = [] for deadline_url_item in settings.deadline_urls: - ws_urls.append(deadline_url_item.name) + ws_server_name.append(deadline_url_item.name) - return ws_urls + return ws_server_name + +class ServerListSubmodel(BaseSettingsModel): + _layout = "compact" + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Url") + require_authentication: bool = SettingsField(title="Require authentication") + ssl: bool = SettingsField(title="SSL") class DeadlineSettings(BaseSettingsModel): deadline_urls: list[ServerListSubmodel] = SettingsField( default_factory=list, - title="System Deadline Webservice URLs", + title="System Deadline Webservice Info", scope=["studio"], ) + deadline_server: str = SettingsField( - title="Project deadline server", + title="Project Deadline server name", section="---", scope=["project"], enum_resolver=defined_deadline_ws_name_enum_resolver ) - require_authentication: bool = SettingsField( - False, - title="Require Authentication", - scope=["project"], - ) + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish Plugins", ) - local_settings: LocalSubmodel = SettingsField( - default_factory=LocalSubmodel, - title="Local setting", - scope=["site"], - description="This setting is only applicable for artist's site", - ) - @validator("deadline_urls") def validate_unique_names(cls, value): ensure_unique_names(value) @@ -86,7 +67,9 @@ DEFAULT_VALUES = { "deadline_urls": [ { "name": "default", - "value": "http://127.0.0.1:8082" + "value": "http://127.0.0.1:8082", + "require_authentication": False, + "ssl": False } ], "deadline_server": "default", diff --git a/server_addon/deadline/server/settings/site_settings.py b/server_addon/deadline/server/settings/site_settings.py new file mode 100644 index 0000000000..cc3ec66ad9 --- /dev/null +++ b/server_addon/deadline/server/settings/site_settings.py @@ -0,0 +1,26 @@ +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, +) +from .main import defined_deadline_ws_name_enum_resolver + + +class LocalSubmodel(BaseSettingsModel): + """Provide credentials for configured DL servers""" + _layout = "expanded" + server_name: str = SettingsField("", + title="DL server name", + enum_resolver=defined_deadline_ws_name_enum_resolver) + username: str = SettingsField("", + title="Username") + password: str = SettingsField("", + title="Password") + + +class DeadlineSiteSettings(BaseSettingsModel): + local_settings: list[LocalSubmodel] = SettingsField( + default_factory=list, + title="Local setting", + description="Please provide credentials for configured Deadline servers", + ) + From d2503fae073113374da76bde5d11a902a055cf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 26 Mar 2024 11:01:08 +0100 Subject: [PATCH 184/727] :recycle: remove explicit list --- client/ayon_core/plugins/publish/integrate.py | 64 +------------------ 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index ce34f2e88b..5b91d9afb8 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -42,7 +42,7 @@ def prepare_changes(old_entity, new_entity): Returns: dict[str, Any]: Changes that have new entity. - + """ changes = {} for key in set(new_entity.keys()): @@ -108,68 +108,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder - families = ["workfile", - "pointcache", - "pointcloud", - "proxyAbc", - "camera", - "animation", - "model", - "maxScene", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "ass", - "vdbcache", - "scene", - "vrayproxy", - "vrayscene_layer", - "render", - "prerender", - "imagesequence", - "review", - "rendersetup", - "rig", - "plate", - "look", - "ociolook", - "audio", - "yetiRig", - "yeticache", - "nukenodes", - "gizmo", - "source", - "matchmove", - "image", - "assembly", - "fbx", - "gltf", - "textures", - "action", - "harmony.template", - "harmony.palette", - "editorial", - "background", - "camerarig", - "redshiftproxy", - "effect", - "xgen", - "hda", - "usd", - "staticMesh", - "skeletalMesh", - "mvLook", - "mvUsd", - "mvUsdComposition", - "mvUsdOverride", - "online", - "uasset", - "blendScene", - "yeticacheUE", - "tycache" - ] - default_template_name = "publish" # Representation context keys that should always be written to From 6b0568a3edb5b5eb8e91160da8a6d69f7f268da1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:36:54 +0100 Subject: [PATCH 185/727] AY-745 - added version to client side Required for getting site settings from addon --- client/ayon_core/modules/deadline/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/modules/deadline/__init__.py b/client/ayon_core/modules/deadline/__init__.py index 5631e501d8..683d8dbe4a 100644 --- a/client/ayon_core/modules/deadline/__init__.py +++ b/client/ayon_core/modules/deadline/__init__.py @@ -1,6 +1,8 @@ from .deadline_module import DeadlineModule +from .version import __version__ __all__ = ( "DeadlineModule", + "__version__" ) From 12d49cbe6158907102aaec4bd7d3e55a0a7f2a38 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:37:27 +0100 Subject: [PATCH 186/727] AY-745 - added field to carry over deadline info Should be refactored into more generic if necessary --- client/ayon_core/pipeline/publish/abstract_collect_render.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 745632ca0a..6bd011b8f1 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -80,6 +80,7 @@ class RenderInstance(object): anatomyData = attr.ib(default=None) outputDir = attr.ib(default=None) context = attr.ib(default=None) + deadline = attr.ib(default=None) @frameStart.validator def check_frame_start(self, _, value): From 6347e659650f7b7179f9c6be76fc4befdd8ec6e5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:38:20 +0100 Subject: [PATCH 187/727] AY-745 - explicit cast to tuple Data class translates auth tuple into list --- .../ayon_core/modules/deadline/abstract_submit_deadline.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index cc565fdc1e..9b62f473dd 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -49,6 +49,10 @@ def requests_post(*args, **kwargs): if 'verify' not in kwargs: kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa + + auth = kwargs.get("auth") + if auth: + kwargs["auth"] = tuple(auth) # explicit cast to tuple # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.post(*args, **kwargs) @@ -70,6 +74,9 @@ def requests_get(*args, **kwargs): if 'verify' not in kwargs: kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa + auth = kwargs.get("auth") + if auth: + kwargs["auth"] = tuple(auth) # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.get(*args, **kwargs) From 5c9fc4a9960cbeeca236e56df0ecf5fa28e174ab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:38:46 +0100 Subject: [PATCH 188/727] AY-745 - changed structure Additiona 'deadline' wrapper introduced --- .../ayon_core/modules/deadline/abstract_submit_deadline.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index 9b62f473dd..e71177b34e 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -441,9 +441,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """Plugin entry point.""" self._instance = instance context = instance.context - self._deadline_url = context.data.get("defaultDeadline") - self._deadline_url = instance.data.get( - "deadlineUrl", self._deadline_url) + self._deadline_url = instance.data["deadline"]["url"] assert self._deadline_url, "Requires Deadline Webservice URL" @@ -467,7 +465,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() - auth = context.data.get("deadline_auth") + auth = instance.data["deadline"]["auth"] job_id = self.process_submission(auth) self.log.info("Submitted job to Deadline: {}.".format(job_id)) From 68be4d77303ef311f09c92c779a125aff8fc93c0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:40:56 +0100 Subject: [PATCH 189/727] AY-745 - changed aproach to get DL to use collect_deadline_server_from_instance.py should be reworked as currently it is not applicable for any publishes. There were fields to provide DL server directly into DCC UI (Maya, Nuke), but they are gone with New Publisher. --- .../collect_deadline_server_from_instance.py | 41 +++++++++++++++---- .../collect_default_deadline_server.py | 18 ++++---- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index ea4b7a213e..74ab79cfbc 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -7,6 +7,7 @@ attribute or using default server if that attribute doesn't exists. """ import pyblish.api from ayon_core.pipeline.publish import KnownPublishError +from ayon_core.pipeline.context_tools import get_current_host_name class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): @@ -15,15 +16,37 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): # Run before collect_render. order = pyblish.api.CollectorOrder + 0.005 label = "Deadline Webservice from the Instance" - families = ["rendering", "renderlayer"] - hosts = ["maya"] + families = ["render", + "rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou", + "image"] # for Fusion def process(self, instance): - instance.data["deadlineUrl"] = self._collect_deadline_url(instance) - instance.data["deadlineUrl"] = \ - instance.data["deadlineUrl"].strip().rstrip("/") + if not "deadline" in instance.data: + instance.data["deadline"] = {} + + host_name = get_current_host_name() + if host_name == "maya": + deadline_url = self._collect_deadline_url(instance) + else: + deadline_url = (instance.data.get("deadlineUrl") or # backwards + instance.data.get("deadline", {}).get("url")) + if deadline_url: + instance.data["deadline"]["url"] = deadline_url.strip().rstrip("/") + else: + instance.data["deadline"]["url"] = instance.context.data["deadline"]["defaultDeadline"] # noqa self.log.debug( - "Using {} for submission.".format(instance.data["deadlineUrl"])) + "Using {} for submission".format(instance.data["deadline"]["url"])) def _collect_deadline_url(self, render_instance): # type: (pyblish.api.Instance) -> str @@ -49,8 +72,8 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): ["project_settings"] ["deadline"] ) - - default_server = render_instance.context.data["defaultDeadline"] + default_server = (render_instance.context.data["deadline"] + ["defaultDeadline"]) # QUESTION How and where is this is set? Should be removed? instance_server = render_instance.data.get("deadlineServers") if not instance_server: @@ -66,7 +89,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): default_servers = { url_item["name"]: url_item["value"] - for url_item in deadline_settings["deadline_urls"] + for url_item in deadline_settings["deadline_server_info"] } project_servers = ( render_instance.context.data diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 8123409052..472a40300d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -33,15 +33,17 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): deadline_settings = context.data["project_settings"]["deadline"] deadline_server_name = deadline_settings["deadline_server"] - deadline_webservice = None + dl_ws_item = None if deadline_server_name: - deadline_webservice = deadline_module.deadline_urls.get( + dl_ws_item = deadline_module.deadline_server_info.get( deadline_server_name) - default_deadline_webservice = deadline_module.deadline_urls["default"] - deadline_webservice = ( - deadline_webservice - or default_deadline_webservice - ) + if dl_ws_item: + deadline_url = dl_ws_item["value"] + else: + default_dl_item = deadline_module.deadline_server_info.pop() + deadline_url = default_dl_item["value"] - context.data["defaultDeadline"] = deadline_webservice.strip().rstrip("/") # noqa + context.data["deadline"] = {} + context.data["deadline"]["defaultDeadline"] = ( + deadline_url.strip().rstrip("/")) From eca34a912be848c4f3f7f16991634156edb35745 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:41:44 +0100 Subject: [PATCH 190/727] AY-745 - update to Harmony collector DL will not work for Harmony, needs to be translated into New Publisher. --- .../hosts/harmony/plugins/publish/collect_farm_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py index 156e2ac6ba..e869de316f 100644 --- a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py @@ -177,7 +177,8 @@ class CollectFarmRender(publish.AbstractCollectRender): outputFormat=info[1], outputStartFrame=info[3], leadingZeros=info[2], - ignoreFrameHandleCheck=True + ignoreFrameHandleCheck=True, + # deadline=inst.data.get("deadline") TODO ) render_instance.context = context From abfcd8b2e7985d15664fbc48923a0b2a0c71b211 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:43:02 +0100 Subject: [PATCH 191/727] AY-745 - explicit carry over of DL meta for AbstractCollectRender AbstractCollectRender changes from original instance to `RenderInstance`, DL metadata must be propagated. --- .../hosts/aftereffects/plugins/publish/collect_render.py | 1 + client/ayon_core/hosts/fusion/plugins/publish/collect_render.py | 1 + 2 files changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py index afd58ca758..913b4a7b96 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py @@ -146,6 +146,7 @@ class CollectAERender(publish.AbstractCollectRender): if "review" in instance.families: # to skip ExtractReview locally instance.families.remove("review") + instance.deadline = inst.data.get("deadline") instances.append(instance) instances_to_remove.append(inst) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 36102d02cb..ddc2902644 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -113,6 +113,7 @@ class CollectFusionRender( if "review" in instance.families: # to skip ExtractReview locally instance.families.remove("review") + instance.deadline = inst.data.get("deadline") # add new instance to the list and remove the original # instance since it is not needed anymore From 50ade43360ec6b5c6011f37186a25d216c5ea6b9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:47:38 +0100 Subject: [PATCH 192/727] AY-745 - add user credentials to instance All render instances should have deadline server collected, enhance metadata with credentials --- .../publish/collect_user_credentials.py | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 75836d9fca..061890dd08 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -3,32 +3,85 @@ Requires: context -> project_settings + instance.data["deadline"]["url"] + or instance.data["deadlineUrl"] for backward compatibility, remove soon Provides: - context -> deadline_require_authentication (bool) - context -> deadline_auth (tuple (str, str)) - (username, password) or None + instance.data["deadline"] -> require_authentication (bool) + instance.data["deadline"] -> auth (tuple (str, str)) - + (username, password) or None """ import pyblish.api +from ayon_api import get_server_api_connection +from ayon_core.modules.deadline.deadline_module import DeadlineModule +from ayon_core.modules.deadline import __version__ -class CollectDeadlineUserCredentials(pyblish.api.ContextPlugin): + +class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): """Collects user name and password for artist if DL requires authentication """ # Run before collect_deadline_server_instance. - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.200 label = "Collect Deadline User Credentials" - def process(self, context): - deadline_settings = context.data["project_settings"]["deadline"] + hosts = ["aftereffects", + "fusion", + "harmony" + "nuke", + "maya", + "max", + "houdini"] - context.data["deadline_require_authentication"] = ( - deadline_settings)["require_authentication"] - context.data["deadline_auth"] = None + families = ["render", + "rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou"] - if not context.data["deadline_require_authentication"]: + def process(self, instance): + # backward compatibility, remove soon + collected_deadline_url = (instance.data.get("deadlineUrl") or + instance.data.get("deadline", {}).get("url")) + if not collected_deadline_url: + raise ValueError("Instance doesn't have 'deadlineUrl'.") + context_data = instance.context.data + deadline_settings = context_data["project_settings"]["deadline"] + + deadline_server_name = None + # deadline url might be set directly from instance, need to find + # metadata for it + for deadline_info in deadline_settings["deadline_urls"]: + dl_settings_url = deadline_info["value"].strip().rstrip("/") + if dl_settings_url == collected_deadline_url: + deadline_server_name = deadline_info["name"] + break + + if not deadline_server_name: + raise ValueError(f"Collected {collected_deadline_url} doesn't " + "match any site configured in Studio Settings") + + instance.data["deadline"]["require_authentication"] = ( + deadline_info["require_authentication"] + ) + instance.data["deadline"]["auth"] = None + + if not deadline_info["require_authentication"]: return - local_settings = deadline_settings["local_settings"] - context.data["deadline_auth"] = (local_settings["username"], - local_settings["password"]) + local_settings = get_server_api_connection().get_addon_site_settings( + DeadlineModule.name, __version__) + local_settings = local_settings["local_settings"] + for server_info in local_settings: + if deadline_server_name == server_info["server_name"]: + instance.data["deadline"]["auth"] = (server_info["username"], + server_info["password"]) From 7cf416dc7ed40858daeb8510c625dd83cf2f6e4f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:48:24 +0100 Subject: [PATCH 193/727] AY-745 - refactored class variable It is not only urls, it is whole metadata --- client/ayon_core/modules/deadline/deadline_module.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index 761c8a8e92..3a35654737 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -19,23 +19,23 @@ class DeadlineModule(AYONAddon, IPluginPaths): def initialize(self, studio_settings): # This module is always enabled - deadline_urls = {} + deadline_server_info = {} enabled = self.name in studio_settings if enabled: deadline_settings = studio_settings[self.name] - deadline_urls = { - url_item["name"]: url_item["value"] + deadline_server_info = { + url_item["name"]: url_item for url_item in deadline_settings["deadline_urls"] } - if enabled and not deadline_urls: + if enabled and not deadline_server_info: enabled = False self.log.warning(( "Deadline Webservice URLs are not specified. Disabling addon." )) self.enabled = enabled - self.deadline_urls = deadline_urls + self.deadline_server_info = deadline_server_info def get_plugin_paths(self): """Deadline plugin paths.""" From 6faa5acdf79d185b6f09ec9db88bb6f0b80debc2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:49:20 +0100 Subject: [PATCH 194/727] AY-745 - refactored format of credentials Now on instance, in `deadline` dictionary --- .../plugins/publish/submit_blender_deadline.py | 3 ++- .../plugins/publish/submit_fusion_deadline.py | 10 ++++------ .../plugins/publish/submit_max_deadline.py | 6 ++++-- .../plugins/publish/submit_maya_deadline.py | 8 ++++---- .../plugins/publish/submit_nuke_deadline.py | 5 +++-- .../plugins/publish/submit_publish_cache_job.py | 5 +++-- .../deadline/plugins/publish/submit_publish_job.py | 14 +++++++------- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py index ae19e63a37..a60bd70b13 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -172,7 +172,8 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance.data["toBeRenderedOn"] = "deadline" payload = self.assemble_payload() - return self.submit(payload) + return self.submit(payload, + auth=instance.data["deadline"]["auth"]) def from_published_scene(self): """ diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 54ec6101d0..35c99108be 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -94,10 +94,7 @@ class FusionSubmitDeadline( from ayon_core.hosts.fusion.api.lib import get_frame_path # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" # Collect all saver instances in context that are to be rendered @@ -251,8 +248,9 @@ class FusionSubmitDeadline( # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth response = requests_post(url, json=payload, **kwargs) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py index 1abefa515a..51a9e6abfd 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py @@ -185,11 +185,13 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, payload_data, project_settings) job_infos, plugin_infos = payload for job_info, plugin_info in zip(job_infos, plugin_infos): - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.data["deadline"]["auth"]) else: payload = self._use_published_name(payload_data, project_settings) job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.data["deadline"]["auth"]) def _use_published_name(self, data, project_settings): # Not all hosts can import these modules. diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py index 10e834e09a..83ccfc7278 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -331,7 +331,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) export_job = self.submit(vray_export_payload, - instance.context.data["deadline_auth"]) + instance.data["deadline"]["auth"]) payload = self._get_vray_render_payload(payload_data) @@ -351,7 +351,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Submit main render job job_info, plugin_info = payload self.submit(self.assemble_payload(job_info, plugin_info), - instance.context.data["deadline_auth"]) + instance.data["deadline"]["auth"]) def _tile_render(self, payload): """Submit as tile render per frame with dependent assembly jobs.""" @@ -452,7 +452,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, frame_tile_job_id = {} for frame, tile_job_payload in frame_payloads.items(): job_id = self.submit(tile_job_payload, - instance.context.data["deadline_auth"]) + instance.data["deadline"]["auth"]) frame_tile_job_id[frame] = job_id # Define assembly payloads @@ -561,7 +561,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, num_assemblies) ) assembly_job_id = self.submit(payload, - instance.context.data["deadline_auth"]) + instance.data["deadline"]["auth"]) assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index e80c56ee1f..07ee30af8c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -435,8 +435,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth response = requests_post(self.deadline_url, json=payload, timeout=10, **kwargs) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 86ac2201e6..09e4f8a446 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -210,8 +210,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, url = "{}/api/jobs".format(self.deadline_url) kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth response = requests_post(url, json=payload, timeout=10, **kwargs) if not response.ok: diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index e9e76a112c..bacf902849 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -304,8 +304,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, url = "{}/api/jobs".format(self.deadline_url) kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth response = requests_post(url, json=payload, timeout=10, **kwargs) if not response.ok: @@ -462,10 +463,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, } # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") + self.deadline_url = instance.data["deadline"]["url"] assert self.deadline_url, "Requires Deadline Webservice URL" deadline_publish_job_id = \ @@ -473,7 +471,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, # Inject deadline url to instances. for inst in instances: - inst["deadlineUrl"] = self.deadline_url + if not "deadline" in inst: + inst["deadline"] = {} + inst["deadline"]["url"] = self.deadline_url # publish job file publish_job = { From 2ed1d0feee7cb12db33e53b0982e5b6c8ce3b1ae Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:50:26 +0100 Subject: [PATCH 195/727] AY-745 - refactored format of credentials Now on instance, in `deadline` dictionary --- .../publish/validate_deadline_connection.py | 21 +++++++------------ .../publish/validate_deadline_pools.py | 12 ++--------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py index 2c05e505c5..e077aedd9b 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -10,29 +10,22 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): label = "Validate Deadline Web Service" order = pyblish.api.ValidatorOrder - hosts = ["maya", "nuke"] - families = ["renderlayer", "render"] + hosts = ["maya", "nuke", "aftereffects", "harmony", "fusion"] + families = ["renderlayer", "render", "render.farm"] # cache responses = {} def process(self, instance): - context = instance.context - # get default deadline webservice url from deadline module - deadline_url = context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") - self.log.debug( - "We have deadline URL on instance {}".format(deadline_url) - ) + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" kwargs = {} - if context.data["deadline_require_authentication"]: - kwargs["auth"] = context.data["deadline_auth"] + if instance.data["deadline"]["require_authentication"]: + auth = instance.data["deadline"]["auth"] + kwargs["auth"] = auth - if not context.data["deadline_auth"][0]: + if not auth[0]: raise PublishXmlValidationError( self, "Deadline requires authentication. " diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py index c54d187ccf..1afe49b7c9 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -37,9 +37,9 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, self.log.debug("Skipping local instance.") return - deadline_url = self.get_deadline_url(instance) + deadline_url = instance.data["deadline"]["url"] pools = self.get_pools(deadline_url, - instance.context.data["deadline_auth"]) + instance.data["deadline"].get("auth")) invalid_pools = {} primary_pool = instance.data.get("primaryPool") @@ -62,14 +62,6 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, formatting_data={"pools_str": ", ".join(pools)} ) - def get_deadline_url(self, instance): - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - if instance.data.get("deadlineUrl"): - # if custom one is set in instance, use that - deadline_url = instance.data.get("deadlineUrl") - return deadline_url - def get_pools(self, deadline_url, auth): if deadline_url not in self.pools_per_url: self.log.debug( From 2dac53a940a1a8cc5efeaa6fcdc487cf2344f2ba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:51:00 +0100 Subject: [PATCH 196/727] AY-745 - added protection for older DL Some DLs returned `none` for no pools configured. --- .../deadline/plugins/publish/validate_deadline_pools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py index 1afe49b7c9..5094b3deaf 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -71,6 +71,9 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, pools = DeadlineModule.get_deadline_pools(deadline_url, auth=auth, log=self.log) + # some DL return "none" as a pool name + if not "none" in pools: + pools.append("none") self.log.info("Available pools: {}".format(pools)) self.pools_per_url[deadline_url] = pools From e604c28cdb901f1d9312691f0f71fcc6754d2d87 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 13:51:15 +0100 Subject: [PATCH 197/727] AY-745 - added version for client side --- client/ayon_core/modules/deadline/version.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 client/ayon_core/modules/deadline/version.py diff --git a/client/ayon_core/modules/deadline/version.py b/client/ayon_core/modules/deadline/version.py new file mode 100644 index 0000000000..569b1212f7 --- /dev/null +++ b/client/ayon_core/modules/deadline/version.py @@ -0,0 +1 @@ +__version__ = "0.1.10" From 3791731b6ac2b42583f56ccc69501f39b6372f06 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Mar 2024 14:45:30 +0100 Subject: [PATCH 198/727] 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 569d11932395bdc5c97a8cd2386f6b85c56fd8bb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 14:57:11 +0100 Subject: [PATCH 199/727] AY-745 - add local filtering Shouldn't run to DL --- .../publish/collect_deadline_server_from_instance.py | 1 + .../plugins/publish/collect_default_deadline_server.py | 3 +-- .../plugins/publish/collect_user_credentials.py | 10 ++++------ .../publish/validate_expected_and_rendered_files.py | 6 +----- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 74ab79cfbc..913d64cb91 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -16,6 +16,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): # Run before collect_render. order = pyblish.api.CollectorOrder + 0.005 label = "Deadline Webservice from the Instance" + targets = ["local"] families = ["render", "rendering", "render.farm", diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 472a40300d..17b9386b5d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -20,8 +20,7 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): # Run before collect_deadline_server_instance. order = pyblish.api.CollectorOrder + 0.0025 label = "Default Deadline Webservice" - - pass_mongo_url = False + targets = ["local"] def process(self, context): try: diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 061890dd08..e7bbe48bd0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -4,7 +4,6 @@ Requires: context -> project_settings instance.data["deadline"]["url"] - or instance.data["deadlineUrl"] for backward compatibility, remove soon Provides: instance.data["deadline"] -> require_authentication (bool) @@ -26,9 +25,10 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.200 label = "Collect Deadline User Credentials" + targets = ["local"] hosts = ["aftereffects", "fusion", - "harmony" + "harmony", "nuke", "maya", "max", @@ -49,11 +49,9 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): "publish.hou"] def process(self, instance): - # backward compatibility, remove soon - collected_deadline_url = (instance.data.get("deadlineUrl") or - instance.data.get("deadline", {}).get("url")) + collected_deadline_url = instance.data["deadline"]["url"] if not collected_deadline_url: - raise ValueError("Instance doesn't have 'deadlineUrl'.") + raise ValueError("Instance doesn't have '[deadline][url]'.") context_data = instance.context.data deadline_settings = context_data["project_settings"]["deadline"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index 0f20b5a644..2b1c99aca8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -199,11 +199,7 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): (dict): Job info from Deadline """ - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" url = "{}/api/jobs?JobID={}".format(deadline_url, job_id) From b4d5c02a6c51a7b8c2ced6e30925aa1052772253 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 14:57:37 +0100 Subject: [PATCH 200/727] AY-745 - fix retrieval of deadline url --- .../deadline/plugins/publish/submit_celaction_deadline.py | 6 +----- .../deadline/plugins/publish/submit_fusion_deadline.py | 1 - .../deadline/plugins/publish/submit_nuke_deadline.py | 6 +----- .../deadline/plugins/publish/submit_publish_cache_job.py | 8 ++------ 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index e3160988c8..fe399b6a32 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -31,11 +31,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" self.deadline_url = "{}/api/jobs".format(deadline_url) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 35c99108be..88929ff6ab 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -93,7 +93,6 @@ class FusionSubmitDeadline( from ayon_core.hosts.fusion.api.lib import get_frame_path - # get default deadline webservice url from deadline module deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index 07ee30af8c..98f775187e 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -109,11 +109,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, node = instance.data["transientData"]["node"] context = instance.context - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" self.deadline_url = "{}/api/jobs".format(deadline_url) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 09e4f8a446..728c4d186a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -346,12 +346,8 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, deadline_publish_job_id = None if submission_type == "deadline": - # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") - assert self.deadline_url, "Requires Deadline Webservice URL" + deadline_url = instance.data["deadline"]["url"] + assert deadline_url, "Requires Deadline Webservice URL" deadline_publish_job_id = \ self._submit_deadline_post_job(instance, render_job) From 77c939b93be2b2d7600043a8c9dcaa6f928f8cf5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 15:06:54 +0100 Subject: [PATCH 201/727] AY-745 - remove unnecessary comment --- .../deadline/plugins/publish/collect_user_credentials.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index e7bbe48bd0..86418387a5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -20,8 +20,6 @@ from ayon_core.modules.deadline import __version__ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): """Collects user name and password for artist if DL requires authentication """ - - # Run before collect_deadline_server_instance. order = pyblish.api.CollectorOrder + 0.200 label = "Collect Deadline User Credentials" From 466f940a737f2ff32bdb115b503c7d0fab30530e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 15:31:00 +0100 Subject: [PATCH 202/727] AY-745 - added todo This should be refactored in next PR --- .../plugins/publish/collect_deadline_server_from_instance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 913d64cb91..8e7f836830 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -36,6 +36,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): if not "deadline" in instance.data: instance.data["deadline"] = {} + # todo: separate logic should be removed, all hosts should have same host_name = get_current_host_name() if host_name == "maya": deadline_url = self._collect_deadline_url(instance) From 6bbb956732a4eaec3def182a3c240c5e90d59d53 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 15:34:40 +0100 Subject: [PATCH 203/727] AY-745 - renamed class --- server_addon/deadline/server/settings/site_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/deadline/server/settings/site_settings.py b/server_addon/deadline/server/settings/site_settings.py index cc3ec66ad9..a77a6edc7e 100644 --- a/server_addon/deadline/server/settings/site_settings.py +++ b/server_addon/deadline/server/settings/site_settings.py @@ -5,7 +5,7 @@ from ayon_server.settings import ( from .main import defined_deadline_ws_name_enum_resolver -class LocalSubmodel(BaseSettingsModel): +class CredentialPerServerModel(BaseSettingsModel): """Provide credentials for configured DL servers""" _layout = "expanded" server_name: str = SettingsField("", @@ -18,7 +18,7 @@ class LocalSubmodel(BaseSettingsModel): class DeadlineSiteSettings(BaseSettingsModel): - local_settings: list[LocalSubmodel] = SettingsField( + local_settings: list[CredentialPerServerModel] = SettingsField( default_factory=list, title="Local setting", description="Please provide credentials for configured Deadline servers", From 2dc3eec35f1b9d4b381334d6be164f41a10f0b76 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 15:38:03 +0100 Subject: [PATCH 204/727] AY-745 - renamed class --- server_addon/deadline/server/settings/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 31a42a3e27..4289e3d335 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -29,7 +29,8 @@ async def defined_deadline_ws_name_enum_resolver( return ws_server_name -class ServerListSubmodel(BaseSettingsModel): +class ServerItemSubmodel(BaseSettingsModel): + """Connection info about configured DL servers.""" _layout = "compact" name: str = SettingsField(title="Name") value: str = SettingsField(title="Url") @@ -38,12 +39,14 @@ class ServerListSubmodel(BaseSettingsModel): class DeadlineSettings(BaseSettingsModel): - deadline_urls: list[ServerListSubmodel] = SettingsField( + # configured DL servers + deadline_urls: list[ServerItemSubmodel] = SettingsField( default_factory=list, title="System Deadline Webservice Info", scope=["studio"], ) + # name(key) of selected server for project deadline_server: str = SettingsField( title="Project Deadline server name", section="---", From bef6855cca0972d24cf942d6f7e964f78a875288 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 26 Mar 2024 15:55:53 +0100 Subject: [PATCH 205/727] AY-745 - fix passing DL credentials to metadata file Must be passed to query current values for job if changed by artist in DL directly. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 ++-- .../plugins/publish/validate_expected_and_rendered_files.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 99a976132a..6d288111b7 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -469,11 +469,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, deadline_publish_job_id = \ self._submit_deadline_post_job(instance, render_job, instances) - # Inject deadline url to instances. + # Inject deadline url to instances to query DL for job id for overrides for inst in instances: if not "deadline" in inst: inst["deadline"] = {} - inst["deadline"]["url"] = self.deadline_url + inst["deadline"] = instance.data["deadline"] # publish job file publish_job = { diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index 0c3977278e..83e867408c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -205,8 +205,9 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): url = "{}/api/jobs?JobID={}".format(deadline_url, job_id) try: kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth response = requests_get(url, **kwargs) except requests.exceptions.ConnectionError: self.log.error("Deadline is not accessible at " From 4c51f3a560109fa75c50f4104e609f93b4c8f28b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 17:44:31 +0100 Subject: [PATCH 206/727] Implement fix for losing instance id and disconnected logs in publisher UI --- .../fusion/plugins/publish/collect_render.py | 7 +++++++ .../publish/abstract_collect_render.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index b1ecce728b..3f5e2837bc 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -119,6 +119,13 @@ class CollectFusionRender( instances.append(instance) instances_to_remove.append(inst) + # TODO: Avoid this transfer instance id hack + # pass on the `id` of the original instance so any artist + # facing logs transfer as if they were made on the new instance + # instead, see `AbstractCollectRender.process()` + instance.id = inst.id + instance.instance_id = inst.data.get("instance_id") + for instance in instances_to_remove: context.remove(instance) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 745632ca0a..8b98cb678e 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -215,6 +215,25 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): render_instance_dict = attr.asdict(render_instance) instance = context.create_instance(render_instance.name) + + # TODO: Avoid this transfer instance id hack + # Transfer the id from another instance, e.g. when the render + # instance is intended to "replace" an existing instance like + # fusion does in `CollectRender`. Without matching the ids any + # logs produced for the instance prior to the "replacement" will + # not show artist-facing logs in reports + transfer_id = getattr(render_instance, "id") + if transfer_id: + instance._id = transfer_id + # The `instance_id` data may be overridden on the Creator + # to e.g. maybe make unique by node name instead of uuid, + # like in Maya, Fusion, Houdini integration. + # This transfers that unique (named) instance id. + # This transfer logic is currently (only?) used in Fusion. + transfer_instance_id = getattr(render_instance, "instance_id") + if transfer_instance_id: + instance.data["instance_id"] = transfer_instance_id + instance.data["label"] = render_instance.label instance.data.update(render_instance_dict) instance.data.update(data) From 49cfea34c7893cd221a2fbf2973241b353a4923a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 01:25:46 +0100 Subject: [PATCH 207/727] 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 208/727] 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 209/727] 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 210/727] 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 211/727] 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 212/727] 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 213/727] 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 214/727] 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 4bd1f05036e1cb22326f6621ed3df8c8dc810ad4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 23:10:08 +0100 Subject: [PATCH 215/727] Remove legacy `suspend_publish` attribute definition in favor of `publishJobState` --- .../plugins/publish/submit_fusion_deadline.py | 14 +------------- .../publish/submit_houdini_render_deadline.py | 6 ------ .../plugins/publish/submit_nuke_deadline.py | 9 --------- .../plugins/publish/submit_publish_cache_job.py | 3 --- .../deadline/plugins/publish/submit_publish_job.py | 3 --- 5 files changed, 1 insertion(+), 34 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index cf124c0bcc..3232edfeb3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -9,10 +9,7 @@ import pyblish.api from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) -from ayon_core.lib import ( - BoolDef, - NumberDef, -) +from ayon_core.lib import NumberDef class FusionSubmitDeadline( @@ -64,11 +61,6 @@ class FusionSubmitDeadline( decimals=0, minimum=1, maximum=10 - ), - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" ) ] @@ -80,10 +72,6 @@ class FusionSubmitDeadline( attribute_values = self.get_attr_values_from_data( instance.data) - # add suspend_publish attributeValue to instance data - instance.data["suspend_publish"] = attribute_values[ - "suspend_publish"] - context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 6952604293..597a3cfc55 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -10,7 +10,6 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from ayon_core.lib import ( is_in_tests, - BoolDef, TextDef, NumberDef ) @@ -90,11 +89,6 @@ class HoudiniSubmitDeadline( @classmethod def get_attribute_defs(cls): return [ - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" - ), NumberDef( "priority", label="Priority", diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index ac01af901c..3138cd02e3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -76,11 +76,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, default=cls.use_gpu, label="Use GPU" ), - BoolDef( - "suspend_publish", - default=False, - label="Suspend publish" - ), BoolDef( "workfile_dependency", default=cls.workfile_dependency, @@ -100,10 +95,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, instance.data["attributeValues"] = self.get_attr_values_from_data( instance.data) - # add suspend_publish attributeValue to instance data - instance.data["suspend_publish"] = instance.data["attributeValues"][ - "suspend_publish"] - families = instance.data["families"] node = instance.data["transientData"]["node"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 50bd414587..800a906630 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -144,9 +144,6 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, instance_settings = self.get_attr_values_from_data(instance.data) initial_status = instance_settings.get("publishJobState", "Active") - # TODO: Remove this backwards compatibility of `suspend_publish` - if instance.data.get("suspend_publish"): - initial_status = "Suspended" args = [ "--headless", diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 84bac6d017..7546ce08d6 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -221,9 +221,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, instance_settings = self.get_attr_values_from_data(instance.data) initial_status = instance_settings.get("publishJobState", "Active") - # TODO: Remove this backwards compatibility of `suspend_publish` - if instance.data.get("suspend_publish"): - initial_status = "Suspended" args = [ "--headless", From 3bae4dba34c1dd141139b914b61d948c4afedec8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:13:45 +0100 Subject: [PATCH 216/727] 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 217/727] 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 941e80dd952864adcd4ba4386d5883434f5cd338 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 15:25:41 +0200 Subject: [PATCH 218/727] Use a general family for houdini farm rendering --- .../plugins/publish/collect_farm_instances.py | 23 +++++++++++++++++++ .../plugins/publish/increment_current_file.py | 4 ++-- .../deadline/plugins/publish/collect_pools.py | 2 +- .../publish/submit_houdini_render_deadline.py | 13 +++++++---- .../plugins/publish/submit_publish_job.py | 2 +- 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py new file mode 100644 index 0000000000..9a05ff75dd --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -0,0 +1,23 @@ +import pyblish.api + + +class CollectFarmInstances(pyblish.api.InstancePlugin): + """Collect instances for farm render.""" + + order = pyblish.api.CollectorOrder + families = ["mantra_rop"] + + hosts = ["houdini"] + targets = ["local", "remote"] + label = "Collect farm instances" + + def process(self, instance): + creator_attribute = instance.data["creator_attributes"] + farm_enabled = creator_attribute["farm"] + instance.data["farm"] = farm_enabled + if not farm_enabled: + self.log.debug("Render on farm is disabled. " + "Skipping farm collecting.") + return + + instance.data["families"].append("render.farm.hou") diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index 73145b211a..f94d1f10ed 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -3,7 +3,7 @@ import pyblish.api from ayon_core.lib import version_up from ayon_core.pipeline import registered_host from ayon_core.pipeline.publish import get_errored_plugins_from_context -from ayon_core.hosts.houdini.api import HoudiniHost + from ayon_core.pipeline.publish import KnownPublishError @@ -20,9 +20,9 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): families = ["workfile", "redshift_rop", "arnold_rop", - "mantra_rop", "karma_rop", "usdrender", + "render.farm.hou", "publish.hou"] optional = True diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 6923c2b16b..62d997eb2c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -43,9 +43,9 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "usdrender", "redshift_rop", "arnold_rop", - "mantra_rop", "karma_rop", "vray_rop", + "render.farm.hou", "publish.hou"] primary_pool = None diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 6952604293..d91fd895ad 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -73,9 +73,9 @@ class HoudiniSubmitDeadline( families = ["usdrender", "redshift_rop", "arnold_rop", - "mantra_rop", "karma_rop", - "vray_rop"] + "vray_rop", + "render.farm.hou"] targets = ["local"] use_published = True @@ -86,7 +86,7 @@ class HoudiniSubmitDeadline( priority = 50 chunk_size = 1 group = "" - + @classmethod def get_attribute_defs(cls): return [ @@ -194,7 +194,7 @@ class HoudiniSubmitDeadline( job_info.Pool = instance.data.get("primaryPool") job_info.SecondaryPool = instance.data.get("secondaryPool") - + if split_render_job and is_export_job: job_info.Priority = attribute_values.get( "export_priority", self.export_priority @@ -265,11 +265,14 @@ class HoudiniSubmitDeadline( # Output driver to render if job_type == "render": product_type = instance.data.get("productType") + rop_node = hou.node(instance.data.get("instance_node")) + node_type = rop_node.type().name() + if product_type == "arnold_rop": plugin_info = ArnoldRenderDeadlinePluginInfo( InputFile=instance.data["ifdFile"] ) - elif product_type == "mantra_rop": + elif node_type == "ifd": plugin_info = MantraRenderDeadlinePluginInfo( SceneFile=instance.data["ifdFile"], Version=hou_major_minor, diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 84bac6d017..82232c70c0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -92,7 +92,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "prerender.farm", "prerender.frames_farm", "renderlayer", "imagesequence", "vrayscene", "maxrender", - "arnold_rop", "mantra_rop", + "arnold_rop", "render.farm.hou", "karma_rop", "vray_rop", "redshift_rop"] From 37cecde8869dc273b1e783efe529b82b570a7794 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:08:03 +0200 Subject: [PATCH 219/727] add few keywords related to Houdini --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ee124ddc2d..5bc11031e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput" +ignore-words-list = "ayon,ynput,hda,parms" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true quiet-level = 3 From 71bf18910c8955523313a947f9be5b8adc5b9ad4 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:09:45 +0200 Subject: [PATCH 220/727] make codespell happy about integrate.py --- client/ayon_core/plugins/publish/integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index ce34f2e88b..38169ca2c3 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -42,7 +42,7 @@ def prepare_changes(old_entity, new_entity): Returns: dict[str, Any]: Changes that have new entity. - + """ changes = {} for key in set(new_entity.keys()): @@ -358,7 +358,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Compute the resource file infos once (files belonging to the # version instance instead of an individual representation) so - # we can re-use those file infos per representation + # we can reuse those file infos per representation resource_file_infos = self.get_files_info( resource_destinations, anatomy ) From 620538330c10e83ec95a90ec0a59b587ba09b7df Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:10:48 +0200 Subject: [PATCH 221/727] support local rendering for mantra_rop and add dedicated plugins --- .../plugins/create/create_mantra_rop.py | 43 +++++---- .../publish/collect_local_render_instances.py | 88 +++++++++++++++++++ .../publish/extract_mantra_local_render.py | 29 ++++++ .../plugins/publish/increment_current_file.py | 1 + .../validate_split_render_is_disabled.py | 65 ++++++++++++++ client/ayon_core/plugins/publish/integrate.py | 3 +- 6 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index f15f49f463..6dac3ff02a 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin to create Mantra ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance -from ayon_core.lib import EnumDef, BoolDef +from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef class CreateMantraROP(plugin.HoudiniCreator): @@ -23,12 +22,14 @@ class CreateMantraROP(plugin.HoudiniCreator): # Add chunk size attribute instance_data["chunkSize"] = 10 # Submit for job publishing - instance_data["farm"] = pre_create_data.get("farm") + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + creator_attributes["farm"] = pre_create_data.get("farm") instance = super(CreateMantraROP, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) @@ -78,21 +79,14 @@ class CreateMantraROP(plugin.HoudiniCreator): to_lock = ["productType", "id"] self.lock_parameters(instance_node, to_lock) - def get_pre_create_attr_defs(self): - attrs = super(CreateMantraROP, self).get_pre_create_attr_defs() - + def get_instance_attr_defs(self): image_format_enum = [ "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] - return attrs + [ - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("export_job", - label="Split export and render jobs", - default=self.export_job), + return [ + UILabelDef(label="Mantra Render Settings:"), EnumDef("image_format", image_format_enum, default="exr", @@ -101,5 +95,24 @@ class CreateMantraROP(plugin.HoudiniCreator): label="Override Camera Resolution", tooltip="Override the current camera " "resolution, recommended for IPR.", - default=False) + default=False), + UISeparatorDef(key="1"), + UILabelDef(label="Farm Render Options:"), + BoolDef("farm", + label="Submitting to Farm", + default=True), + BoolDef("export_job", + label="Split export and render jobs", + default=self.export_job), + UISeparatorDef(key="2"), + UILabelDef(label="Local Render Options:"), + BoolDef("skip_render", + label="Skip Render", + tooltip="Enable this option to skip render which publish existing frames.", + default=False), ] + + def get_pre_create_attr_defs(self): + attrs = super(CreateMantraROP, self).get_pre_create_attr_defs() + + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py new file mode 100644 index 0000000000..6b55bfffa4 --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -0,0 +1,88 @@ +import os +import pyblish.api + + +class CollectLocalRenderInstances(pyblish.api.InstancePlugin): + """Collect instances for local render. + + Agnostic Local Render Collector. + """ + + # this plugin runs after Collect Render Products + order = pyblish.api.CollectorOrder + 0.12 + families = ["mantra_rop"] + + hosts = ["houdini"] + targets = ["local", "remote"] + label = "Collect local render instances" + + def process(self, instance): + creator_attribute = instance.data["creator_attributes"] + farm_enabled = creator_attribute["farm"] + instance.data["farm"] = farm_enabled + if farm_enabled: + self.log.debug("Render on farm is enabled. " + "Skipping local render collecting.") + return + + # Create Instance for each AOV. + context = instance.context + expectedFiles = next(iter(instance.data["expectedFiles"]), {}) + + product_type = "render" # is always render + product_group = "render{Task}{productName}".format( + Task=self._capitalize(instance.data["task"]), + productName=self._capitalize(instance.data["productName"]) + ) # is always the group + + for aov_name, aov_filepaths in expectedFiles.items(): + # Some AOV instance data + # label = "{productName}_{AOV}".format( + # AOV=aov_name, + # productName=instance.data["productName"] + # ) + product_name = "render{Task}{productName}_{AOV}".format( + Task=self._capitalize(instance.data["task"]), + productName=self._capitalize(instance.data["productName"]), + AOV=aov_name + ) + + # Create instance for each AOV + aov_instance = context.create_instance(product_name) + + # Prepare Representation for each AOV + aov_filenames = [os.path.basename(path) for path in aov_filepaths] + staging_dir = os.path.dirname(aov_filepaths[0]) + ext = aov_filepaths[0].split(".")[-1] + + aov_instance.data.update({ + # 'label': label, + "task": instance.data["task"], + "folderPath": instance.data["folderPath"], + "frameStart": instance.data["frameStartHandle"], + "frameEnd": instance.data["frameEndHandle"], + "productType": product_type, + "productName": product_name, + "productGroup": product_group, + "tags": [], + "families": ["render.local.hou"], + "instance_node": instance.data["instance_node"], + "representations": [ + { + "stagingDir": staging_dir, + "ext": ext, + "name": ext, + "files": aov_filenames, + "frameStart": instance.data["frameStartHandle"], + "frameEnd": instance.data["frameEndHandle"] + } + ] + }) + + # Remove Mantra instance + # I can't remove it here as I still need it to trigger the render. + # context.remove(instance) + + @staticmethod + def _capitalize(word): + return word[:1].upper() + word[1:] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py new file mode 100644 index 0000000000..bb78f6b1ee --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py @@ -0,0 +1,29 @@ +import pyblish.api + +from ayon_core.pipeline import publish +from ayon_core.hosts.houdini.api.lib import render_rop +import hou + + +class ExtractMantraLocalRender(publish.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract Mantra Local Render" + hosts = ["houdini"] + families = ["mantra_rop"] + targets = ["local", "remote"] + + def process(self, instance): + if instance.data.get("farm"): + self.log.debug("Should be processed on farm, skipping.") + return + + creator_attribute = instance.data["creator_attributes"] + skip_render = creator_attribute["skip_render"] + + if skip_render: + self.log.debug("Skip render is enabled, skipping rendering.") + return + + ropnode = hou.node(instance.data.get("instance_node")) + render_rop(ropnode) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index f94d1f10ed..199843bc14 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -23,6 +23,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): "karma_rop", "usdrender", "render.farm.hou", + "render.local.hou", "publish.hou"] optional = True diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py new file mode 100644 index 0000000000..2d7a95e817 --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +import pyblish.api +import hou +from ayon_core.pipeline import PublishValidationError +from ayon_core.pipeline.publish import RepairAction + + +class DisableSplitExportAction(RepairAction): + label = "Disable Split Export" + + +class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): + """Validate the Instance has no current cooking errors.""" + + order = pyblish.api.ValidatorOrder + hosts = ["houdini"] + families = ["mantra_rop"] + label = "Validate Split Export Is Disabled" + actions = [DisableSplitExportAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + nodes = [n.path() for n in invalid] + raise PublishValidationError( + "See log for details. " + "Invalid nodes: {0}".format(nodes) + ) + + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + rop_node = hou.node(instance.data["instance_node"]) + + creator_attribute = instance.data["creator_attributes"] + farm_enabled = creator_attribute["farm"] + if farm_enabled: + cls.log.debug( + "Farm is enabled, skipping validation." + ) + return + + + split_enabled = creator_attribute["export_job"] + if split_enabled: + invalid.append(rop_node) + cls.log.error( + "Split Export must be disabled in local render instances." + ) + + return invalid + + @classmethod + def repair(cls, instance): + + create_context = instance.context.data["create_context"] + created_instance = create_context.get_instance_by_id( + instance.data["instance_id"]) + creator_attributes = created_instance["creator_attributes"] + # Disable export_job + creator_attributes["export_job"] = False + create_context.save_changes() diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 38169ca2c3..ea24112831 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -167,7 +167,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "uasset", "blendScene", "yeticacheUE", - "tycache" + "tycache", + "render.local.hou" ] default_template_name = "publish" From b35bc8c9d55b7b064f9d9b81cb0184c37925705a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:18:15 +0200 Subject: [PATCH 222/727] remove redundant code --- .../ayon_core/hosts/houdini/plugins/create/create_karma_rop.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index 9eb9d80cd3..e91ddbc0ac 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin to create Karma ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance from ayon_core.lib import BoolDef, EnumDef, NumberDef @@ -25,7 +24,7 @@ class CreateKarmaROP(plugin.HoudiniCreator): instance = super(CreateKarmaROP, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) From 068b9c3f4ec721d6404c5c26e78b6d03f5ae40aa Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:32:43 +0200 Subject: [PATCH 223/727] support local rendering for karma_rop --- .../plugins/create/create_karma_rop.py | 24 ++++++++++++------- .../plugins/publish/collect_farm_instances.py | 3 ++- .../publish/collect_local_render_instances.py | 3 ++- .../publish/extract_mantra_local_render.py | 7 +++--- .../plugins/publish/increment_current_file.py | 1 - .../deadline/plugins/publish/collect_pools.py | 1 - .../publish/submit_houdini_render_deadline.py | 1 - .../plugins/publish/submit_publish_job.py | 2 +- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index e91ddbc0ac..c791cfe647 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin to create Karma ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import BoolDef, EnumDef, NumberDef +from ayon_core.lib import BoolDef, EnumDef, NumberDef, UISeparatorDef, UILabelDef class CreateKarmaROP(plugin.HoudiniCreator): @@ -18,8 +18,6 @@ class CreateKarmaROP(plugin.HoudiniCreator): instance_data.update({"node_type": "karma"}) # Add chunk size attribute instance_data["chunkSize"] = 10 - # Submit for job publishing - instance_data["farm"] = pre_create_data.get("farm") instance = super(CreateKarmaROP, self).create( product_name, @@ -86,15 +84,13 @@ class CreateKarmaROP(plugin.HoudiniCreator): to_lock = ["productType", "id"] self.lock_parameters(instance_node, to_lock) - def get_pre_create_attr_defs(self): - attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs() - + def get_instance_attr_defs(self): image_format_enum = [ "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] - return attrs + [ + return [ BoolDef("farm", label="Submitting to Farm", default=True), @@ -112,5 +108,17 @@ class CreateKarmaROP(plugin.HoudiniCreator): decimals=0), BoolDef("cam_res", label="Camera Resolution", - default=False) + default=False), + UISeparatorDef(key="2"), + UILabelDef(label="Local Render Options:"), + BoolDef("skip_render", + label="Skip Render", + tooltip="Enable this option to skip render which publish existing frames.", + default=False), ] + + + def get_pre_create_attr_defs(self): + attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs() + + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index 9a05ff75dd..61894da98e 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -5,7 +5,8 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): """Collect instances for farm render.""" order = pyblish.api.CollectorOrder - families = ["mantra_rop"] + families = ["mantra_rop", + "karma_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 6b55bfffa4..e94e1187d7 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -10,7 +10,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # this plugin runs after Collect Render Products order = pyblish.api.CollectorOrder + 0.12 - families = ["mantra_rop"] + families = ["mantra_rop", + "karma_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py index bb78f6b1ee..a7967435c9 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py @@ -5,12 +5,13 @@ from ayon_core.hosts.houdini.api.lib import render_rop import hou -class ExtractMantraLocalRender(publish.Extractor): +class ExtractLocalRender(publish.Extractor): order = pyblish.api.ExtractorOrder - label = "Extract Mantra Local Render" + label = "Extract Local Render" hosts = ["houdini"] - families = ["mantra_rop"] + families = ["mantra_rop", + "karma_rop"] targets = ["local", "remote"] def process(self, instance): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index 199843bc14..5885fd8643 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -20,7 +20,6 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): families = ["workfile", "redshift_rop", "arnold_rop", - "karma_rop", "usdrender", "render.farm.hou", "render.local.hou", diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 62d997eb2c..bb556c2b9d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -43,7 +43,6 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "usdrender", "redshift_rop", "arnold_rop", - "karma_rop", "vray_rop", "render.farm.hou", "publish.hou"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index d91fd895ad..b433151b34 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -73,7 +73,6 @@ class HoudiniSubmitDeadline( families = ["usdrender", "redshift_rop", "arnold_rop", - "karma_rop", "vray_rop", "render.farm.hou"] targets = ["local"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 82232c70c0..69f626b602 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -93,7 +93,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "renderlayer", "imagesequence", "vrayscene", "maxrender", "arnold_rop", "render.farm.hou", - "karma_rop", "vray_rop", + "vray_rop", "redshift_rop"] aov_filter = [ From 5dd571dc2140601823f6fdf3c9da6a875eaebcce Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 16:33:48 +0200 Subject: [PATCH 224/727] algin file name to class name --- .../{extract_mantra_local_render.py => extract_local_render.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/ayon_core/hosts/houdini/plugins/publish/{extract_mantra_local_render.py => extract_local_render.py} (100%) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py similarity index 100% rename from client/ayon_core/hosts/houdini/plugins/publish/extract_mantra_local_render.py rename to client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py From e8907b00c171a6c6d76ebff009335aeeb83ab026 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 17:02:13 +0200 Subject: [PATCH 225/727] add parm to cspell ignore list --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5bc11031e2..58f07f0fa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput,hda,parms" +ignore-words-list = "ayon,ynput,hda,parms,parm" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true quiet-level = 3 From 313a7a2456a680b659999436924140afca18d2fc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 17:02:26 +0200 Subject: [PATCH 226/727] support local rendering for redshift_rop --- .../plugins/create/create_redshift_rop.py | 38 ++++++++++++------- .../plugins/publish/collect_farm_instances.py | 3 +- .../publish/collect_local_render_instances.py | 3 +- .../plugins/publish/extract_local_render.py | 3 +- .../plugins/publish/increment_current_file.py | 1 - .../validate_split_render_is_disabled.py | 7 ++-- .../deadline/plugins/publish/collect_pools.py | 1 - .../publish/submit_houdini_render_deadline.py | 3 +- .../plugins/publish/submit_publish_job.py | 3 +- 9 files changed, 36 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 1cd239e929..f6d42419f9 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -4,7 +4,7 @@ import hou # noqa from ayon_core.pipeline import CreatorError from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef, BoolDef +from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef class CreateRedshiftROP(plugin.HoudiniCreator): @@ -26,8 +26,6 @@ class CreateRedshiftROP(plugin.HoudiniCreator): instance_data.update({"node_type": "Redshift_ROP"}) # Add chunk size attribute instance_data["chunkSize"] = 10 - # Submit for job publishing - instance_data["farm"] = pre_create_data.get("farm") instance = super(CreateRedshiftROP, self).create( product_name, @@ -118,8 +116,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): return super(CreateRedshiftROP, self).remove_instances(instances) - def get_pre_create_attr_defs(self): - attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs() + def get_instance_attr_defs(self): image_format_enum = [ "exr", "tif", "jpg", "png", ] @@ -128,14 +125,8 @@ class CreateRedshiftROP(plugin.HoudiniCreator): "Full Multi-Layered EXR File" ] - - return attrs + [ - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), + return [ + UILabelDef(label="RedShift Render Settings:"), EnumDef("image_format", image_format_enum, default=self.ext, @@ -143,5 +134,24 @@ class CreateRedshiftROP(plugin.HoudiniCreator): EnumDef("multi_layered_mode", multi_layered_mode, default=self.multi_layered_mode, - label="Multi-Layered EXR") + label="Multi-Layered EXR"), + UISeparatorDef(key="1"), + UILabelDef(label="Farm Render Options:"), + BoolDef("farm", + label="Submitting to Farm", + default=True), + BoolDef("split_render", + label="Split export and render jobs", + default=self.split_render), + UISeparatorDef(key="2"), + UILabelDef(label="Local Render Options:"), + BoolDef("skip_render", + label="Skip Render", + tooltip="Enable this option to skip render which publish existing frames.", + default=False), ] + + def get_pre_create_attr_defs(self): + attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs() + + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index 61894da98e..ffdce1df32 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -6,7 +6,8 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder families = ["mantra_rop", - "karma_rop"] + "karma_rop", + "redshift_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index e94e1187d7..0b8e004873 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -11,7 +11,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # this plugin runs after Collect Render Products order = pyblish.api.CollectorOrder + 0.12 families = ["mantra_rop", - "karma_rop"] + "karma_rop", + "redshift_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index a7967435c9..e2f51d0dff 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -11,7 +11,8 @@ class ExtractLocalRender(publish.Extractor): label = "Extract Local Render" hosts = ["houdini"] families = ["mantra_rop", - "karma_rop"] + "karma_rop", + "redshift_rop"] targets = ["local", "remote"] def process(self, instance): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index 5885fd8643..b33b9cc344 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -18,7 +18,6 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] families = ["workfile", - "redshift_rop", "arnold_rop", "usdrender", "render.farm.hou", diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py index 2d7a95e817..cbed59fa3f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py @@ -14,7 +14,8 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder hosts = ["houdini"] - families = ["mantra_rop"] + families = ["mantra_rop", + "redshift_rop"] label = "Validate Split Export Is Disabled" actions = [DisableSplitExportAction] @@ -44,7 +45,7 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): return - split_enabled = creator_attribute["export_job"] + split_enabled = creator_attribute["split_render"] if split_enabled: invalid.append(rop_node) cls.log.error( @@ -61,5 +62,5 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): instance.data["instance_id"]) creator_attributes = created_instance["creator_attributes"] # Disable export_job - creator_attributes["export_job"] = False + creator_attributes["split_render"] = False create_context.save_changes() diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index bb556c2b9d..05b0e55548 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -41,7 +41,6 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "renderlayer", "maxrender", "usdrender", - "redshift_rop", "arnold_rop", "vray_rop", "render.farm.hou", diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index b433151b34..39a150ab2d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -71,7 +71,6 @@ class HoudiniSubmitDeadline( order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "redshift_rop", "arnold_rop", "vray_rop", "render.farm.hou"] @@ -280,7 +279,7 @@ class HoudiniSubmitDeadline( plugin_info = VrayRenderPluginInfo( InputFilename=instance.data["ifdFile"], ) - elif product_type == "redshift_rop": + elif node_type == "Redshift_ROP": plugin_info = RedshiftRenderPluginInfo( SceneFile=instance.data["ifdFile"] ) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 69f626b602..d70bc925ed 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -93,8 +93,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "renderlayer", "imagesequence", "vrayscene", "maxrender", "arnold_rop", "render.farm.hou", - "vray_rop", - "redshift_rop"] + "vray_rop"] aov_filter = [ { From 2228279a2d6cee48f7e0ed18ea07a993d1d4d077 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 17:09:36 +0200 Subject: [PATCH 227/727] refactor 'export_job' variable name into 'split_render' --- .../hosts/houdini/plugins/create/create_arnold_rop.py | 8 ++++---- .../hosts/houdini/plugins/create/create_mantra_rop.py | 8 ++++---- .../hosts/houdini/plugins/create/create_vray_rop.py | 10 +++++----- .../publish/validate_split_render_is_disabled.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index b7c5910a4f..68c68c5a16 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -14,7 +14,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): ext = "exr" # Default to split export and render jobs - export_job = True + split_render = True def create(self, product_name, instance_data, pre_create_data): import hou @@ -51,7 +51,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): "ar_exr_half_precision": 1 # half precision } - if pre_create_data.get("export_job"): + if pre_create_data.get("split_render"): ass_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.ass".format( export_dir=hou.text.expandString("$HIP/pyblish/ass/"), @@ -78,9 +78,9 @@ class CreateArnoldRop(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), - BoolDef("export_job", + BoolDef("split_render", label="Split export and render jobs", - default=self.export_job), + default=self.split_render), EnumDef("image_format", image_format_enum, default=self.ext, diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index 6dac3ff02a..58aadfd26c 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -12,7 +12,7 @@ class CreateMantraROP(plugin.HoudiniCreator): icon = "magic" # Default to split export and render jobs - export_job = True + split_render = True def create(self, product_name, instance_data, pre_create_data): import hou # noqa @@ -48,7 +48,7 @@ class CreateMantraROP(plugin.HoudiniCreator): "vm_picture": filepath, } - if pre_create_data.get("export_job"): + if pre_create_data.get("split_render"): ifd_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.ifd".format( export_dir=hou.text.expandString("$HIP/pyblish/ifd/"), @@ -101,9 +101,9 @@ class CreateMantraROP(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), - BoolDef("export_job", + BoolDef("split_render", label="Split export and render jobs", - default=self.export_job), + default=self.split_render), UISeparatorDef(key="2"), UILabelDef(label="Local Render Options:"), BoolDef("skip_render", diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 6b2396bffb..f7779cc67c 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -3,7 +3,7 @@ import hou from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance, CreatorError +from ayon_core.pipeline import CreatorError from ayon_core.lib import EnumDef, BoolDef @@ -17,7 +17,7 @@ class CreateVrayROP(plugin.HoudiniCreator): ext = "exr" # Default to split export and render jobs - export_job = True + split_render = True def create(self, product_name, instance_data, pre_create_data): @@ -55,7 +55,7 @@ class CreateVrayROP(plugin.HoudiniCreator): "SettingsEXR_bits_per_channel": "16" # half precision } - if pre_create_data.get("export_job"): + if pre_create_data.get("split_render"): scene_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.vrscene".format( export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), @@ -154,9 +154,9 @@ class CreateVrayROP(plugin.HoudiniCreator): BoolDef("farm", label="Submitting to Farm", default=True), - BoolDef("export_job", + BoolDef("split_render", label="Split export and render jobs", - default=self.export_job), + default=self.split_render), EnumDef("image_format", image_format_enum, default=self.ext, diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py index cbed59fa3f..d54f10b29b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py @@ -61,6 +61,6 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): created_instance = create_context.get_instance_by_id( instance.data["instance_id"]) creator_attributes = created_instance["creator_attributes"] - # Disable export_job + # Disable split_render creator_attributes["split_render"] = False create_context.save_changes() From 0ba5fee7e259543b0c8df66d9f2e898b46089ccc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 17:23:56 +0200 Subject: [PATCH 228/727] support local rendering for arnold_rop --- .../plugins/create/create_arnold_rop.py | 23 ++++++++++++------- .../plugins/publish/collect_farm_instances.py | 3 ++- .../publish/collect_local_render_instances.py | 3 ++- .../plugins/publish/extract_local_render.py | 3 ++- .../plugins/publish/increment_current_file.py | 1 - .../validate_split_render_is_disabled.py | 3 ++- .../deadline/plugins/publish/collect_pools.py | 1 - .../publish/submit_houdini_render_deadline.py | 3 +-- .../plugins/publish/submit_publish_job.py | 2 +- 9 files changed, 25 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index 68c68c5a16..c65c425a45 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,5 +1,5 @@ from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef, BoolDef +from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef class CreateArnoldRop(plugin.HoudiniCreator): @@ -25,8 +25,6 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Add chunk size attribute instance_data["chunkSize"] = 1 - # Submit for job publishing - instance_data["farm"] = pre_create_data.get("farm") instance = super(CreateArnoldRop, self).create( product_name, @@ -66,15 +64,13 @@ class CreateArnoldRop(plugin.HoudiniCreator): to_lock = ["productType", "id"] self.lock_parameters(instance_node, to_lock) - def get_pre_create_attr_defs(self): - attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs() - + def get_instance_attr_defs(self): image_format_enum = [ "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] - return attrs + [ + return [ BoolDef("farm", label="Submitting to Farm", default=True), @@ -84,5 +80,16 @@ class CreateArnoldRop(plugin.HoudiniCreator): EnumDef("image_format", image_format_enum, default=self.ext, - label="Image Format Options") + label="Image Format Options"), + UISeparatorDef(key="2"), + UILabelDef(label="Local Render Options:"), + BoolDef("skip_render", + label="Skip Render", + tooltip="Enable this option to skip render which publish existing frames.", + default=False), ] + + def get_pre_create_attr_defs(self): + attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs() + + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index ffdce1df32..afe67279e1 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -7,7 +7,8 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder families = ["mantra_rop", "karma_rop", - "redshift_rop"] + "redshift_rop", + "arnold_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 0b8e004873..9b121a6894 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -12,7 +12,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.12 families = ["mantra_rop", "karma_rop", - "redshift_rop"] + "redshift_rop", + "arnold_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index e2f51d0dff..1fce9dc87f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -12,7 +12,8 @@ class ExtractLocalRender(publish.Extractor): hosts = ["houdini"] families = ["mantra_rop", "karma_rop", - "redshift_rop"] + "redshift_rop", + "arnold_rop"] targets = ["local", "remote"] def process(self, instance): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index b33b9cc344..acb66afa4e 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -18,7 +18,6 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] families = ["workfile", - "arnold_rop", "usdrender", "render.farm.hou", "render.local.hou", diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py index d54f10b29b..38912f434f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py @@ -15,7 +15,8 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder hosts = ["houdini"] families = ["mantra_rop", - "redshift_rop"] + "redshift_rop", + "arnold_rop"] label = "Validate Split Export Is Disabled" actions = [DisableSplitExportAction] diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 05b0e55548..c30c7fb0f2 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -41,7 +41,6 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "renderlayer", "maxrender", "usdrender", - "arnold_rop", "vray_rop", "render.farm.hou", "publish.hou"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 39a150ab2d..17a0b4ad32 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -71,7 +71,6 @@ class HoudiniSubmitDeadline( order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "arnold_rop", "vray_rop", "render.farm.hou"] targets = ["local"] @@ -266,7 +265,7 @@ class HoudiniSubmitDeadline( rop_node = hou.node(instance.data.get("instance_node")) node_type = rop_node.type().name() - if product_type == "arnold_rop": + if node_type == "arnold": plugin_info = ArnoldRenderDeadlinePluginInfo( InputFile=instance.data["ifdFile"] ) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index d70bc925ed..6171d7135f 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -92,7 +92,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "prerender.farm", "prerender.frames_farm", "renderlayer", "imagesequence", "vrayscene", "maxrender", - "arnold_rop", "render.farm.hou", + "render.farm.hou", "vray_rop"] aov_filter = [ From c7e0821ff57e046c8de87c37d0779d296c8ca407 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 17:42:49 +0200 Subject: [PATCH 229/727] support local rendering for vray_rop --- .../houdini/plugins/create/create_vray_rop.py | 36 ++++++++++++------- .../plugins/publish/collect_farm_instances.py | 3 +- .../publish/collect_local_render_instances.py | 3 +- .../plugins/publish/extract_local_render.py | 3 +- .../validate_split_render_is_disabled.py | 3 +- .../deadline/plugins/publish/collect_pools.py | 1 - .../publish/submit_houdini_render_deadline.py | 3 +- .../plugins/publish/submit_publish_job.py | 3 +- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index f7779cc67c..682eec379e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -4,7 +4,7 @@ import hou from ayon_core.hosts.houdini.api import plugin from ayon_core.pipeline import CreatorError -from ayon_core.lib import EnumDef, BoolDef +from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef class CreateVrayROP(plugin.HoudiniCreator): @@ -25,8 +25,6 @@ class CreateVrayROP(plugin.HoudiniCreator): instance_data.update({"node_type": "vray_renderer"}) # Add chunk size attribute instance_data["chunkSize"] = 10 - # Submit for job publishing - instance_data["farm"] = pre_create_data.get("farm") instance = super(CreateVrayROP, self).create( product_name, @@ -143,20 +141,13 @@ class CreateVrayROP(plugin.HoudiniCreator): return super(CreateVrayROP, self).remove_instances(instances) - def get_pre_create_attr_defs(self): - attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() + def get_instance_attr_defs(self): image_format_enum = [ "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] - return attrs + [ - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), + return [ EnumDef("image_format", image_format_enum, default=self.ext, @@ -170,5 +161,24 @@ class CreateVrayROP(plugin.HoudiniCreator): label="Render Element", tooltip="Create Render Element Node " "if enabled", - default=False) + default=False), + UISeparatorDef(key="1"), + UILabelDef(label="Farm Render Options:"), + BoolDef("farm", + label="Submitting to Farm", + default=True), + BoolDef("split_render", + label="Split export and render jobs", + default=self.split_render), + UISeparatorDef(key="2"), + UILabelDef(label="Local Render Options:"), + BoolDef("skip_render", + label="Skip Render", + tooltip="Enable this option to skip render which publish existing frames.", + default=False), ] + + def get_pre_create_attr_defs(self): + attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() + + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index afe67279e1..37a979d94b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -8,7 +8,8 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): families = ["mantra_rop", "karma_rop", "redshift_rop", - "arnold_rop"] + "arnold_rop", + "vray_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 9b121a6894..9ad44da978 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -13,7 +13,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): families = ["mantra_rop", "karma_rop", "redshift_rop", - "arnold_rop"] + "arnold_rop", + "vray_rop"] hosts = ["houdini"] targets = ["local", "remote"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index 1fce9dc87f..5e89e760ab 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -13,7 +13,8 @@ class ExtractLocalRender(publish.Extractor): families = ["mantra_rop", "karma_rop", "redshift_rop", - "arnold_rop"] + "arnold_rop", + "vray_rop"] targets = ["local", "remote"] def process(self, instance): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py index 38912f434f..72ccb90e86 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py @@ -16,7 +16,8 @@ class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): hosts = ["houdini"] families = ["mantra_rop", "redshift_rop", - "arnold_rop"] + "arnold_rop", + "vray_rop"] label = "Validate Split Export Is Disabled" actions = [DisableSplitExportAction] diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index c30c7fb0f2..76b397eee0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -41,7 +41,6 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "renderlayer", "maxrender", "usdrender", - "vray_rop", "render.farm.hou", "publish.hou"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 17a0b4ad32..404c7ade04 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -71,7 +71,6 @@ class HoudiniSubmitDeadline( order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "vray_rop", "render.farm.hou"] targets = ["local"] use_published = True @@ -274,7 +273,7 @@ class HoudiniSubmitDeadline( SceneFile=instance.data["ifdFile"], Version=hou_major_minor, ) - elif product_type == "vray_rop": + elif node_type == "vray_renderer": plugin_info = VrayRenderPluginInfo( InputFilename=instance.data["ifdFile"], ) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 6171d7135f..f8df1b4d4c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -92,8 +92,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "prerender.farm", "prerender.frames_farm", "renderlayer", "imagesequence", "vrayscene", "maxrender", - "render.farm.hou", - "vray_rop"] + "render.farm.hou"] aov_filter = [ { From bf18daafe5c5d3128158b536cbd796fe856f81fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:42:00 +0100 Subject: [PATCH 230/727] 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 231/727] 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 232/727] 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 233/727] 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 234/727] 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 235/727] 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 236/727] 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 237/727] 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 a18737523115d95c9f294e4a088d8f0d1c4df027 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 17:37:45 +0800 Subject: [PATCH 238/727] change to use task enntity for validators which are related to context setting --- client/ayon_core/hosts/max/api/lib.py | 61 +++++++++++++------ .../plugins/publish/validate_frame_range.py | 2 +- .../publish/validate_resolution_setting.py | 10 +-- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 5f13856c9b..595c2d8841 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -8,7 +8,12 @@ from typing import Any, Dict, Union import six import ayon_api -from ayon_core.pipeline import get_current_project_name, colorspace +from ayon_core.pipeline import ( + get_current_project_name, + get_current_folder_path, + get_current_task_name, + colorspace +) from ayon_core.settings import get_project_settings from ayon_core.pipeline.context_tools import ( get_current_project_folder, @@ -221,18 +226,15 @@ def reset_scene_resolution(): scene resolution can be overwritten by a folder if the folder.attrib contains any information regarding scene resolution. """ - - folder_entity = get_current_project_folder( - fields={"attrib.resolutionWidth", "attrib.resolutionHeight"} - ) - folder_attributes = folder_entity["attrib"] - width = int(folder_attributes["resolutionWidth"]) - height = int(folder_attributes["resolutionHeight"]) + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + width = int(task_attributes["resolutionWidth"]) + height = int(task_attributes["resolutionHeight"]) set_scene_resolution(width, height) -def get_frame_range(folder_entiy=None) -> Union[Dict[str, Any], None]: +def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: """Get the current folder frame range and handles. Args: @@ -242,20 +244,19 @@ def get_frame_range(folder_entiy=None) -> Union[Dict[str, Any], None]: dict: with frame start, frame end, handle start, handle end. """ # Set frame start/end - if folder_entiy is None: - folder_entiy = get_current_project_folder() - - folder_attributes = folder_entiy["attrib"] - frame_start = folder_attributes.get("frameStart") - frame_end = folder_attributes.get("frameEnd") + if task_entity is None: + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + frame_start = task_attributes.get("frameStart") + frame_end = task_attributes.get("frameEnd") if frame_start is None or frame_end is None: return {} frame_start = int(frame_start) frame_end = int(frame_end) - handle_start = int(folder_attributes.get("handleStart", 0)) - handle_end = int(folder_attributes.get("handleEnd", 0)) + handle_start = int(task_attributes.get("handleStart", 0)) + handle_end = int(task_attributes.get("handleEnd", 0)) frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end @@ -281,9 +282,9 @@ def reset_frame_range(fps: bool = True): scene frame rate in frames-per-second. """ if fps: - project_name = get_current_project_name() - project_entity = ayon_api.get_project(project_name) - fps_number = float(project_entity["attrib"].get("fps")) + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + fps_number = float(task_attributes.get("fps")) rt.frameRate = fps_number frame_range = get_frame_range() @@ -356,6 +357,26 @@ def get_max_version(): return max_info[7] +def get_current_task_entity(): + """Function to get current task entity data + + Returns: + dict: data of task entity + """ + project_name = get_current_project_name() + folder_path = get_current_folder_path() + task_name = get_current_task_name() + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"id"}) + task_entity = ayon_api.get_task_by_name( + project_name, + folder_entity["id"], + task_name, + fields={"attrib"} + ) + return task_entity + + def is_headless(): """Check if 3dsMax runs in batch mode. If it returns True, it runs in 3dsbatch.exe diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py b/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py index 2f4ec5f86c..11b55232d5 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py @@ -42,7 +42,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, return frame_range = get_frame_range( - instance.data["folderEntity"]) + instance.data["taskEntity"]) inst_frame_start = instance.data.get("frameStartHandle") inst_frame_end = instance.data.get("frameEndHandle") diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index f499f851f1..7f64a413cf 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -42,11 +42,11 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, "on asset or shot.") def get_folder_resolution(self, instance): - folder_entity = instance.data["folderEntity"] - if folder_entity: - folder_attributes = folder_entity["attrib"] - width = folder_attributes["resolutionWidth"] - height = folder_attributes["resolutionHeight"] + task_entity = instance.data.get("taskEntity") + if task_entity: + task_attributes = task_entity["attrib"] + width = task_attributes["resolutionWidth"] + height = task_attributes["resolutionHeight"] return int(width), int(height) # Defaults if not found in folder entity From 066262b6ad5d539930b869a76c2994894e7b83ce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:02:13 +0800 Subject: [PATCH 239/727] code tweaks on the reset frame range --- client/ayon_core/hosts/max/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 595c2d8841..988f069dfa 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -247,16 +247,16 @@ def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: if task_entity is None: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - frame_start = task_attributes.get("frameStart") - frame_end = task_attributes.get("frameEnd") + frame_start = task_attributes["frameStart"] + frame_end = task_attributes["frameEnd"] if frame_start is None or frame_end is None: return {} frame_start = int(frame_start) frame_end = int(frame_end) - handle_start = int(task_attributes.get("handleStart", 0)) - handle_end = int(task_attributes.get("handleEnd", 0)) + handle_start = int(task_attributes["handleStart"]) + handle_end = int(task_attributes["handleEnd"]) frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end From 30f1145c8b875e6a23b3b1c79beaacb53950d9e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:14:12 +0800 Subject: [PATCH 240/727] clean up the code in regard to the change of getting task entity data --- client/ayon_core/hosts/max/api/lib.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 988f069dfa..42084f40f4 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -247,14 +247,8 @@ def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: if task_entity is None: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - frame_start = task_attributes["frameStart"] - frame_end = task_attributes["frameEnd"] - - if frame_start is None or frame_end is None: - return {} - - frame_start = int(frame_start) - frame_end = int(frame_end) + frame_start = int(task_attributes["frameStart"]) + frame_end = int(task_attributes["frameEnd"]) handle_start = int(task_attributes["handleStart"]) handle_end = int(task_attributes["handleEnd"]) frame_start_handle = frame_start - handle_start @@ -284,7 +278,7 @@ def reset_frame_range(fps: bool = True): if fps: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - fps_number = float(task_attributes.get("fps")) + fps_number = float(task_attributes["fps"]) rt.frameRate = fps_number frame_range = get_frame_range() From bb91cc523e47a3aa1308149cfd2837b7d2061bf5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:16:01 +0800 Subject: [PATCH 241/727] update docstring for 'get_frame_range' --- client/ayon_core/hosts/max/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 42084f40f4..d8133f4db7 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -235,10 +235,10 @@ def reset_scene_resolution(): def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: - """Get the current folder frame range and handles. + """Get the current task frame range and handles Args: - folder_entiy (dict): Folder eneity. + task_entity (dict): Task Entity. Returns: dict: with frame start, frame end, handle start, handle end. From bf26eba7797d5f53a5eda6b2836c43276443d7dc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:45:57 +0200 Subject: [PATCH 242/727] 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 243/727] 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 4390318f208afdd6a13723275d688d3b8e5c388d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 22:04:55 +0800 Subject: [PATCH 244/727] tweak on validation error message --- .../max/plugins/publish/validate_instance_in_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py index 963a601009..5107665235 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py @@ -38,15 +38,15 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_label = "{} > {}".format(*context) instance_label = "{} > {}".format(folderPath, task) message = ( - "Instance '{}' publishes to different context than current " - "context: {}. Current context: {}".format( + "Instance '{}' publishes to different context(folder or task) " + "than current context: {}. Current context: {}".format( instance.name, instance_label, context_label ) ) raise PublishValidationError( message=message, description=( - "## Publishing to a different context data\n" + "## Publishing to a different context data(folder or task)\n" "There are publish instances present which are publishing " "into a different folder path or task than your current context.\n\n" "Usually this is not what you want but there can be cases " From bcb1c2a0ba3b49c43a08507ad52a9fdce07037ef Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 21:42:56 +0200 Subject: [PATCH 245/727] Use render targets in a similar fashion to Nuke + set houdini parms according to render target value --- .../plugins/create/create_arnold_rop.py | 30 +++++++-------- .../plugins/create/create_karma_rop.py | 23 +++++++----- .../plugins/create/create_mantra_rop.py | 37 +++++++------------ .../plugins/create/create_redshift_rop.py | 33 +++++++---------- .../houdini/plugins/create/create_vray_rop.py | 32 +++++++--------- .../plugins/publish/collect_farm_instances.py | 34 +++++++++++++++-- .../publish/collect_local_render_instances.py | 14 ++++--- .../plugins/publish/extract_local_render.py | 21 ++++++++++- 8 files changed, 128 insertions(+), 96 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index c65c425a45..07c1c98a28 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,5 +1,5 @@ from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef +from ayon_core.lib import EnumDef class CreateArnoldRop(plugin.HoudiniCreator): @@ -13,8 +13,8 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Default extension ext = "exr" - # Default to split export and render jobs - split_render = True + # Default render target + render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): import hou @@ -49,7 +49,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): "ar_exr_half_precision": 1 # half precision } - if pre_create_data.get("split_render"): + if pre_create_data.get("render_target") == "farm_split": ass_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.ass".format( export_dir=hou.text.expandString("$HIP/pyblish/ass/"), @@ -69,24 +69,22 @@ class CreateArnoldRop(plugin.HoudiniCreator): "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] + render_target_items = { + "local": "Local machine rendering", + "local_no_render": "Use existing frames (local)", + "farm": "Farm Rendering", + "farm_split": "Farm Rendering - Split export & render jobs", + } return [ - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), + EnumDef("render_target", + items=render_target_items, + label="Render target", + default=self.render_target), EnumDef("image_format", image_format_enum, default=self.ext, label="Image Format Options"), - UISeparatorDef(key="2"), - UILabelDef(label="Local Render Options:"), - BoolDef("skip_render", - label="Skip Render", - tooltip="Enable this option to skip render which publish existing frames.", - default=False), ] def get_pre_create_attr_defs(self): diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index c791cfe647..5d56150df9 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin to create Karma ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import BoolDef, EnumDef, NumberDef, UISeparatorDef, UILabelDef +from ayon_core.lib import BoolDef, EnumDef, NumberDef class CreateKarmaROP(plugin.HoudiniCreator): @@ -11,6 +11,9 @@ class CreateKarmaROP(plugin.HoudiniCreator): product_type = "karma_rop" icon = "magic" + # Default render target + render_target = "farm" + def create(self, product_name, instance_data, pre_create_data): import hou # noqa @@ -89,11 +92,17 @@ class CreateKarmaROP(plugin.HoudiniCreator): "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] + render_target_items = { + "local": "Local machine rendering", + "local_no_render": "Use existing frames (local)", + "farm": "Farm Rendering", + } return [ - BoolDef("farm", - label="Submitting to Farm", - default=True), + EnumDef("render_target", + items=render_target_items, + label="Render target", + default=self.render_target), EnumDef("image_format", image_format_enum, default="exr", @@ -109,12 +118,6 @@ class CreateKarmaROP(plugin.HoudiniCreator): BoolDef("cam_res", label="Camera Resolution", default=False), - UISeparatorDef(key="2"), - UILabelDef(label="Local Render Options:"), - BoolDef("skip_render", - label="Skip Render", - tooltip="Enable this option to skip render which publish existing frames.", - default=False), ] diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index 58aadfd26c..6705621f58 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin to create Mantra ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef +from ayon_core.lib import EnumDef, BoolDef class CreateMantraROP(plugin.HoudiniCreator): @@ -11,8 +11,8 @@ class CreateMantraROP(plugin.HoudiniCreator): product_type = "mantra_rop" icon = "magic" - # Default to split export and render jobs - split_render = True + # Default render target + render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): import hou # noqa @@ -21,10 +21,6 @@ class CreateMantraROP(plugin.HoudiniCreator): instance_data.update({"node_type": "ifd"}) # Add chunk size attribute instance_data["chunkSize"] = 10 - # Submit for job publishing - creator_attributes = instance_data.setdefault( - "creator_attributes", dict()) - creator_attributes["farm"] = pre_create_data.get("farm") instance = super(CreateMantraROP, self).create( product_name, @@ -48,7 +44,7 @@ class CreateMantraROP(plugin.HoudiniCreator): "vm_picture": filepath, } - if pre_create_data.get("split_render"): + if pre_create_data.get("render_target") == "farm_split": ifd_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.ifd".format( export_dir=hou.text.expandString("$HIP/pyblish/ifd/"), @@ -84,9 +80,18 @@ class CreateMantraROP(plugin.HoudiniCreator): "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] + render_target_items = { + "local": "Local machine rendering", + "local_no_render": "Use existing frames (local)", + "farm": "Farm Rendering", + "farm_split": "Farm Rendering - Split export & render jobs", + } return [ - UILabelDef(label="Mantra Render Settings:"), + EnumDef("render_target", + items=render_target_items, + label="Render target", + default=self.render_target), EnumDef("image_format", image_format_enum, default="exr", @@ -96,20 +101,6 @@ class CreateMantraROP(plugin.HoudiniCreator): tooltip="Override the current camera " "resolution, recommended for IPR.", default=False), - UISeparatorDef(key="1"), - UILabelDef(label="Farm Render Options:"), - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), - UISeparatorDef(key="2"), - UILabelDef(label="Local Render Options:"), - BoolDef("skip_render", - label="Skip Render", - tooltip="Enable this option to skip render which publish existing frames.", - default=False), ] def get_pre_create_attr_defs(self): diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index f6d42419f9..02c3ed2fc0 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -4,7 +4,7 @@ import hou # noqa from ayon_core.pipeline import CreatorError from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef +from ayon_core.lib import EnumDef class CreateRedshiftROP(plugin.HoudiniCreator): @@ -17,8 +17,8 @@ class CreateRedshiftROP(plugin.HoudiniCreator): ext = "exr" multi_layered_mode = "No Multi-Layered EXR File" - # Default to split export and render jobs - split_render = True + # Default render target + render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): @@ -97,7 +97,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): rs_filepath = f"{export_dir}{product_name}/{product_name}.$F4.rs" parms["RS_archive_file"] = rs_filepath - if pre_create_data.get("split_render", self.split_render): + if pre_create_data.get("render_target") == "farm_split": parms["RS_archive_enable"] = 1 instance_node.setParms(parms) @@ -124,9 +124,18 @@ class CreateRedshiftROP(plugin.HoudiniCreator): "No Multi-Layered EXR File", "Full Multi-Layered EXR File" ] + render_target_items = { + "local": "Local machine rendering", + "local_no_render": "Use existing frames (local)", + "farm": "Farm Rendering", + "farm_split": "Farm Rendering - Split export & render jobs", + } return [ - UILabelDef(label="RedShift Render Settings:"), + EnumDef("render_target", + items=render_target_items, + label="Render target", + default=self.render_target), EnumDef("image_format", image_format_enum, default=self.ext, @@ -135,20 +144,6 @@ class CreateRedshiftROP(plugin.HoudiniCreator): multi_layered_mode, default=self.multi_layered_mode, label="Multi-Layered EXR"), - UISeparatorDef(key="1"), - UILabelDef(label="Farm Render Options:"), - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), - UISeparatorDef(key="2"), - UILabelDef(label="Local Render Options:"), - BoolDef("skip_render", - label="Skip Render", - tooltip="Enable this option to skip render which publish existing frames.", - default=False), ] def get_pre_create_attr_defs(self): diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 682eec379e..147a34191f 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -4,7 +4,7 @@ import hou from ayon_core.hosts.houdini.api import plugin from ayon_core.pipeline import CreatorError -from ayon_core.lib import EnumDef, BoolDef, UISeparatorDef, UILabelDef +from ayon_core.lib import EnumDef, BoolDef class CreateVrayROP(plugin.HoudiniCreator): @@ -16,8 +16,8 @@ class CreateVrayROP(plugin.HoudiniCreator): icon = "magic" ext = "exr" - # Default to split export and render jobs - split_render = True + # Default render target + render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): @@ -53,7 +53,7 @@ class CreateVrayROP(plugin.HoudiniCreator): "SettingsEXR_bits_per_channel": "16" # half precision } - if pre_create_data.get("split_render"): + if pre_create_data.get("render_target") == "farm_split": scene_filepath = \ "{export_dir}{product_name}/{product_name}.$F4.vrscene".format( export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"), @@ -146,8 +146,18 @@ class CreateVrayROP(plugin.HoudiniCreator): "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", "rad", "rat", "rta", "sgi", "tga", "tif", ] + render_target_items = { + "local": "Local machine rendering", + "local_no_render": "Use existing frames (local)", + "farm": "Farm Rendering", + "farm_split": "Farm Rendering - Split export & render jobs", + } return [ + EnumDef("render_target", + items=render_target_items, + label="Render target", + default=self.render_target), EnumDef("image_format", image_format_enum, default=self.ext, @@ -162,20 +172,6 @@ class CreateVrayROP(plugin.HoudiniCreator): tooltip="Create Render Element Node " "if enabled", default=False), - UISeparatorDef(key="1"), - UILabelDef(label="Farm Render Options:"), - BoolDef("farm", - label="Submitting to Farm", - default=True), - BoolDef("split_render", - label="Split export and render jobs", - default=self.split_render), - UISeparatorDef(key="2"), - UILabelDef(label="Local Render Options:"), - BoolDef("skip_render", - label="Skip Render", - tooltip="Enable this option to skip render which publish existing frames.", - default=False), ] def get_pre_create_attr_defs(self): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index 37a979d94b..56a2b42940 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -16,12 +16,40 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): label = "Collect farm instances" def process(self, instance): + import hou + creator_attribute = instance.data["creator_attributes"] - farm_enabled = creator_attribute["farm"] - instance.data["farm"] = farm_enabled - if not farm_enabled: + product_type = instance.data["productType"] + rop_node = hou.node(instance.data.get("instance_node")) + + # Align split parameter value on rop node to the render target. + if creator_attribute.get("render_target") == "farm_split": + if product_type == "arnold_rop": + rop_node.setParms({"ar_ass_export_enable": 1}) + elif product_type == "mantra_rop": + rop_node.setParms({"soho_outputmode": 1}) + elif product_type == "redshift_rop": + rop_node.setParms({"RS_archive_enable": 1}) + elif product_type == "vray_rop": + rop_node.setParms({"render_export_mode": "2"}) + else: + if product_type == "arnold_rop": + rop_node.setParms({"ar_ass_export_enable": 0}) + elif product_type == "mantra_rop": + rop_node.setParms({"soho_outputmode": 0}) + elif product_type == "redshift_rop": + rop_node.setParms({"RS_archive_enable": 0}) + elif product_type == "vray_rop": + rop_node.setParms({"render_export_mode": "1"}) + + # Collect Render Target + if creator_attribute.get("render_target") not in { + "farm_split", "farm" + }: + instance.data["farm"] = False self.log.debug("Render on farm is disabled. " "Skipping farm collecting.") return + instance.data["farm"] = True instance.data["families"].append("render.farm.hou") diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 9ad44da978..194a05f42d 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -21,10 +21,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): label = "Collect local render instances" def process(self, instance): - creator_attribute = instance.data["creator_attributes"] - farm_enabled = creator_attribute["farm"] - instance.data["farm"] = farm_enabled - if farm_enabled: + + if instance.data["farm"]: self.log.debug("Render on farm is enabled. " "Skipping local render collecting.") return @@ -45,7 +43,13 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # AOV=aov_name, # productName=instance.data["productName"] # ) - product_name = "render{Task}{productName}_{AOV}".format( + name_template = "render{Task}{productName}_{AOV}" + if not aov_name: + # This is done to remove the trailing `_` + # if aov name is an empty string. + name_template = "render{Task}{productName}" + + product_name = name_template.format( Task=self._capitalize(instance.data["task"]), productName=self._capitalize(instance.data["productName"]), AOV=aov_name diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index 5e89e760ab..120e5563e9 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -23,11 +23,28 @@ class ExtractLocalRender(publish.Extractor): return creator_attribute = instance.data["creator_attributes"] - skip_render = creator_attribute["skip_render"] - if skip_render: + if creator_attribute.get("render_target") == "local_no_render": self.log.debug("Skip render is enabled, skipping rendering.") return + # Make sure split parameter is turned off. + # Otherwise, render nodes will generate intermediate + # render files instead of render. + product_type = instance.data["productType"] + rop_node = hou.node(instance.data.get("instance_node")) + + if product_type == "arnold_rop": + rop_node.setParms({"ar_ass_export_enable": 0}) + elif product_type == "mantra_rop": + rop_node.setParms({"soho_outputmode": 0}) + elif product_type == "redshift_rop": + rop_node.setParms({"RS_archive_enable": 0}) + elif product_type == "vray_rop": + rop_node.setParms({"render_export_mode": "1"}) + ropnode = hou.node(instance.data.get("instance_node")) render_rop(ropnode) + + # TODO: Check for missing frames. + # self.log.debug(instance.data["expectedFiles"]) From bca10c7c7d0d4d8457a2ba25730dd37ebd5bbff9 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 21:43:36 +0200 Subject: [PATCH 246/727] remove unnecessary validator --- .../validate_split_render_is_disabled.py | 68 ------------------- 1 file changed, 68 deletions(-) delete mode 100644 client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py deleted file mode 100644 index 72ccb90e86..0000000000 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_split_render_is_disabled.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -import hou -from ayon_core.pipeline import PublishValidationError -from ayon_core.pipeline.publish import RepairAction - - -class DisableSplitExportAction(RepairAction): - label = "Disable Split Export" - - -class ValidateSplitExportIsDisabled(pyblish.api.InstancePlugin): - """Validate the Instance has no current cooking errors.""" - - order = pyblish.api.ValidatorOrder - hosts = ["houdini"] - families = ["mantra_rop", - "redshift_rop", - "arnold_rop", - "vray_rop"] - label = "Validate Split Export Is Disabled" - actions = [DisableSplitExportAction] - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - nodes = [n.path() for n in invalid] - raise PublishValidationError( - "See log for details. " - "Invalid nodes: {0}".format(nodes) - ) - - - @classmethod - def get_invalid(cls, instance): - - invalid = [] - rop_node = hou.node(instance.data["instance_node"]) - - creator_attribute = instance.data["creator_attributes"] - farm_enabled = creator_attribute["farm"] - if farm_enabled: - cls.log.debug( - "Farm is enabled, skipping validation." - ) - return - - - split_enabled = creator_attribute["split_render"] - if split_enabled: - invalid.append(rop_node) - cls.log.error( - "Split Export must be disabled in local render instances." - ) - - return invalid - - @classmethod - def repair(cls, instance): - - create_context = instance.context.data["create_context"] - created_instance = create_context.get_instance_by_id( - instance.data["instance_id"]) - creator_attributes = created_instance["creator_attributes"] - # Disable split_render - creator_attributes["split_render"] = False - create_context.save_changes() From 4a5f0ebc92113f2dafd263ac3b771fb2f194563b Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 21:44:54 +0200 Subject: [PATCH 247/727] set rsnode parms according to render target value before collecting expected files --- .../plugins/publish/collect_redshift_rop.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py index 55a55bb12a..191a9c1ebc 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -60,11 +60,27 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) - full_exr_mode = (rop.evalParm("RS_outputMultilayerMode") == "2") - if full_exr_mode: - # Ignore beauty suffix if full mode is enabled - # As this is what the rop does. - beauty_suffix = "" + # Set MultiLayer Mode. + creator_attribute = instance.data["creator_attributes"] + ext = creator_attribute.get("image_format") + multi_layered_mode = creator_attribute.get("multi_layered_mode") + full_exr_mode = False + if ext == "exr": + if multi_layered_mode == "No Multi-Layered EXR File": + rop.setParms({ + "RS_outputMultilayerMode": "1", + "RS_aovMultipart": False + }) + full_exr_mode = True + # Ignore beauty suffix if full mode is enabled + # As this is what the rop does. + beauty_suffix = "" + + elif multi_layered_mode == "Full Multi-Layered EXR File": + rop.setParms({ + "RS_outputMultilayerMode": "2", + "RS_aovMultipart": True + }) # Default beauty/main layer AOV beauty_product = self.get_render_product_name( @@ -75,7 +91,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): beauty_suffix: self.generate_expected_files(instance, beauty_product) } - + aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode() if aovs_rop: rop = aovs_rop @@ -98,7 +114,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): if rop.parm(f"RS_aovID_{i}").evalAsString() == "CRYPTOMATTE" or \ not full_exr_mode: - + aov_product = self.get_render_product_name(aov_prefix, aov_suffix) render_products.append(aov_product) From 7fde8ac0bcad96fc273e309bf2f90d7f938d9e1f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 Apr 2024 17:23:33 +0800 Subject: [PATCH 248/727] optional validator for resolution settings in review product type --- .../hosts/max/plugins/publish/validate_resolution_setting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 7f64a413cf..48e2e1d45d 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -15,7 +15,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender"] + families = ["maxrender", "review"] hosts = ["max"] label = "Validate Resolution Setting" optional = True From ab74098b7bd49494ee7a1cf60e33605b602495a4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 3 Apr 2024 14:07:39 +0200 Subject: [PATCH 249/727] AY-745 - provide default values for new Settings field --- server_addon/deadline/server/settings/main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 4289e3d335..8a6b0e3b37 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -34,8 +34,11 @@ class ServerItemSubmodel(BaseSettingsModel): _layout = "compact" name: str = SettingsField(title="Name") value: str = SettingsField(title="Url") - require_authentication: bool = SettingsField(title="Require authentication") - ssl: bool = SettingsField(title="SSL") + require_authentication: bool = SettingsField( + False, + title="Require authentication") + ssl: bool = SettingsField(False, + title="SSL") class DeadlineSettings(BaseSettingsModel): From ab408bd177972e1d49778fa5da1c59c7fcba04ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:16:06 +0200 Subject: [PATCH 250/727] 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 251/727] 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 252/727] 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 253/727] 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 7f703585f12d47bc7d6044faf3641fc61cbdab55 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 4 Apr 2024 10:51:51 +0200 Subject: [PATCH 254/727] remove targets class attribute. Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- .../houdini/plugins/publish/collect_local_render_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 194a05f42d..1fd4129ee1 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -17,7 +17,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): "vray_rop"] hosts = ["houdini"] - targets = ["local", "remote"] label = "Collect local render instances" def process(self, instance): From be694cba5619f8cb7ac66cb378435614f52a98d8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 17:43:24 +0800 Subject: [PATCH 255/727] 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 4e6bd3d3361708fa7cf84ef69e0e5715ed451df2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 4 Apr 2024 12:32:57 +0200 Subject: [PATCH 256/727] remove the un-necessary 'render.farm.hou' intermidate family --- .../plugins/publish/collect_farm_instances.py | 1 - .../plugins/publish/increment_current_file.py | 6 +++++- .../deadline/plugins/publish/collect_pools.py | 6 +++++- .../publish/submit_houdini_render_deadline.py | 12 +++++++++++- .../deadline/plugins/publish/submit_publish_job.py | 6 +++++- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index 56a2b42940..391afe7387 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -52,4 +52,3 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): return instance.data["farm"] = True - instance.data["families"].append("render.farm.hou") diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index acb66afa4e..ffd9a75620 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -19,7 +19,11 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): hosts = ["houdini"] families = ["workfile", "usdrender", - "render.farm.hou", + "mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop", "render.local.hou", "publish.hou"] optional = True diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 76b397eee0..6b7449b8f8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -41,7 +41,11 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "renderlayer", "maxrender", "usdrender", - "render.farm.hou", + "mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop", "publish.hou"] primary_pool = None diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 404c7ade04..b562e2848b 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -71,7 +71,12 @@ class HoudiniSubmitDeadline( order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "render.farm.hou"] + "mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop"] + targets = ["local"] use_published = True @@ -314,6 +319,11 @@ class HoudiniSubmitDeadline( return attr.asdict(plugin_info) def process(self, instance): + if not instance.data["farm"]: + self.log.debug("Render on farm is disabled. " + "Skipping deadline submission.") + return + super(HoudiniSubmitDeadline, self).process(instance) # TODO: Avoid the need for this logic here, needed for submit publish diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index f8df1b4d4c..773532e5c0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -92,7 +92,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "prerender.farm", "prerender.frames_farm", "renderlayer", "imagesequence", "vrayscene", "maxrender", - "render.farm.hou"] + "mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop"] aov_filter = [ { From a7ee5154b9867c53d0efe192b9f1f12001e032e1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 4 Apr 2024 11:35:26 +0100 Subject: [PATCH 257/727] 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 05bdbb2aa6ff99eed01848ce2e53a3fbe3ff9341 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 4 Apr 2024 12:58:29 +0200 Subject: [PATCH 258/727] Abort publishing if there are missing frames. --- .../plugins/publish/extract_local_render.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index 120e5563e9..23a64945aa 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -3,6 +3,7 @@ import pyblish.api from ayon_core.pipeline import publish from ayon_core.hosts.houdini.api.lib import render_rop import hou +import os class ExtractLocalRender(publish.Extractor): @@ -46,5 +47,17 @@ class ExtractLocalRender(publish.Extractor): ropnode = hou.node(instance.data.get("instance_node")) render_rop(ropnode) - # TODO: Check for missing frames. - # self.log.debug(instance.data["expectedFiles"]) + # Check missing frames. + # Frames won't exist if user cancels the render. + expected_files = next(iter(instance.data["expectedFiles"]), {}) + expected_files = sum(expected_files.values(), []) + missing_frames = [ + frame + for frame in expected_files + if not os.path.exists(frame) + ] + if missing_frames: + # TODO: Use user friendly error reporting. + raise RuntimeError("Failed to complete render extraction. " + "Missing output files: {}".format( + missing_frames)) From fe6c1fc9f5efa2c0e5c73d397c8584443ae7cd94 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 4 Apr 2024 15:54:38 +0200 Subject: [PATCH 259/727] remove targets class attribute. Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- .../hosts/houdini/plugins/publish/extract_local_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index 23a64945aa..cf94019947 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -16,7 +16,6 @@ class ExtractLocalRender(publish.Extractor): "redshift_rop", "arnold_rop", "vray_rop"] - targets = ["local", "remote"] def process(self, instance): if instance.data.get("farm"): From 50127b9d84a19869cd2110294cc48dbec3b9340e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 4 Apr 2024 17:10:17 +0200 Subject: [PATCH 260/727] Refactor name to denote multiple servers --- client/ayon_core/modules/deadline/deadline_module.py | 8 ++++---- .../publish/collect_deadline_server_from_instance.py | 2 +- .../plugins/publish/collect_default_deadline_server.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index 3a35654737..ea9e4085ab 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -19,23 +19,23 @@ class DeadlineModule(AYONAddon, IPluginPaths): def initialize(self, studio_settings): # This module is always enabled - deadline_server_info = {} + deadline_servers_info = {} enabled = self.name in studio_settings if enabled: deadline_settings = studio_settings[self.name] - deadline_server_info = { + deadline_servers_info = { url_item["name"]: url_item for url_item in deadline_settings["deadline_urls"] } - if enabled and not deadline_server_info: + if enabled and not deadline_servers_info: enabled = False self.log.warning(( "Deadline Webservice URLs are not specified. Disabling addon." )) self.enabled = enabled - self.deadline_server_info = deadline_server_info + self.deadline_servers_info = deadline_servers_info def get_plugin_paths(self): """Deadline plugin paths.""" diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 8e7f836830..c6b30d3b2a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -91,7 +91,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): default_servers = { url_item["name"]: url_item["value"] - for url_item in deadline_settings["deadline_server_info"] + for url_item in deadline_settings["deadline_servers_info"] } project_servers = ( render_instance.context.data diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 419de7acac..ced72607bc 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -34,13 +34,13 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): dl_ws_item = None if deadline_server_name: - dl_ws_item = deadline_module.deadline_server_info.get( + dl_ws_item = deadline_module.deadline_servers_info.get( deadline_server_name) if dl_ws_item: deadline_url = dl_ws_item["value"] else: - default_dl_item = deadline_module.deadline_server_info.pop() + default_dl_item = deadline_module.deadline_servers_info.pop() deadline_url = default_dl_item["value"] context.data["deadline"] = {} From 766cbd9f57135f44fb3121ad29ca29efc66fd0f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 4 Apr 2024 17:13:00 +0200 Subject: [PATCH 261/727] Refactor change docstring --- client/ayon_core/modules/deadline/deadline_module.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index ea9e4085ab..b1089bbfe2 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -46,13 +46,14 @@ class DeadlineModule(AYONAddon, IPluginPaths): @staticmethod def get_deadline_pools(webservice, auth=None, log=None): - # type: (str) -> list """Get pools from Deadline. Args: webservice (str): Server url. - log (Logger) + auth (Optional[Tuple[str, str]]): Tuple containing username, + password + log (Optional[Logger]): Logger to log errors to, if provided. Returns: - list: Pools. + List[str]: Pools. Throws: RuntimeError: If deadline webservice is unreachable. From bbcee5fd6b352e6ef309e00199139b02dbbe014b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 17:18:12 +0200 Subject: [PATCH 262/727] 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 263/727] 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 264/727] 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 265/727] 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 266/727] 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 267/727] 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 268/727] 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 269/727] 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 270/727] 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 271/727] 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 c4c56f8d3f0d752abd5ddd6000d391b82d838fbc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 5 Apr 2024 11:09:27 +0200 Subject: [PATCH 272/727] AY-747- refactor name of variable --- .../publish/collect_default_deadline_server.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index ced72607bc..2ea17123b7 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -32,16 +32,16 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): deadline_settings = context.data["project_settings"]["deadline"] deadline_server_name = deadline_settings["deadline_server"] - dl_ws_item = None + dl_server_info = None if deadline_server_name: - dl_ws_item = deadline_module.deadline_servers_info.get( + dl_server_info = deadline_module.deadline_servers_info.get( deadline_server_name) - if dl_ws_item: - deadline_url = dl_ws_item["value"] + if dl_server_info: + deadline_url = dl_server_info["value"] else: - default_dl_item = deadline_module.deadline_servers_info.pop() - deadline_url = default_dl_item["value"] + default_dl_server_info = deadline_module.deadline_servers_info.pop() + deadline_url = default_dl_server_info["value"] context.data["deadline"] = {} context.data["deadline"]["defaultDeadline"] = ( From 26a11a562869141fd2b7d2cf604af775c65fa1bd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 5 Apr 2024 11:10:23 +0200 Subject: [PATCH 273/727] AY-747- refactor query default --- .../deadline/plugins/publish/collect_default_deadline_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 2ea17123b7..6fca97b4ef 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -40,7 +40,7 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): if dl_server_info: deadline_url = dl_server_info["value"] else: - default_dl_server_info = deadline_module.deadline_servers_info.pop() + default_dl_server_info = deadline_module.deadline_servers_info[0] deadline_url = default_dl_server_info["value"] context.data["deadline"] = {} From cba1dae30ffeced94f79931f034e2640d1d5def8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 5 Apr 2024 11:14:22 +0200 Subject: [PATCH 274/727] Update client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml Co-authored-by: Roy Nieterau --- .../plugins/publish/help/validate_deadline_connection.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml index e9377d8baa..eec05df08a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml +++ b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml @@ -9,8 +9,8 @@ This project has set in Settings that Deadline requires authentication. ### How to repair? -Please go to Ayon Server Site settings and provide your Deadline username and - most likely password too. (Deadline could run in configuration that empty passwords are allowed. Ask your administrator for details.) +Please go to Ayon Server > Site Settings and provide your Deadline username and password. +In some cases the password may be empty if Deadline is configured to allow that. Ask your administrator. From 4332c507368c7d20ebf82043e5a2c8269e7221cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 5 Apr 2024 11:18:41 +0200 Subject: [PATCH 275/727] AY-747- refactor passing of auth No necessary to pass kwargs --- .../ayon_core/modules/deadline/abstract_submit_deadline.py | 5 +---- .../deadline/plugins/publish/submit_celaction_deadline.py | 6 ++---- .../deadline/plugins/publish/submit_fusion_deadline.py | 5 +---- .../deadline/plugins/publish/submit_nuke_deadline.py | 5 +---- .../deadline/plugins/publish/submit_publish_cache_job.py | 5 +---- .../modules/deadline/plugins/publish/submit_publish_job.py | 5 +---- 6 files changed, 7 insertions(+), 24 deletions(-) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index e71177b34e..00e51100bc 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -601,11 +601,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ url = "{}/api/jobs".format(self._deadline_url) - kwargs = {} - if auth: - kwargs["auth"] = auth response = requests_post(url, json=payload, - **kwargs) + auth=auth) if not response.ok: self.log.error("Submission failed!") self.log.error(response.status_code) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index 86c017818f..2ff50a16b9 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -194,10 +194,8 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - kwargs = {} - if instance.context.data["deadline_auth"]: - kwargs["auth"] = instance.context.data["deadline_auth"] - response = requests_post(self.deadline_url, json=payload, **kwargs) + response = requests_post(self.deadline_url, json=payload, + auth=instance.context.data["deadline_auth"]) if not response.ok: self.log.error( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 4027991ca7..d5664f14c4 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -251,11 +251,8 @@ class FusionSubmitDeadline( # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) - kwargs = {} auth = instance.data["deadline"]["auth"] - if auth: - kwargs["auth"] = auth - response = requests_post(url, json=payload, **kwargs) + response = requests_post(url, json=payload, auth=auth) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index 287b3da19c..dbf92719e8 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -431,12 +431,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - kwargs = {} auth = instance.data["deadline"]["auth"] - if auth: - kwargs["auth"] = auth response = requests_post(self.deadline_url, json=payload, timeout=10, - **kwargs) + auth=auth) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 8ae781d051..9f6278a4c5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -209,12 +209,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - kwargs = {} auth = instance.data["deadline"]["auth"] - if auth: - kwargs["auth"] = auth response = requests_post(url, json=payload, timeout=10, - **kwargs) + auth=auth) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 6d288111b7..ce90fc2706 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -303,12 +303,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - kwargs = {} auth = instance.data["deadline"]["auth"] - if auth: - kwargs["auth"] = auth response = requests_post(url, json=payload, timeout=10, - **kwargs) + auth=auth) if not response.ok: raise Exception(response.text) From f5e24b642bf940b8277272ad8647e85bb7a6d31f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 5 Apr 2024 11:30:14 +0200 Subject: [PATCH 276/727] AY-747- update todo --- .../hosts/harmony/plugins/publish/collect_farm_render.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py index e869de316f..c63eb114e5 100644 --- a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py @@ -178,7 +178,9 @@ class CollectFarmRender(publish.AbstractCollectRender): outputStartFrame=info[3], leadingZeros=info[2], ignoreFrameHandleCheck=True, - # deadline=inst.data.get("deadline") TODO + #todo: inst is not available, must be determined, fix when + #reworking to Publisher + # deadline=inst.data.get("deadline") ) render_instance.context = context From 9f7fe606f7f045005aa3d481682e1252ac67ab02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 Apr 2024 21:44:50 +0800 Subject: [PATCH 277/727] refactor original resolution setting for validate review resolution setting --- .../publish/validate_resolution_setting.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 48e2e1d45d..49beecbabc 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -15,7 +15,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender", "review"] + families = ["maxrender"] hosts = ["max"] label = "Validate Resolution Setting" optional = True @@ -25,8 +25,10 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return width, height = self.get_folder_resolution(instance) - current_width = rt.renderWidth - current_height = rt.renderHeight + current_width, current_height = ( + self.get_current_resolution(instance) + ) + if current_width != width and current_height != height: raise PublishValidationError("Resolution Setting " "not matching resolution " @@ -41,7 +43,11 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, "not matching resolution set " "on asset or shot.") - def get_folder_resolution(self, instance): + def get_current_resolution(self, instance): + return rt.renderWidth, rt.renderHeight + + @classmethod + def get_folder_resolution(cls, instance): task_entity = instance.data.get("taskEntity") if task_entity: task_attributes = task_entity["attrib"] @@ -55,3 +61,23 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): reset_scene_resolution() + + +class ValidateReviewResolutionSetting(ValidateResolutionSetting): + families = ["review"] + label = "Validate Review Animation Resolution Setting" + optional = True + actions = [RepairAction] + + def get_current_resolution(self, instance): + current_width = instance.data["review_width"] + current_height = instance.data["review_height"] + return current_width, current_height + + @classmethod + def repair(cls, instance): + context_width, context_height = ( + cls.get_folder_resolution(instance) + ) + instance.data["review_width"] = context_width + instance.data["review_height"] = context_height From a6ca1488997950044ebddc69b418f5605b9103ab Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 8 Apr 2024 14:24:06 +0200 Subject: [PATCH 278/727] Houdini local render: support single frame --- .../plugins/publish/collect_local_render_instances.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 1fd4129ee1..1dc26e1322 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -62,6 +62,13 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): staging_dir = os.path.dirname(aov_filepaths[0]) ext = aov_filepaths[0].split(".")[-1] + # Support Single frame. + # The integrator wants single files to be a single + # filename instead of a list. + # More info: https://github.com/ynput/ayon-core/issues/238 + if len(aov_filenames) == 1: + aov_filenames = aov_filenames[0] + aov_instance.data.update({ # 'label': label, "task": instance.data["task"], @@ -85,6 +92,7 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): } ] }) + self.log.debug(aov_instance.data) # Remove Mantra instance # I can't remove it here as I still need it to trigger the render. From d7b1f3a3f7a82be99e325201b14dcffbafab4614 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 8 Apr 2024 16:34:38 +0200 Subject: [PATCH 279/727] Houdini local render: support adding review family to the render --- .../publish/collect_local_render_instances.py | 5 ++--- .../plugins/publish/collect_review_data.py | 20 +++++++++++++++++-- .../houdini/plugins/publish/extract_opengl.py | 4 ++++ .../publish/validate_review_colorspace.py | 8 +++++++- .../plugins/publish/validate_scene_review.py | 4 ++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 1dc26e1322..e221990f2b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -78,21 +78,20 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): "productType": product_type, "productName": product_name, "productGroup": product_group, - "tags": [], - "families": ["render.local.hou"], + "families": ["render.local.hou", "review"], "instance_node": instance.data["instance_node"], "representations": [ { "stagingDir": staging_dir, "ext": ext, "name": ext, + "tags": ["review"], "files": aov_filenames, "frameStart": instance.data["frameStartHandle"], "frameEnd": instance.data["frameEndHandle"] } ] }) - self.log.debug(aov_instance.data) # Remove Mantra instance # I can't remove it here as I still need it to trigger the render. diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py index 9671945b9a..7714ed0954 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py @@ -8,7 +8,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): label = "Collect Review Data" # This specific order value is used so that # this plugin runs after CollectRopFrameRange - order = pyblish.api.CollectorOrder + 0.1 + # Also after CollectLocalRenderInstances + order = pyblish.api.CollectorOrder + 0.13 hosts = ["houdini"] families = ["review"] @@ -28,7 +29,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): ropnode_path = instance.data["instance_node"] ropnode = hou.node(ropnode_path) - camera_path = ropnode.parm("camera").eval() + # Get camera based on the instance_node type. + camera_path = self._get_camera_path(ropnode) camera_node = hou.node(camera_path) if not camera_node: self.log.warning("No valid camera node found on review node: " @@ -55,3 +57,17 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): # Store focal length in `burninDataMembers` burnin_members = instance.data.setdefault("burninDataMembers", {}) burnin_members["focalLength"] = focal_length + + def _get_camera_path(self, ropnode): + if ropnode.type().name() in { + "opengl", "karma", "ifd", "arnold" + }: + return ropnode.parm("camera").eval() + + elif ropnode.type().name() == "Redshift_ROP": + return ropnode.parm("RS_renderCamera").eval() + + elif ropnode.type().name() == "vray_renderer": + return ropnode.parm("render_camera").eval() + + return "" diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py index fabdfd9a9d..69bbb22340 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py @@ -17,6 +17,10 @@ class ExtractOpenGL(publish.Extractor): def process(self, instance): ropnode = hou.node(instance.data.get("instance_node")) + if ropnode.type().name() != "opengl": + self.log.debug("Skipping OpenGl extraction. Rop node {} " + "is not an OpenGl node.".format(ropnode.path())) + return output = ropnode.evalParm("picture") staging_dir = os.path.normpath(os.path.dirname(output)) 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..e02ce93f0d 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 @@ -33,6 +33,13 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, def process(self, instance): + rop_node = hou.node(instance.data["instance_node"]) + + if rop_node.type().name() != "opengl": + self.log.debug("Skipping Validation. Rop node {} " + "is not an OpenGl node.".format(rop_node.path())) + return + if not self.is_active(instance.data): return @@ -43,7 +50,6 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, ) return - rop_node = hou.node(instance.data["instance_node"]) if rop_node.evalParm("colorcorrect") != 2: # any colorspace settings other than default requires # 'Color Correct' parm to be set to 'OpenColorIO' diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py index b6007d3f0f..9b81f0f8ed 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py @@ -19,6 +19,10 @@ class ValidateSceneReview(pyblish.api.InstancePlugin): report = [] instance_node = hou.node(instance.data.get("instance_node")) + if instance_node.type().name() != "opengl": + self.log.debug("Skipping Validation. Rop node {} " + "is not an OpenGl node.".format(instance_node.path())) + return invalid = self.get_invalid_scene_path(instance_node) if invalid: 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 280/727] 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 281/727] 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 282/727] 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 283/727] 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 284/727] 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 285/727] 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 c5df561c970bc1cfc0498f8b1adf52392f5fe969 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 20:20:33 +0800 Subject: [PATCH 286/727] Transfer settings from pre create to instance --- .../hosts/houdini/plugins/create/create_arnold_rop.py | 6 ++++++ .../hosts/houdini/plugins/create/create_karma_rop.py | 6 ++++++ .../hosts/houdini/plugins/create/create_mantra_rop.py | 6 ++++++ .../hosts/houdini/plugins/create/create_redshift_rop.py | 6 ++++++ .../hosts/houdini/plugins/create/create_vray_rop.py | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index 07c1c98a28..08ed1bc91a 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -18,6 +18,12 @@ class CreateArnoldRop(plugin.HoudiniCreator): def create(self, product_name, instance_data, pre_create_data): import hou + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["render_target"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] # Remove the active, we are checking the bypass flag of the nodes instance_data.pop("active", None) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index 5d56150df9..a3a557791e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -16,6 +16,12 @@ class CreateKarmaROP(plugin.HoudiniCreator): def create(self, product_name, instance_data, pre_create_data): import hou # noqa + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["render_target"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "karma"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index 6705621f58..1b177563bc 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -16,6 +16,12 @@ class CreateMantraROP(plugin.HoudiniCreator): def create(self, product_name, instance_data, pre_create_data): import hou # noqa + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["render_target"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "ifd"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 02c3ed2fc0..942d321b92 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -21,6 +21,12 @@ class CreateRedshiftROP(plugin.HoudiniCreator): render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["render_target"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "Redshift_ROP"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 147a34191f..ad181e4f89 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -20,6 +20,12 @@ class CreateVrayROP(plugin.HoudiniCreator): render_target = "farm_split" def create(self, product_name, instance_data, pre_create_data): + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["render_target"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "vray_renderer"}) From df201386f52721803aa6280da236886881ee6887 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 16:04:25 +0200 Subject: [PATCH 287/727] 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 76e4b77845bb34737840680d0e5a15ff56733e30 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 20:25:41 +0200 Subject: [PATCH 288/727] transfer all precreate settings to instance --- .../hosts/houdini/plugins/create/create_arnold_rop.py | 4 +--- .../hosts/houdini/plugins/create/create_karma_rop.py | 4 +--- .../hosts/houdini/plugins/create/create_mantra_rop.py | 4 +--- .../hosts/houdini/plugins/create/create_redshift_rop.py | 4 +--- .../ayon_core/hosts/houdini/plugins/create/create_vray_rop.py | 4 +--- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index 08ed1bc91a..0e25523123 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -21,9 +21,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - for key in ["render_target"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] + creator_attributes.update(pre_create_data) # Remove the active, we are checking the bypass flag of the nodes instance_data.pop("active", None) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index a3a557791e..4ddf7af376 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -19,9 +19,7 @@ class CreateKarmaROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - for key in ["render_target"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] + creator_attributes.update(pre_create_data) instance_data.pop("active", None) instance_data.update({"node_type": "karma"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index 1b177563bc..7d481d0dbf 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -19,9 +19,7 @@ class CreateMantraROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - for key in ["render_target"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] + creator_attributes.update(pre_create_data) instance_data.pop("active", None) instance_data.update({"node_type": "ifd"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 942d321b92..dd5325c23c 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -24,9 +24,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - for key in ["render_target"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] + creator_attributes.update(pre_create_data) instance_data.pop("active", None) instance_data.update({"node_type": "Redshift_ROP"}) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index ad181e4f89..5587f0151b 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -23,9 +23,7 @@ class CreateVrayROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - for key in ["render_target"]: - if key in pre_create_data: - creator_attributes[key] = pre_create_data[key] + creator_attributes.update(pre_create_data) instance_data.pop("active", None) instance_data.update({"node_type": "vray_renderer"}) From 90e2c1f1b5235e0d6fd408588a74f69f9cb7ac51 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 21:58:31 +0200 Subject: [PATCH 289/727] use get_product_name instead of hardcoded productname --- .../publish/collect_local_render_instances.py | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index e221990f2b..4622f2d9cd 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -1,5 +1,6 @@ import os import pyblish.api +from ayon_core.pipeline.create import get_product_name class CollectLocalRenderInstances(pyblish.api.InstancePlugin): @@ -28,31 +29,24 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # Create Instance for each AOV. context = instance.context + self.log.debug(instance.data["expectedFiles"]) expectedFiles = next(iter(instance.data["expectedFiles"]), {}) product_type = "render" # is always render - product_group = "render{Task}{productName}".format( - Task=self._capitalize(instance.data["task"]), - productName=self._capitalize(instance.data["productName"]) - ) # is always the group + product_group = get_product_name( + context.data["projectName"], + context.data["taskEntity"]["name"], + context.data["taskEntity"]["taskType"], + context.data["hostName"], + product_type, + instance.data["productName"] + ) for aov_name, aov_filepaths in expectedFiles.items(): - # Some AOV instance data - # label = "{productName}_{AOV}".format( - # AOV=aov_name, - # productName=instance.data["productName"] - # ) - name_template = "render{Task}{productName}_{AOV}" - if not aov_name: - # This is done to remove the trailing `_` - # if aov name is an empty string. - name_template = "render{Task}{productName}" + product_name = product_group - product_name = name_template.format( - Task=self._capitalize(instance.data["task"]), - productName=self._capitalize(instance.data["productName"]), - AOV=aov_name - ) + if aov_name: + product_name = "{}_{}".format(product_name, aov_name) # Create instance for each AOV aov_instance = context.create_instance(product_name) @@ -96,7 +90,3 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # Remove Mantra instance # I can't remove it here as I still need it to trigger the render. # context.remove(instance) - - @staticmethod - def _capitalize(word): - return word[:1].upper() + word[1:] From 95757c6b68776884d481f03e2890b9c0a2ee8107 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 22:16:53 +0200 Subject: [PATCH 290/727] add doc string to _get_camera_path --- .../houdini/plugins/publish/collect_review_data.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py index 7714ed0954..ed2de785a2 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_review_data.py @@ -59,6 +59,18 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): burnin_members["focalLength"] = focal_length def _get_camera_path(self, ropnode): + """Get the camera path associated with the given rop node. + + This function evaluates the camera parameter according to the + type of the given rop node. + + Returns: + Union[str, None]: Camera path or None. + + This function can return empty string if the camera + path is empty i.e. no camera path. + """ + if ropnode.type().name() in { "opengl", "karma", "ifd", "arnold" }: @@ -70,4 +82,4 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin): elif ropnode.type().name() == "vray_renderer": return ropnode.parm("render_camera").eval() - return "" + return None From 75879f54be31e293cad18a1c4a8a60005d62ba70 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 22:28:53 +0200 Subject: [PATCH 291/727] revert changes --- .../deadline/plugins/publish/collect_pools.py | 4 ++-- .../publish/submit_houdini_render_deadline.py | 21 ++++++------------- .../plugins/publish/submit_publish_job.py | 8 +++---- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py index 6b7449b8f8..6923c2b16b 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_pools.py @@ -41,10 +41,10 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin, "renderlayer", "maxrender", "usdrender", - "mantra_rop", - "karma_rop", "redshift_rop", "arnold_rop", + "mantra_rop", + "karma_rop", "vray_rop", "publish.hou"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index b562e2848b..64a7423e8d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -71,12 +71,11 @@ class HoudiniSubmitDeadline( order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "mantra_rop", - "karma_rop", "redshift_rop", "arnold_rop", + "mantra_rop", + "karma_rop", "vray_rop"] - targets = ["local"] use_published = True @@ -266,23 +265,20 @@ class HoudiniSubmitDeadline( # Output driver to render if job_type == "render": product_type = instance.data.get("productType") - rop_node = hou.node(instance.data.get("instance_node")) - node_type = rop_node.type().name() - - if node_type == "arnold": + if product_type == "arnold_rop": plugin_info = ArnoldRenderDeadlinePluginInfo( InputFile=instance.data["ifdFile"] ) - elif node_type == "ifd": + elif product_type == "mantra_rop": plugin_info = MantraRenderDeadlinePluginInfo( SceneFile=instance.data["ifdFile"], Version=hou_major_minor, ) - elif node_type == "vray_renderer": + elif product_type == "vray_rop": plugin_info = VrayRenderPluginInfo( InputFilename=instance.data["ifdFile"], ) - elif node_type == "Redshift_ROP": + elif product_type == "redshift_rop": plugin_info = RedshiftRenderPluginInfo( SceneFile=instance.data["ifdFile"] ) @@ -319,11 +315,6 @@ class HoudiniSubmitDeadline( return attr.asdict(plugin_info) def process(self, instance): - if not instance.data["farm"]: - self.log.debug("Render on farm is disabled. " - "Skipping deadline submission.") - return - super(HoudiniSubmitDeadline, self).process(instance) # TODO: Avoid the need for this logic here, needed for submit publish diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 87693522c3..8def9cc63c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -92,11 +92,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "prerender.farm", "prerender.frames_farm", "renderlayer", "imagesequence", "vrayscene", "maxrender", - "mantra_rop", - "karma_rop", - "redshift_rop", - "arnold_rop", - "vray_rop"] + "arnold_rop", "mantra_rop", + "karma_rop", "vray_rop", + "redshift_rop"] aov_filter = [ { From 6151ff57e2dc9f5574cb3ddbb8685d4ec69752f0 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 22:30:01 +0200 Subject: [PATCH 292/727] skip submission if farm is disabled --- .../plugins/publish/submit_houdini_render_deadline.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 64a7423e8d..4c517d7848 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -315,6 +315,11 @@ class HoudiniSubmitDeadline( return attr.asdict(plugin_info) def process(self, instance): + if not instance.data["farm"]: + self.log.debug("Render on farm is disabled. " + "Skipping deadline submission.") + return + super(HoudiniSubmitDeadline, self).process(instance) # TODO: Avoid the need for this logic here, needed for submit publish From c49c9016bfdafa27a67f7693b72d0a340a909fa1 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 22:35:06 +0200 Subject: [PATCH 293/727] add a TODO about enhancing code readability --- .../hosts/houdini/plugins/publish/extract_local_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py index cf94019947..3f332acc55 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py @@ -49,7 +49,8 @@ class ExtractLocalRender(publish.Extractor): # Check missing frames. # Frames won't exist if user cancels the render. expected_files = next(iter(instance.data["expectedFiles"]), {}) - expected_files = sum(expected_files.values(), []) + # TODO: enhance the readability. + expected_files = sum(expected_files.values(), []) missing_frames = [ frame for frame in expected_files From 3b6c3bb5e5fe61361dd2774b1aa07f9a80a0384b Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Apr 2024 22:49:34 +0200 Subject: [PATCH 294/727] remove redundant attr_defs --- .../hosts/houdini/plugins/create/create_arnold_rop.py | 3 +-- .../ayon_core/hosts/houdini/plugins/create/create_karma_rop.py | 3 +-- .../hosts/houdini/plugins/create/create_mantra_rop.py | 3 +-- .../hosts/houdini/plugins/create/create_redshift_rop.py | 3 +-- .../ayon_core/hosts/houdini/plugins/create/create_vray_rop.py | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index 0e25523123..0965ee59ca 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -92,6 +92,5 @@ class CreateArnoldRop(plugin.HoudiniCreator): ] def get_pre_create_attr_defs(self): - attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() + return self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index 4ddf7af376..c795512469 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -126,6 +126,5 @@ class CreateKarmaROP(plugin.HoudiniCreator): def get_pre_create_attr_defs(self): - attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() + return self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index 7d481d0dbf..d0fc79f608 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -108,6 +108,5 @@ class CreateMantraROP(plugin.HoudiniCreator): ] def get_pre_create_attr_defs(self): - attrs = super(CreateMantraROP, self).get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() + return self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index dd5325c23c..0094269f47 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -151,6 +151,5 @@ class CreateRedshiftROP(plugin.HoudiniCreator): ] def get_pre_create_attr_defs(self): - attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() + return self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 5587f0151b..8c4084cf9f 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -179,6 +179,5 @@ class CreateVrayROP(plugin.HoudiniCreator): ] def get_pre_create_attr_defs(self): - attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() - return attrs + self.get_instance_attr_defs() + return self.get_instance_attr_defs() From 6f8ab66eb2684b36326a563bc7c91d792ce88484 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Apr 2024 09:32:22 +0200 Subject: [PATCH 295/727] 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 296/727] 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 a2b73014da7266280f65bc9c800ae264883da78e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:14:08 +0200 Subject: [PATCH 297/727] mark known bare except handling with noqa --- client/ayon_core/hosts/blender/api/lib.py | 6 +++--- .../hosts/photoshop/plugins/create/create_image.py | 2 +- client/ayon_core/pipeline/create/context.py | 12 ++++++------ client/ayon_core/tools/adobe_webserver/app.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/hosts/blender/api/lib.py b/client/ayon_core/hosts/blender/api/lib.py index 458a275b51..32137f0fcd 100644 --- a/client/ayon_core/hosts/blender/api/lib.py +++ b/client/ayon_core/hosts/blender/api/lib.py @@ -33,7 +33,7 @@ def load_scripts(paths): if register: try: register() - except: + except: # noqa E722 traceback.print_exc() else: print("\nWarning! '%s' has no register function, " @@ -45,7 +45,7 @@ def load_scripts(paths): if unregister: try: unregister() - except: + except: # noqa E722 traceback.print_exc() def test_reload(mod): @@ -57,7 +57,7 @@ def load_scripts(paths): try: return importlib.reload(mod) - except: + except: # noqa E722 traceback.print_exc() def test_register(mod): diff --git a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py index 26f2469844..97543e96de 100644 --- a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py @@ -53,7 +53,7 @@ class ImageCreator(Creator): stub.select_layers(stub.get_layers()) try: group = stub.group_selected_layers(product_name_from_ui) - except: + except: # noqa E722 raise CreatorError("Cannot group locked Background layer!") groups_to_create.append(group) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index ca9896fb3f..c223b52d03 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -2053,7 +2053,7 @@ class CreateContext: exc_info = sys.exc_info() self.log.warning(error_message.format(identifier, exc_info[1])) - except: + except: # noqa: E722 add_traceback = True exc_info = sys.exc_info() self.log.warning( @@ -2163,7 +2163,7 @@ class CreateContext: exc_info = sys.exc_info() self.log.warning(error_message.format(identifier, exc_info[1])) - except: + except: # noqa: E722 failed = True add_traceback = True exc_info = sys.exc_info() @@ -2197,7 +2197,7 @@ class CreateContext: try: convertor.find_instances() - except: + except: # noqa: E722 failed_info.append( prepare_failed_convertor_operation_info( convertor.identifier, sys.exc_info() @@ -2373,7 +2373,7 @@ class CreateContext: exc_info = sys.exc_info() self.log.warning(error_message.format(identifier, exc_info[1])) - except: + except: # noqa: E722 failed = True add_traceback = True exc_info = sys.exc_info() @@ -2440,7 +2440,7 @@ class CreateContext: error_message.format(identifier, exc_info[1]) ) - except: + except: # noqa: E722 failed = True add_traceback = True exc_info = sys.exc_info() @@ -2546,7 +2546,7 @@ class CreateContext: try: self.run_convertor(convertor_identifier) - except: + except: # noqa: E722 failed_info.append( prepare_failed_convertor_operation_info( convertor_identifier, sys.exc_info() diff --git a/client/ayon_core/tools/adobe_webserver/app.py b/client/ayon_core/tools/adobe_webserver/app.py index 7d97d7d66d..819cfa8084 100644 --- a/client/ayon_core/tools/adobe_webserver/app.py +++ b/client/ayon_core/tools/adobe_webserver/app.py @@ -109,7 +109,7 @@ class WebServerTool: try: sock.bind((host_name, port)) result = False - except: + except: # noqa E722 print("Port is in use") return result From eb5258012f5f30306e4afb36bcfdec8a366e595e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:34:30 +0200 Subject: [PATCH 298/727] added list of files to exclude from linting --- pyproject.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3c9ff4ea0a..29733983e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,20 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +exclude = [ + "client/ayon_core/hosts/unreal/integration/*", + "client/ayon_core/hosts/aftereffects/api/extension/js/libs/*", + "client/ayon_core/hosts/hiero/api/startup/*", + "client/ayon_core/modules/deadline/repository/custom/plugins/CelAction/*", + "client/ayon_core/modules/deadline/repository/custom/plugins/HarmonyAYON/*", + "client/ayon_core/modules/click_wrap.py", + "client/ayon_core/scripts/slates/__init__.py" +] + +[tool.ruff.lint.per-file-ignores] +"client/ayon_core/lib/__init__.py" = ["E402"] +"client/ayon_core/hosts/max/startup/startup.py" = ["E402"] + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" From 78a895b720fda253a37725ce30814ca85abafdb9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:37:11 +0200 Subject: [PATCH 299/727] change port check logic --- client/ayon_core/tools/adobe_webserver/app.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/adobe_webserver/app.py b/client/ayon_core/tools/adobe_webserver/app.py index 819cfa8084..6ea9745c59 100644 --- a/client/ayon_core/tools/adobe_webserver/app.py +++ b/client/ayon_core/tools/adobe_webserver/app.py @@ -104,14 +104,11 @@ class WebServerTool: again. In that case, use existing running webserver. Check here is easier than capturing exception from thread. """ - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = True - try: - sock.bind((host_name, port)) - result = False - except: # noqa E722 - print("Port is in use") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as con: + result = con.connect_ex((host_name, port)) == 0 + if result: + print("Port is in use") return result def call(self, func): From 364dee88f1a99e3410b6cc0d31566ceb25485b67 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 10 Apr 2024 22:03:37 +0800 Subject: [PATCH 300/727] 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 301/727] 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 302/727] 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 303/727] 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 304/727] 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 305/727] 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 306/727] 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 307/727] 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 308/727] 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 309/727] 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 310/727] 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 311/727] 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 312/727] 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 313/727] 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 314/727] 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 315/727] 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 316/727] 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 317/727] 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 e587ef53440e18649cce4642517705d538011cf2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:03:34 +0200 Subject: [PATCH 318/727] log which port is in use Co-authored-by: Roy Nieterau --- client/ayon_core/tools/adobe_webserver/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/adobe_webserver/app.py b/client/ayon_core/tools/adobe_webserver/app.py index 6ea9745c59..26bf638c91 100644 --- a/client/ayon_core/tools/adobe_webserver/app.py +++ b/client/ayon_core/tools/adobe_webserver/app.py @@ -108,7 +108,7 @@ class WebServerTool: result = con.connect_ex((host_name, port)) == 0 if result: - print("Port is in use") + print(f"Port {port} is already in use") return result def call(self, func): From 74bc0102c829bfad88bbad6d40f8dee6da972447 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 18:23:33 +0800 Subject: [PATCH 319/727] 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 320/727] 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 321/727] 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 322/727] 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 323/727] 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 324/727] 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 325/727] 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 326/727] 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 327/727] 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 328/727] 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 329/727] 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 330/727] 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 331/727] 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 332/727] 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 333/727] 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 334/727] 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 335/727] 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 336/727] 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 337/727] 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 338/727] 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 339/727] 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 340/727] 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 341/727] 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 342/727] 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 343/727] 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 344/727] 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 345/727] 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 346/727] 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 bedebd8f8e871665d6b117f5c13c8a20a63ad24a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Apr 2024 12:28:31 +0200 Subject: [PATCH 347/727] add 'Mark as reviewable' todo --- .../plugins/publish/collect_local_render_instances.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 4622f2d9cd..f3ad5862a6 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -63,6 +63,8 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): if len(aov_filenames) == 1: aov_filenames = aov_filenames[0] + # TODO: Add some option to allow users to mark + # aov_instances as reviewable. aov_instance.data.update({ # 'label': label, "task": instance.data["task"], @@ -72,14 +74,14 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): "productType": product_type, "productName": product_name, "productGroup": product_group, - "families": ["render.local.hou", "review"], + "families": ["render.local.hou"], "instance_node": instance.data["instance_node"], "representations": [ { "stagingDir": staging_dir, "ext": ext, "name": ext, - "tags": ["review"], + "tags": [], "files": aov_filenames, "frameStart": instance.data["frameStartHandle"], "frameEnd": instance.data["frameEndHandle"] From 16c8c859b32c186559e526cbb941842e1fb0b972 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Apr 2024 12:29:58 +0200 Subject: [PATCH 348/727] update a comment --- .../houdini/plugins/publish/collect_local_render_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index f3ad5862a6..ae98e6ed87 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -89,6 +89,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): ] }) - # Remove Mantra instance + # Remove original render instance # I can't remove it here as I still need it to trigger the render. # context.remove(instance) From 8499660acbf9dc0317db2cd6010d65022942a47a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 12:14:24 +0100 Subject: [PATCH 349/727] 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 350/727] 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 351/727] 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 352/727] 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 353/727] 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 354/727] 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 355/727] 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 356/727] 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 357/727] 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 358/727] 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 359/727] 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 6ef55adaf044a2c3ece228688fdae2cafcb7e5ec Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Apr 2024 21:01:19 +0200 Subject: [PATCH 360/727] add missing key --- .../houdini/plugins/publish/collect_local_render_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index ae98e6ed87..ea1eeb62af 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -72,6 +72,7 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): "frameStart": instance.data["frameStartHandle"], "frameEnd": instance.data["frameEndHandle"], "productType": product_type, + "family": product_type, "productName": product_name, "productGroup": product_group, "families": ["render.local.hou"], From 409c243516c498164cd115de8daea4cdf72d5206 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Apr 2024 21:47:13 +0200 Subject: [PATCH 361/727] update node parameter in the extractor instead of the collector --- .../plugins/publish/collect_arnold_rop.py | 4 +- .../plugins/publish/collect_farm_instances.py | 26 +------ .../plugins/publish/collect_mantra_rop.py | 4 +- .../plugins/publish/collect_redshift_rop.py | 4 +- .../plugins/publish/collect_vray_rop.py | 4 +- .../plugins/publish/extract_local_render.py | 63 ----------------- .../houdini/plugins/publish/extract_render.py | 67 +++++++++++++++++++ 7 files changed, 74 insertions(+), 98 deletions(-) delete mode 100644 client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/extract_render.py diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py index 7fe38555a3..c373d94653 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -41,11 +41,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("ar_ass_export_enable").eval()) - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "ar_ass_file", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index 391afe7387..c5a982996b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -16,31 +16,8 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): label = "Collect farm instances" def process(self, instance): - import hou creator_attribute = instance.data["creator_attributes"] - product_type = instance.data["productType"] - rop_node = hou.node(instance.data.get("instance_node")) - - # Align split parameter value on rop node to the render target. - if creator_attribute.get("render_target") == "farm_split": - if product_type == "arnold_rop": - rop_node.setParms({"ar_ass_export_enable": 1}) - elif product_type == "mantra_rop": - rop_node.setParms({"soho_outputmode": 1}) - elif product_type == "redshift_rop": - rop_node.setParms({"RS_archive_enable": 1}) - elif product_type == "vray_rop": - rop_node.setParms({"render_export_mode": "2"}) - else: - if product_type == "arnold_rop": - rop_node.setParms({"ar_ass_export_enable": 0}) - elif product_type == "mantra_rop": - rop_node.setParms({"soho_outputmode": 0}) - elif product_type == "redshift_rop": - rop_node.setParms({"RS_archive_enable": 0}) - elif product_type == "vray_rop": - rop_node.setParms({"render_export_mode": "1"}) # Collect Render Target if creator_attribute.get("render_target") not in { @@ -52,3 +29,6 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): return instance.data["farm"] = True + instance.data["splitRender"] = ( + creator_attribute.get("render_target") == "farm_split" + ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py index df9acc4b61..9894e2beda 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -45,11 +45,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("soho_outputmode").eval()) - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "soho_diskfile", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py index 191a9c1ebc..bd01f929c3 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -43,10 +43,8 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix") beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix") # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("RS_archive_enable").eval()) - instance.data["splitRender"] = split_render export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "RS_archive_file", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py index 62b7dcdd5d..63e16d541d 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -46,11 +46,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): # TODO: add render elements if render element # Store whether we are splitting the render job in an export + render - split_render = rop.parm("render_export_mode").eval() == "2" - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "render_export_filepath", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py deleted file mode 100644 index 3f332acc55..0000000000 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_local_render.py +++ /dev/null @@ -1,63 +0,0 @@ -import pyblish.api - -from ayon_core.pipeline import publish -from ayon_core.hosts.houdini.api.lib import render_rop -import hou -import os - - -class ExtractLocalRender(publish.Extractor): - - order = pyblish.api.ExtractorOrder - label = "Extract Local Render" - hosts = ["houdini"] - families = ["mantra_rop", - "karma_rop", - "redshift_rop", - "arnold_rop", - "vray_rop"] - - def process(self, instance): - if instance.data.get("farm"): - self.log.debug("Should be processed on farm, skipping.") - return - - creator_attribute = instance.data["creator_attributes"] - - if creator_attribute.get("render_target") == "local_no_render": - self.log.debug("Skip render is enabled, skipping rendering.") - return - - # Make sure split parameter is turned off. - # Otherwise, render nodes will generate intermediate - # render files instead of render. - product_type = instance.data["productType"] - rop_node = hou.node(instance.data.get("instance_node")) - - if product_type == "arnold_rop": - rop_node.setParms({"ar_ass_export_enable": 0}) - elif product_type == "mantra_rop": - rop_node.setParms({"soho_outputmode": 0}) - elif product_type == "redshift_rop": - rop_node.setParms({"RS_archive_enable": 0}) - elif product_type == "vray_rop": - rop_node.setParms({"render_export_mode": "1"}) - - ropnode = hou.node(instance.data.get("instance_node")) - render_rop(ropnode) - - # Check missing frames. - # Frames won't exist if user cancels the render. - expected_files = next(iter(instance.data["expectedFiles"]), {}) - # TODO: enhance the readability. - expected_files = sum(expected_files.values(), []) - missing_frames = [ - frame - for frame in expected_files - if not os.path.exists(frame) - ] - if missing_frames: - # TODO: Use user friendly error reporting. - raise RuntimeError("Failed to complete render extraction. " - "Missing output files: {}".format( - missing_frames)) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py new file mode 100644 index 0000000000..7ea276a94d --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -0,0 +1,67 @@ +import pyblish.api + +from ayon_core.pipeline import publish +from ayon_core.hosts.houdini.api.lib import render_rop +import hou +import os + + +class ExtractRender(publish.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract Render" + hosts = ["houdini"] + families = ["mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop"] + + def process(self, instance): + creator_attribute = instance.data["creator_attributes"] + product_type = instance.data["productType"] + rop_node = hou.node(instance.data.get("instance_node")) + + # Align split parameter value on rop node to the render target. + if creator_attribute.get("render_target") == "farm_split": + if product_type == "arnold_rop": + rop_node.setParms({"ar_ass_export_enable": 1}) + elif product_type == "mantra_rop": + rop_node.setParms({"soho_outputmode": 1}) + elif product_type == "redshift_rop": + rop_node.setParms({"RS_archive_enable": 1}) + elif product_type == "vray_rop": + rop_node.setParms({"render_export_mode": "2"}) + else: + if product_type == "arnold_rop": + rop_node.setParms({"ar_ass_export_enable": 0}) + elif product_type == "mantra_rop": + rop_node.setParms({"soho_outputmode": 0}) + elif product_type == "redshift_rop": + rop_node.setParms({"RS_archive_enable": 0}) + elif product_type == "vray_rop": + rop_node.setParms({"render_export_mode": "1"}) + + if instance.data.get("farm"): + self.log.debug("Render should be processed on farm, skipping local render.") + return + + if creator_attribute.get("render_target") == "local": + ropnode = hou.node(instance.data.get("instance_node")) + render_rop(ropnode) + + # Check missing frames. + # Frames won't exist if user cancels the render. + expected_files = next(iter(instance.data["expectedFiles"]), {}) + # TODO: enhance the readability. + expected_files = sum(expected_files.values(), []) + missing_frames = [ + frame + for frame in expected_files + if not os.path.exists(frame) + ] + if missing_frames: + # TODO: Use user friendly error reporting. + raise RuntimeError("Failed to complete render extraction. " + "Missing output files: {}".format( + missing_frames)) From 497ce6d0127792af74e0301239c98154e819e5cd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Apr 2024 11:31:09 +0200 Subject: [PATCH 362/727] Update client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../deadline/plugins/publish/submit_celaction_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index 2ff50a16b9..2220442dac 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -195,7 +195,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): instance.data["expectedFiles"])) response = requests_post(self.deadline_url, json=payload, - auth=instance.context.data["deadline_auth"]) + auth=instance.data["deadline"]["require_authentication"]) if not response.ok: self.log.error( From f43fbc239c6a46525b203a2deb1bb30d99ab1e4e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Apr 2024 11:32:41 +0200 Subject: [PATCH 363/727] Use collected host name Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/collect_deadline_server_from_instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index c6b30d3b2a..3927b67d37 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -37,7 +37,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): instance.data["deadline"] = {} # todo: separate logic should be removed, all hosts should have same - host_name = get_current_host_name() + host_name = instance.context.data["hostName"] if host_name == "maya": deadline_url = self._collect_deadline_url(instance) else: From 48a1dc86ffdb787b7bf4718eb8dcb7e15e577f57 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Apr 2024 11:33:21 +0200 Subject: [PATCH 364/727] Add todo Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../deadline/plugins/publish/collect_user_credentials.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 86418387a5..2777cc906a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -73,7 +73,8 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): if not deadline_info["require_authentication"]: return - + # TODO import 'get_addon_site_settings' when available + # in public 'ayon_api' local_settings = get_server_api_connection().get_addon_site_settings( DeadlineModule.name, __version__) local_settings = local_settings["local_settings"] From ff2296def65d9ab262facdab63a97b9c1c3f9573 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 16 Apr 2024 11:33:46 +0200 Subject: [PATCH 365/727] Removed unneeded import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/collect_deadline_server_from_instance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 3927b67d37..181b553a61 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -7,7 +7,6 @@ attribute or using default server if that attribute doesn't exists. """ import pyblish.api from ayon_core.pipeline.publish import KnownPublishError -from ayon_core.pipeline.context_tools import get_current_host_name class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): From 64a26e21874681102e73cd434de95699f6ac6b09 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 17:40:18 +0800 Subject: [PATCH 366/727] 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 367/727] 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 368/727] 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 369/727] 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 6a93e29923e75f4edb3dc501105e322927eccb44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:01:35 +0200 Subject: [PATCH 370/727] fix applications addon version --- client/ayon_core/addon/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 3d028dba07..21b1193b07 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -50,7 +50,7 @@ IGNORED_MODULES_IN_AYON = set() # When addon was moved from ayon-core codebase # - this is used to log the missing addon MOVED_ADDON_MILESTONE_VERSIONS = { - "applications": VersionInfo(2, 0, 0), + "applications": VersionInfo(0, 2, 0), } # Inherit from `object` for Python 2 hosts From e8e1a1a2dffd197e61101ccc910c9d3f69c01a1f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 14:09:21 +0200 Subject: [PATCH 371/727] 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 c1cfd266f61add3a45cd0c0f4b5bb772c6044f82 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Tue, 16 Apr 2024 16:51:54 +0200 Subject: [PATCH 372/727] Just fixing typo in "product" which broke functionality. --- .../ayon_core/hosts/maya/plugins/inventory/connect_geometry.py | 2 +- client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py | 2 +- .../ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py index 839a4dad90..5410546a2e 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py @@ -37,7 +37,7 @@ class ConnectGeometry(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py index bf9e679928..166c419072 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py @@ -36,7 +36,7 @@ class ConnectXgen(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py index 5916bf7b97..8f13cc6ae5 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py @@ -39,7 +39,7 @@ class ConnectYetiRig(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) From e5eec7f558e20e138fa532c3a881c27fa89d39d7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 22:53:09 +0800 Subject: [PATCH 373/727] 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 374/727] 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 375/727] 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 ca3f3910232fc4077574c8272b222c9014ecd2fe Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 17:55:59 +0200 Subject: [PATCH 376/727] add review support on farm render --- .../plugins/create/create_arnold_rop.py | 6 ++++- .../plugins/create/create_karma_rop.py | 4 ++++ .../plugins/create/create_mantra_rop.py | 4 ++++ .../plugins/create/create_redshift_rop.py | 6 ++++- .../houdini/plugins/create/create_vray_rop.py | 4 ++++ .../plugins/publish/collect_arnold_rop.py | 11 ++++++++++ .../plugins/publish/collect_karma_rop.py | 6 +++++ .../plugins/publish/collect_mantra_rop.py | 10 +++++++++ .../plugins/publish/collect_redshift_rop.py | 11 ++++++++++ .../publish/collect_reviewable_instances.py | 22 +++++++++++++++++++ .../plugins/publish/collect_vray_rop.py | 10 +++++++++ 11 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index 0f2fc89764..d3254a28dd 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,5 +1,5 @@ from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef +from ayon_core.lib import EnumDef, BoolDef class CreateArnoldRop(plugin.HoudiniCreator): @@ -81,6 +81,10 @@ class CreateArnoldRop(plugin.HoudiniCreator): } return [ + BoolDef("review", + label="Review", + tooltip="Mark as reviewable", + default=True), EnumDef("render_target", items=render_target_items, label="Render target", diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index c795512469..0af2fe8aeb 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -103,6 +103,10 @@ class CreateKarmaROP(plugin.HoudiniCreator): } return [ + BoolDef("review", + label="Review", + tooltip="Mark as reviewable", + default=True), EnumDef("render_target", items=render_target_items, label="Render target", diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index d0fc79f608..eac7f06b90 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -92,6 +92,10 @@ class CreateMantraROP(plugin.HoudiniCreator): } return [ + BoolDef("review", + label="Review", + tooltip="Mark as reviewable", + default=True), EnumDef("render_target", items=render_target_items, label="Render target", diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 0094269f47..2a87d2b35c 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -4,7 +4,7 @@ import hou # noqa from ayon_core.pipeline import CreatorError from ayon_core.hosts.houdini.api import plugin -from ayon_core.lib import EnumDef +from ayon_core.lib import EnumDef, BoolDef class CreateRedshiftROP(plugin.HoudiniCreator): @@ -136,6 +136,10 @@ class CreateRedshiftROP(plugin.HoudiniCreator): } return [ + BoolDef("review", + label="Review", + tooltip="Mark as reviewable", + default=True), EnumDef("render_target", items=render_target_items, label="Render target", diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 8788af4748..cdaee7db06 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -158,6 +158,10 @@ class CreateVrayROP(plugin.HoudiniCreator): } return [ + BoolDef("review", + label="Review", + tooltip="Mark as reviewable", + default=True), EnumDef("render_target", items=render_target_items, label="Render target", diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py index c373d94653..fa9a1eea0f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -66,6 +66,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): "": self.generate_expected_files(instance, beauty_product) } + # Assume it's a multipartExr Render. + multipartExr = True + num_aovs = rop.evalParm("ar_aovs") for index in range(1, num_aovs + 1): # Skip disabled AOVs @@ -83,6 +86,14 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov[label] = self.generate_expected_files(instance, aov_product) + # Set to False as soon as we have a separated aov. + multipartExr = False + + # Review Logic expects this key to exist and be True + # if render is a multipart Exr. + # As long as we have one AOV then multipartExr should be True. + instance.data["multipartExr"] = multipartExr + for product in render_products: self.log.debug("Found render product: {}".format(product)) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py index 78651b0c69..662ed7ae30 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -55,6 +55,12 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): beauty_product) } + # Review Logic expects this key to exist and be True + # if render is a multipart Exr. + # As long as we have one AOV then multipartExr should be True. + # By default karma render is a multipart Exr. + instance.data["multipartExr"] = True + filenames = list(render_products) instance.data["files"] = filenames instance.data["renderProducts"] = colorspace.ARenderProduct() diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py index 9894e2beda..e85751c08a 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -72,6 +72,8 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): beauty_product) } + # Assume it's a multipartExr Render. + multipartExr = True aov_numbers = rop.evalParm("vm_numaux") if aov_numbers > 0: # get the filenames of the AOVs @@ -83,6 +85,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): aov_enabled = rop.evalParm(aov_boolean) has_aov_path = rop.evalParm(aov_name) if has_aov_path and aov_enabled == 1: + # Set to False as soon as we have a separated aov. + multipartExr = False + aov_prefix = evalParmNoFrame(rop, aov_name) aov_product = self.get_render_product_name( prefix=aov_prefix, suffix=None @@ -91,6 +96,11 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa + # Review Logic expects this key to exist and be True + # if render is a multipart Exr. + # As long as we have one AOV then multipartExr should be True. + instance.data["multipartExr"] = multipartExr + for product in render_products: self.log.debug("Found render product: %s" % product) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py index bd01f929c3..aff9269fa5 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -80,6 +80,9 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): "RS_aovMultipart": True }) + # Assume it's a multipartExr Render. + multipartExr = True + # Default beauty/main layer AOV beauty_product = self.get_render_product_name( prefix=default_prefix, suffix=beauty_suffix @@ -119,6 +122,14 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov[aov_suffix] = self.generate_expected_files(instance, aov_product) # noqa + # Set to False as soon as we have a separated aov. + multipartExr = False + + # Review Logic expects this key to exist and be True + # if render is a multipart Exr. + # As long as we have one AOV then multipartExr should be True. + instance.data["multipartExr"] = multipartExr + for product in render_products: self.log.debug("Found render product: %s" % product) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py new file mode 100644 index 0000000000..78dc5fe11a --- /dev/null +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_reviewable_instances.py @@ -0,0 +1,22 @@ +import pyblish.api + + +class CollectReviewableInstances(pyblish.api.InstancePlugin): + """Collect Reviewable Instances. + + Basically, all instances of the specified families + with creator_attribure["review"] + """ + + order = pyblish.api.CollectorOrder + label = "Collect Reviewable Instances" + families = ["mantra_rop", + "karma_rop", + "redshift_rop", + "arnold_rop", + "vray_rop"] + + def process(self, instance): + creator_attribute = instance.data["creator_attributes"] + + instance.data["review"] = creator_attribute.get("review", False) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py index 63e16d541d..2eb5e3164a 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -68,6 +68,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): "": self.generate_expected_files(instance, beauty_product)} + # Assume it's a multipartExr Render. + multipartExr = True + if instance.data.get("RenderElement", True): render_element = self.get_render_element_name(rop, default_prefix) if render_element: @@ -76,6 +79,13 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov[aov] = self.generate_expected_files( instance, renderpass) + # Set to False as soon as we have a separated aov. + multipartExr = False + + # Review Logic expects this key to exist and be True + # if render is a multipart Exr. + # As long as we have one AOV then multipartExr should be True. + instance.data["multipartExr"] = multipartExr for product in render_products: self.log.debug("Found render product: %s" % product) From 1ddf28c752f03752c22066a4714e752bcb5379c7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 18:18:52 +0200 Subject: [PATCH 377/727] add missing key --- .../hosts/houdini/plugins/publish/collect_farm_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py index c5a982996b..586aa2da57 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_farm_instances.py @@ -24,6 +24,7 @@ class CollectFarmInstances(pyblish.api.InstancePlugin): "farm_split", "farm" }: instance.data["farm"] = False + instance.data["splitRender"] = False self.log.debug("Render on farm is disabled. " "Skipping farm collecting.") return From 205fc0ed21f6a2623d2cc38fc82b7dc1c7a3a216 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 18:19:19 +0200 Subject: [PATCH 378/727] add review support on local render --- .../publish/collect_local_render_instances.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index ea1eeb62af..5918366f06 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -63,8 +63,18 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): if len(aov_filenames) == 1: aov_filenames = aov_filenames[0] - # TODO: Add some option to allow users to mark - # aov_instances as reviewable. + preview = False + if instance.data.get("multipartExr", False): + self.log.debug( + "Adding preview tag because its multipartExr" + ) + preview = True + else: + # TODO: set Preview to True if aov_name matched some regex. + # Also, I'm not sure where that regex is defined. + pass + + preview = preview and instance.data.get("review", False) aov_instance.data.update({ # 'label': label, "task": instance.data["task"], @@ -75,14 +85,14 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): "family": product_type, "productName": product_name, "productGroup": product_group, - "families": ["render.local.hou"], + "families": ["render.local.hou", "review"], "instance_node": instance.data["instance_node"], "representations": [ { "stagingDir": staging_dir, "ext": ext, "name": ext, - "tags": [], + "tags": ["review"] if preview else [], "files": aov_filenames, "frameStart": instance.data["frameStartHandle"], "frameEnd": instance.data["frameEndHandle"] From 991eee9657e7a2e3a56f016170a9d095ce8c1637 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Apr 2024 18:25:49 +0200 Subject: [PATCH 379/727] Fix PreLaunchHook import --- .../ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py index e70d4b844e..113a1ffe59 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py @@ -1,5 +1,5 @@ import os -from ayon_core.lib import PreLaunchHook +from ayon_applications import PreLaunchHook from ayon_core.hosts.fusion import FUSION_HOST_DIR From 95a69a1d8d994c5345c78d1354e32a333983eef2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 13:31:13 +0800 Subject: [PATCH 380/727] 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 381/727] 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 382/727] 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 0ebed6d2c9518bb2cde7df8a5316be30b6e7cd93 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 17 Apr 2024 11:17:24 +0200 Subject: [PATCH 383/727] support $F in Houdini pointcache Abc product type --- .../houdini/plugins/publish/collect_cache_farm.py | 2 +- .../hosts/houdini/plugins/publish/collect_frames.py | 3 ++- .../hosts/houdini/plugins/publish/extract_alembic.py | 11 ++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py index 040ad68a1a..2e3447d4a6 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py @@ -7,7 +7,7 @@ from ayon_core.hosts.houdini.api import lib class CollectDataforCache(pyblish.api.InstancePlugin): """Collect data for caching to Deadline.""" - order = pyblish.api.CollectorOrder + 0.04 + order = pyblish.api.CollectorOrder + 0.11 families = ["ass", "pointcache", "mantraifd", "redshiftproxy", "vdbcache"] diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py index a643ab0d38..7f294560eb 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py @@ -17,7 +17,7 @@ class CollectFrames(pyblish.api.InstancePlugin): label = "Collect Frames" families = ["vdbcache", "imagesequence", "ass", "mantraifd", "redshiftproxy", "review", - "bgeo"] + "pointcache"] def process(self, instance): @@ -61,6 +61,7 @@ class CollectFrames(pyblish.api.InstancePlugin): # todo: `frames` currently conflicts with "explicit frames" for a # for a custom frame list. So this should be refactored. instance.data.update({"frames": result}) + self.log.debug(instance.data["frames"]) @staticmethod def create_file_list(match, start_frame, end_frame): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_alembic.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_alembic.py index daf30b26ed..7ae476d2b4 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_alembic.py @@ -28,10 +28,15 @@ class ExtractAlembic(publish.Extractor): staging_dir = os.path.dirname(output) instance.data["stagingDir"] = staging_dir - file_name = os.path.basename(output) + if instance.data.get("frames"): + # list of files + files = instance.data["frames"] + else: + # single file + files = os.path.basename(output) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, + self.log.info("Writing alembic '%s' to '%s'" % (files, staging_dir)) render_rop(ropnode) @@ -42,7 +47,7 @@ class ExtractAlembic(publish.Extractor): representation = { 'name': 'abc', 'ext': 'abc', - 'files': file_name, + 'files': files, "stagingDir": staging_dir, } instance.data["representations"].append(representation) 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 384/727] 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 385/727] 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 386/727] 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 787ebed3466254af39db4734c50592399b9eb33b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 18:13:31 +0800 Subject: [PATCH 387/727] fix the bug of repair action failing to fix the validate resolution settings --- .../publish/validate_resolution_setting.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 49beecbabc..9f2d94f6e6 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -7,7 +7,10 @@ from ayon_core.pipeline.publish import ( RepairAction, PublishValidationError ) -from ayon_core.hosts.max.api.lib import reset_scene_resolution +from ayon_core.hosts.max.api.lib import ( + reset_scene_resolution, + imprint +) class ValidateResolutionSetting(pyblish.api.InstancePlugin, @@ -79,5 +82,12 @@ class ValidateReviewResolutionSetting(ValidateResolutionSetting): context_width, context_height = ( cls.get_folder_resolution(instance) ) - instance.data["review_width"] = context_width - instance.data["review_height"] = context_height + creator_attrs = instance.data["creator_attributes"] + creator_attrs["review_width"] = context_width + creator_attrs["review_height"] = context_height + creator_attrs_data = { + "creator_attributes": creator_attrs + } + # update the width and height of review + # data in creator_attributes + imprint(instance.data["instance_node"], creator_attrs_data) From 70f64accbcaa6d395d166c3938d9d23d6e0f1357 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 20:10:56 +0800 Subject: [PATCH 388/727] use "validate resolution setting" as label for validate resolution setting in the review family --- .../hosts/max/plugins/publish/validate_resolution_setting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 9f2d94f6e6..5f6cd0a21d 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -68,7 +68,6 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, class ValidateReviewResolutionSetting(ValidateResolutionSetting): families = ["review"] - label = "Validate Review Animation Resolution Setting" optional = True actions = [RepairAction] From b09e181e27644a51b1b7d58e3aca73fc6918c13a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 14:52:44 +0200 Subject: [PATCH 389/727] Fix usage of `BaseServerAddon` when only imported for type checking --- server_addon/deadline/server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 83c7567c0d..21a314cd2f 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -22,7 +22,7 @@ class ServerListSubmodel(BaseSettingsModel): async def defined_deadline_ws_name_enum_resolver( - addon: BaseServerAddon, + addon: "BaseServerAddon", settings_variant: str = "production", project_name: str | None = None, ) -> list[str]: From d7d91b62a9d91e72dde457ed0503692e37fb0445 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 17 Apr 2024 15:18:00 +0200 Subject: [PATCH 390/727] add settings for CollectLocalRenderInstances --- .../houdini/server/settings/publish.py | 36 ++++++++++++++++++- server_addon/houdini/server/version.py | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index 8e0e7f7795..0912ecd997 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -1,4 +1,7 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField +) # Publish Plugins @@ -20,6 +23,25 @@ class CollectChunkSizeModel(BaseSettingsModel): title="Frames Per Task") +class AOVFilterSubmodel(BaseSettingsModel): + value: list[str] = SettingsField( + default_factory=list, + title="AOV regex" + ) + +class CollectLocalRenderInstancesModel(BaseSettingsModel): + + override_deadline_aov_filter: bool = SettingsField( + False, + title="Override Deadline AOV Filter" + ) + + aov_filter: AOVFilterSubmodel = SettingsField( + default_factory=AOVFilterSubmodel, + title="Reviewable products filter" + ) + + class ValidateWorkfilePathsModel(BaseSettingsModel): enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") @@ -49,6 +71,10 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=CollectChunkSizeModel, title="Collect Chunk Size." ) + CollectLocalRenderInstances: CollectLocalRenderInstancesModel = SettingsField( + default_factory=CollectLocalRenderInstancesModel, + title="Collect Local Render Instances." + ) ValidateContainers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Latest Containers.", @@ -82,6 +108,14 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "optional": True, "chunk_size": 999999 }, + "CollectLocalRenderInstances": { + "override_deadline_aov_filter": False, + "aov_filter" : { + "value": [ + ".*([Bb]eauty).*" + ] + } + }, "ValidateContainers": { "enabled": True, "optional": True, 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 885cfcb203d9874b5ec267c0d8bdd7b228194266 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 17 Apr 2024 16:24:20 +0200 Subject: [PATCH 391/727] apply aov_filter in Houdini local render --- .../publish/collect_local_render_instances.py | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 5918366f06..073053188c 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -1,6 +1,11 @@ import os import pyblish.api from ayon_core.pipeline.create import get_product_name +from ayon_core.pipeline.farm.patterning import match_aov_pattern +from ayon_core.pipeline.publish import ( + get_plugin_settings, + apply_plugin_settings_automatically +) class CollectLocalRenderInstances(pyblish.api.InstancePlugin): @@ -20,6 +25,32 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): hosts = ["houdini"] label = "Collect local render instances" + override_deadline_aov_filter = False + aov_filter = {} + + @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) + + if not cls.override_deadline_aov_filter: + # get aov_filter from collector settings + # and restructure it as match_aov_pattern requires. + cls.aov_filter = { + "houdini": cls.aov_filter["value"] + } + else: + # get aov_filter from deadline settings + cls.aov_filter = project_settings["deadline"]["publish"]["ProcessSubmittedJobOnFarm"]["aov_filter"] + cls.aov_filter = { + item["name"]: item["value"] + for item in cls.aov_filter + } + def process(self, instance): if instance.data["farm"]: @@ -29,7 +60,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): # Create Instance for each AOV. context = instance.context - self.log.debug(instance.data["expectedFiles"]) expectedFiles = next(iter(instance.data["expectedFiles"]), {}) product_type = "render" # is always render @@ -56,6 +86,19 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): staging_dir = os.path.dirname(aov_filepaths[0]) ext = aov_filepaths[0].split(".")[-1] + # Decide if instance is reviewable + preview = False + if instance.data.get("multipartExr", False): + # Add preview tag because its multipartExr. + preview = True + else: + # Add Preview tag if the AOV matches the filter. + preview = match_aov_pattern( + "houdini", self.aov_filter, aov_filenames[0] + ) + + preview = preview and instance.data.get("review", False) + # Support Single frame. # The integrator wants single files to be a single # filename instead of a list. @@ -63,18 +106,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): if len(aov_filenames) == 1: aov_filenames = aov_filenames[0] - preview = False - if instance.data.get("multipartExr", False): - self.log.debug( - "Adding preview tag because its multipartExr" - ) - preview = True - else: - # TODO: set Preview to True if aov_name matched some regex. - # Also, I'm not sure where that regex is defined. - pass - - preview = preview and instance.data.get("review", False) aov_instance.data.update({ # 'label': label, "task": instance.data["task"], From d4fdf8530605ca4f03957415f48d99eef292d11b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 22:45:47 +0800 Subject: [PATCH 392/727] 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 393/727] 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 03cfed2c7957538a9c72afe4e31c010cb1d606ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 20:33:39 +0200 Subject: [PATCH 394/727] Add more family so attribute definitions show in Nuke and Fusion for `render`, `image` and `prerender`. This should be safe because `instance.data.get("farm")` is checked in `process()` and if not true the processing is skipped anyway - so if e.g. a render instance in Fusion is set to render local instead of on the farm the actual attribute definition does show - but the processing of the plug-in is skipped regardless. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 41445fabc3..99a5f94cf1 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -88,9 +88,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, hosts = ["fusion", "max", "maya", "nuke", "houdini", "celaction", "aftereffects", "harmony", "blender"] - families = ["render.farm", "render.frames_farm", - "prerender.farm", "prerender.frames_farm", - "renderlayer", "imagesequence", + families = ["render", "render.farm", "render.frames_farm", + "prerender", "prerender.farm", "prerender.frames_farm", + "renderlayer", "imagesequence", "image", "vrayscene", "maxrender", "arnold_rop", "mantra_rop", "karma_rop", "vray_rop", @@ -311,7 +311,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. From 5b7511ed84ac348f3a0065b8e77fb2be9e44cdc9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 23:28:54 +0200 Subject: [PATCH 395/727] 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 396/727] 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 397/727] 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 398/727] 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 399/727] 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 400/727] 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 401/727] 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 402/727] 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 403/727] 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 404/727] 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 405/727] 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 406/727] 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 407/727] 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 408/727] 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 bd42a506cfd8b9c143f18c198e5920459e836124 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:14:06 +0800 Subject: [PATCH 409/727] make sure the bake animation is boolean option --- client/ayon_core/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 939da4011b..3f1395cb40 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -47,7 +47,7 @@ class FBXExtractor: "smoothMesh": bool, "instances": bool, # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": int, + "bakeComplexAnimation": bool, "bakeComplexStart": int, "bakeComplexEnd": int, "bakeComplexStep": int, From 8ff929e7199817eb006e3b5fb4e788268f87ddb1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:19:32 +0800 Subject: [PATCH 410/727] Revert "make sure the bake animation is boolean option" This reverts commit bd42a506cfd8b9c143f18c198e5920459e836124. --- client/ayon_core/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 3f1395cb40..939da4011b 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -47,7 +47,7 @@ class FBXExtractor: "smoothMesh": bool, "instances": bool, # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": bool, + "bakeComplexAnimation": int, "bakeComplexStart": int, "bakeComplexEnd": int, "bakeComplexStep": int, From d094f83efbb7e2f2d4c158dafc3ba6cee93a914e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:22:39 +0800 Subject: [PATCH 411/727] make sure the bake animation is boolean option --- client/ayon_core/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 939da4011b..3f1395cb40 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -47,7 +47,7 @@ class FBXExtractor: "smoothMesh": bool, "instances": bool, # "referencedContainersContent": bool, # deprecated in Maya 2016+ - "bakeComplexAnimation": int, + "bakeComplexAnimation": bool, "bakeComplexStart": int, "bakeComplexEnd": int, "bakeComplexStep": int, From edee279f15fa16b549db68e3adb6aba14fc2f541 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 12:04:24 +0200 Subject: [PATCH 412/727] 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 413/727] 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 414/727] 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 4bc53958128daa420d96b4bb0627e3175b736163 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 Apr 2024 13:06:31 +0200 Subject: [PATCH 415/727] Refactor - updated names for default deadline url --- .../publish/collect_deadline_server_from_instance.py | 8 ++++---- .../plugins/publish/collect_default_deadline_server.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index c6b30d3b2a..9741571e88 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -46,7 +46,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): if deadline_url: instance.data["deadline"]["url"] = deadline_url.strip().rstrip("/") else: - instance.data["deadline"]["url"] = instance.context.data["deadline"]["defaultDeadline"] # noqa + instance.data["deadline"]["url"] = instance.context.data["deadline"]["defaultUrl"] # noqa self.log.debug( "Using {} for submission".format(instance.data["deadline"]["url"])) @@ -74,13 +74,13 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): ["project_settings"] ["deadline"] ) - default_server = (render_instance.context.data["deadline"] - ["defaultDeadline"]) + default_server_url = (render_instance.context.data["deadline"] + ["defaultUrl"]) # QUESTION How and where is this is set? Should be removed? instance_server = render_instance.data.get("deadlineServers") if not instance_server: self.log.debug("Using default server.") - return default_server + return default_server_url # Get instance server as sting. if isinstance(instance_server, int): diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index 6fca97b4ef..dde1043301 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -44,5 +44,5 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): deadline_url = default_dl_server_info["value"] context.data["deadline"] = {} - context.data["deadline"]["defaultDeadline"] = ( + context.data["deadline"]["defaultUrl"] = ( deadline_url.strip().rstrip("/")) From 090304a4a8ac9e81b6434dd3825a5d1a160bfeeb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 Apr 2024 14:06:37 +0200 Subject: [PATCH 416/727] Refactor - move deadline plugins later Run them after collect render plugins to better differentiate between local and farm targetted plugins. --- .../publish/collect_deadline_server_from_instance.py | 8 ++++++-- .../plugins/publish/collect_default_deadline_server.py | 2 +- .../deadline/plugins/publish/collect_user_credentials.py | 6 +++++- .../plugins/publish/validate_deadline_connection.py | 4 ++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index b769a923fe..22022831a0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -13,7 +13,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): """Collect Deadline Webservice URL from instance.""" # Run before collect_render. - order = pyblish.api.CollectorOrder + 0.005 + order = pyblish.api.CollectorOrder + 0.225 label = "Deadline Webservice from the Instance" targets = ["local"] families = ["render", @@ -32,7 +32,11 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): "image"] # for Fusion def process(self, instance): - if not "deadline" in instance.data: + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + + if not instance.data.get("deadline"): instance.data["deadline"] = {} # todo: separate logic should be removed, all hosts should have same diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index dde1043301..9238e0ed95 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -18,7 +18,7 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): """ # Run before collect_deadline_server_instance. - order = pyblish.api.CollectorOrder + 0.0025 + order = pyblish.api.CollectorOrder + 0.200 label = "Default Deadline Webservice" targets = ["local"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 2777cc906a..7a506ab645 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -20,7 +20,7 @@ from ayon_core.modules.deadline import __version__ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): """Collects user name and password for artist if DL requires authentication """ - order = pyblish.api.CollectorOrder + 0.200 + order = pyblish.api.CollectorOrder + 0.250 label = "Collect Deadline User Credentials" targets = ["local"] @@ -47,6 +47,10 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): "publish.hou"] def process(self, instance): + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + collected_deadline_url = instance.data["deadline"]["url"] if not collected_deadline_url: raise ValueError("Instance doesn't have '[deadline][url]'.") diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py index e077aedd9b..8fffd47786 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -17,6 +17,10 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): responses = {} def process(self, instance): + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" From fbc0ee693595123ae233e577bbdad28bb35ef49c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 Apr 2024 14:08:50 +0200 Subject: [PATCH 417/727] Fix - get source_instance directly from instance It was returning it as list without it --- client/ayon_core/pipeline/publish/abstract_collect_render.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index b515d91ae4..17cab876b6 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -216,13 +216,12 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): # add additional data data = self.add_additional_data(data) - render_instance_dict = attr.asdict(render_instance) - # Merge into source instance if provided, otherwise create instance - instance = render_instance_dict.pop("source_instance", None) + instance = render_instance.source_instance if instance is None: instance = context.create_instance(render_instance.name) + render_instance_dict = attr.asdict(render_instance) instance.data.update(render_instance_dict) instance.data.update(data) From f7c8a23d98729dd462bf60026682bfa3f2e09ae6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 21:23:23 +0800 Subject: [PATCH 418/727] 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 419/727] 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 420/727] 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 421/727] 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 422/727] 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 423/727] 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 424/727] 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 425/727] 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 426/727] 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 427/727] 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 428/727] 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 429/727] 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 430/727] 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 431/727] 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 432/727] 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 433/727] 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 434/727] 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 435/727] 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 436/727] 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 437/727] 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 438/727] 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 439/727] 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 60468e4d7410ff5021d771a09b1e2e16494e6380 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 19 Apr 2024 16:09:18 +0200 Subject: [PATCH 440/727] enhance the readability of checking missing frames in expectedFiles --- .../houdini/plugins/publish/extract_render.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 7ea276a94d..8a666541cb 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -50,14 +50,21 @@ class ExtractRender(publish.Extractor): ropnode = hou.node(instance.data.get("instance_node")) render_rop(ropnode) + # `ExpectedFiles` is a list that includes one dict. + expected_files = instance.data["expectedFiles"][0] + # Each key in that dict is a list of files. + # Combine lists of files into one big list. + all_frames = [] + for value in expected_files.values(): + if isinstance(value, str): + all_frames.append(value) + elif isinstance(value, list): + all_frames.extend(value) # Check missing frames. # Frames won't exist if user cancels the render. - expected_files = next(iter(instance.data["expectedFiles"]), {}) - # TODO: enhance the readability. - expected_files = sum(expected_files.values(), []) missing_frames = [ frame - for frame in expected_files + for frame in all_frames if not os.path.exists(frame) ] if missing_frames: From c03b9269bfcf4fcfc6f6c2bce2d0498b2cd3105e Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Fri, 19 Apr 2024 19:45:36 +0200 Subject: [PATCH 441/727] add a note about plugin order Co-authored-by: Roy Nieterau --- .../hosts/houdini/plugins/publish/collect_cache_farm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py index 2e3447d4a6..e931c7bf1b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_cache_farm.py @@ -7,6 +7,7 @@ from ayon_core.hosts.houdini.api import lib class CollectDataforCache(pyblish.api.InstancePlugin): """Collect data for caching to Deadline.""" + # Run after Collect Frames order = pyblish.api.CollectorOrder + 0.11 families = ["ass", "pointcache", "mantraifd", "redshiftproxy", From 5132bf08f2b24c9594d2d67d693053558e5229ec Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Fri, 19 Apr 2024 19:45:49 +0200 Subject: [PATCH 442/727] remove debug code Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py index 7f294560eb..b38ebc6e2f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_frames.py @@ -61,7 +61,6 @@ class CollectFrames(pyblish.api.InstancePlugin): # todo: `frames` currently conflicts with "explicit frames" for a # for a custom frame list. So this should be refactored. instance.data.update({"frames": result}) - self.log.debug(instance.data["frames"]) @staticmethod def create_file_list(match, start_frame, end_frame): From c260996522872a8bf56f74b52a598f61f9ba3b0e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:07:02 +0200 Subject: [PATCH 443/727] 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 444/727] 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 445/727] 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 8e87ef674daba9f3bb7ae6edc1fd89e617ee304e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:32:36 +0200 Subject: [PATCH 446/727] Maya: Implement workfile template run script placeholder --- .../plugins/template/script_placeholder.py | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 client/ayon_core/hosts/maya/plugins/template/script_placeholder.py diff --git a/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py new file mode 100644 index 0000000000..893e2ec0cb --- /dev/null +++ b/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py @@ -0,0 +1,201 @@ +from maya import cmds + +from ayon_core.hosts.maya.api.workfile_template_builder import ( + MayaPlaceholderPlugin +) +from ayon_core.lib import NumberDef, TextDef, EnumDef +from ayon_core.lib.events import weakref_partial + + +EXAMPLE_SCRIPT = """ +# Access maya commands +from maya import cmds + +# Access the placeholder node +placeholder_node = placeholder.scene_identifier + +# Access the event callback +if event is None: + print(f"Populating {placeholder}") +else: + if event.topic == "template.depth_processed": + print(f"Processed depth: {event.get('depth')}") + elif event.topic == "template.finished": + print("Build finished.") +""".strip() + + +class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): + """Execute a script at the given `order` during workfile build. + + This is a very low-level placeholder to run Python scripts at a given + point in time during the workfile template build. + + It can create either a locator or an objectSet as placeholder node. + It defaults to an objectSet, since allowing to run on e.g. other + placeholder node members can be useful, e.g. using: + + >>> members = cmds.sets(placeholder.scene_identifier, query=True) + + """ + + identifier = "maya.runscript" + label = "Run Python Script" + + use_selection_as_parent = False + + def get_placeholder_options(self, options=None): + options = options or {} + return [ + NumberDef( + "order", + label="Order", + default=options.get("order") or 0, + decimals=0, + minimum=0, + maximum=999, + tooltip=( + "Order" + "\nOrder defines asset loading priority (0 to 999)" + "\nPriority rule is : \"lowest is first to load\"." + ) + ), + TextDef( + "prepare_script", + label="Run at\nprepare", + tooltip="Run before populate at prepare order", + multiline=True, + default=options.get("prepare_script", "") + ), + TextDef( + "populate_script", + label="Run at\npopulate", + tooltip="Run script at populate node order
" + "This is the default behavior", + multiline=True, + default=options.get("populate_script", EXAMPLE_SCRIPT) + ), + TextDef( + "depth_processed_script", + label="Run after\ndepth\niteration", + tooltip="Run script after every build depth iteration", + multiline=True, + default=options.get("depth_processed_script", "") + ), + TextDef( + "finished_script", + label="Run after\nbuild", + tooltip=( + "Run script at build finished.
" + "Note: this even runs if other placeholders had " + "errors during the build" + ), + multiline=True, + default=options.get("finished_script", "") + ), + EnumDef( + "create_nodetype", + label="Nodetype", + items={ + "spaceLocator": "Locator", + "objectSet": "ObjectSet" + }, + tooltip=( + "The placeholder's node type to be created.
" + "Note this only works on create, not on update" + ), + default=options.get("create_nodetype", "objectSet") + ), + ] + + def create_placeholder(self, placeholder_data): + nodetype = placeholder_data.get("create_nodetype", "objectSet") + + if nodetype == "spaceLocator": + super(MayaPlaceholderScriptPlugin, self).create_placeholder( + placeholder_data + ) + elif nodetype == "objectSet": + placeholder_data["plugin_identifier"] = self.identifier + + # Create maya objectSet on selection + selection = cmds.ls(selection=True, long=True) + name = self._create_placeholder_name(placeholder_data) + node = cmds.sets(selection, name=name) + + self.imprint(node, placeholder_data) + + def prepare_placeholders(self, placeholders): + super(MayaPlaceholderScriptPlugin, self).prepare_placeholders( + placeholders + ) + for placeholder in placeholders: + prepare_script = placeholder.data.get("prepare_script") + if not prepare_script: + continue + + self.run_script(placeholder, prepare_script) + + def populate_placeholder(self, placeholder): + + populate_script = placeholder.data.get("populate_script") + depth_script = placeholder.data.get("depth_processed_script") + finished_script = placeholder.data.get("finished_script") + + # Run now + if populate_script: + self.run_script(placeholder, populate_script) + + if not any([depth_script, finished_script]): + # No callback scripts to run + if not placeholder.data.get("keep_placeholder", True): + self.delete_placeholder(placeholder) + return + + # Run at each depth processed + if depth_script: + callback = weakref_partial( + self.run_script, placeholder, depth_script) + self.builder.register_on_depth_processed_callback( + callback, order=placeholder.order) + + # Run at build finish + if finished_script: + callback = weakref_partial( + self.run_script, placeholder, finished_script) + self.builder.register_on_finished_callback( + callback, order=placeholder.order) + + # If placeholder should be deleted, delete it after finish so + # the scripts have access to it up to the last run + if not placeholder.data.get("keep_placeholder", True): + delete_callback = weakref_partial( + self.delete_placeholder, placeholder) + self.builder.register_on_finished_callback( + delete_callback, order=placeholder.order + 1) + + def run_script(self, placeholder, script, event=None): + """Run script + + Even though `placeholder` is an unused arguments by exposing it as + an input argument it means it makes it available through + globals()/locals() in the `exec` call, giving the script access + to the placeholder. + + For example: + >>> node = placeholder.scene_identifier + + In the case the script is running at a callback level (not during + populate) then it has access to the `event` as well, otherwise the + value is None if it runs during `populate_placeholder` directly. + + For example adding this as the callback script: + >>> if event is not None: + >>> if event.topic == "on_depth_processed": + >>> print(f"Processed depth: {event.get('depth')}") + >>> elif event.topic == "on_finished": + >>> print("Build finished.") + + """ + self.log.debug(f"Running script at event: {event}") + exec(script, locals()) From da5abf836773283ccb4d1296a35258cc9106e443 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:24:44 +0200 Subject: [PATCH 447/727] 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 448/727] 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 449/727] 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 450/727] 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 7cf7e33452b22499db077876343af210913b8dd2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:35:57 +0200 Subject: [PATCH 451/727] Refactor adding callbacks --- .../hosts/maya/plugins/workfile_build/script_placeholder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py b/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py index 893e2ec0cb..62e10ba023 100644 --- a/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py @@ -156,14 +156,14 @@ class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): if depth_script: callback = weakref_partial( self.run_script, placeholder, depth_script) - self.builder.register_on_depth_processed_callback( + self.builder.add_on_depth_processed_callback( callback, order=placeholder.order) # Run at build finish if finished_script: callback = weakref_partial( self.run_script, placeholder, finished_script) - self.builder.register_on_finished_callback( + self.builder.add_on_finished_callback( callback, order=placeholder.order) # If placeholder should be deleted, delete it after finish so @@ -171,7 +171,7 @@ class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): if not placeholder.data.get("keep_placeholder", True): delete_callback = weakref_partial( self.delete_placeholder, placeholder) - self.builder.register_on_finished_callback( + self.builder.add_on_finished_callback( delete_callback, order=placeholder.order + 1) def run_script(self, placeholder, script, event=None): 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 452/727] 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 453/727] 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 454/727] 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 455/727] 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 456/727] 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 457/727] 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 458/727] 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 459/727] 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 460/727] 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 461/727] 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 462/727] 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 463/727] 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 464/727] 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 465/727] 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 466/727] 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 467/727] 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 468/727] 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 469/727] 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 470/727] 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 471/727] 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 472/727] 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 473/727] 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 474/727] 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 475/727] 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 476/727] 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 477/727] 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 13fbc5b74f3bc83157bd927ab6d5ff0cd69b9c5f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:13:37 +0200 Subject: [PATCH 478/727] added folder type to template data --- client/ayon_core/pipeline/template_data.py | 5 ++-- .../publish/collect_anatomy_instance_data.py | 28 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py index 526c7d35c5..02bccb5f49 100644 --- a/client/ayon_core/pipeline/template_data.py +++ b/client/ayon_core/pipeline/template_data.py @@ -73,8 +73,8 @@ def get_folder_template_data(folder_entity, project_name): - 'parent' - direct parent name, project name used if is under project - Required document fields: - Folder: 'path' -> Plan to require: 'folderType' + Required entity fields: + Folder: 'path', 'folderType' Args: folder_entity (Dict[str, Any]): Folder entity. @@ -101,6 +101,7 @@ def get_folder_template_data(folder_entity, project_name): return { "folder": { "name": folder_name, + "type": folder_entity["folderType"], }, "asset": folder_name, "hierarchy": hierarchy, diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py index f8cc81e718..f0119ef42e 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py @@ -33,6 +33,7 @@ import collections import pyblish.api import ayon_api +from ayon_core.pipeline.template_data import get_folder_template_data from ayon_core.pipeline.version_start import get_versioning_start @@ -383,24 +384,11 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): # - 'folder', 'hierarchy', 'parent', 'folder' folder_entity = instance.data.get("folderEntity") if folder_entity: - folder_name = folder_entity["name"] - folder_path = folder_entity["path"] - hierarchy_parts = folder_path.split("/") - hierarchy_parts.pop(0) - hierarchy_parts.pop(-1) - parent_name = project_entity["name"] - if hierarchy_parts: - parent_name = hierarchy_parts[-1] - - hierarchy = "/".join(hierarchy_parts) - anatomy_data.update({ - "asset": folder_name, - "hierarchy": hierarchy, - "parent": parent_name, - "folder": { - "name": folder_name, - }, - }) + folder_data = get_folder_template_data( + folder_entity, + project_entity["name"] + ) + anatomy_data.update(folder_data) return if instance.data.get("newAssetPublishing"): @@ -418,6 +406,10 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "parent": parent_name, "folder": { "name": folder_name, + # TODO get folder type from hierarchy + # Using 'Shot' is current default behavior of editorial + # (or 'newAssetPublishing') publishing. + "type": "Shot", }, }) From 6e548a83c9d40be1012201160bb794eec50da6d5 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 23 Apr 2024 21:21:30 +0200 Subject: [PATCH 479/727] expose 'render_target' and 'review' creator attributes in publish tab only --- .../plugins/create/create_arnold_rop.py | 28 ++++++++----- .../plugins/create/create_karma_rop.py | 35 +++++++++++------ .../plugins/create/create_mantra_rop.py | 32 ++++++++++----- .../plugins/create/create_redshift_rop.py | 39 ++++++++++++------- .../houdini/plugins/create/create_vray_rop.py | 35 +++++++++++------ 5 files changed, 115 insertions(+), 54 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index d3254a28dd..1208cfc1ea 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -21,7 +21,9 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - creator_attributes.update(pre_create_data) + for key in ["render_target", "review"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] # Remove the active, we are checking the bypass flag of the nodes instance_data.pop("active", None) @@ -69,10 +71,12 @@ class CreateArnoldRop(plugin.HoudiniCreator): self.lock_parameters(instance_node, to_lock) def get_instance_attr_defs(self): - image_format_enum = [ - "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", - "rad", "rat", "rta", "sgi", "tga", "tif", - ] + """get instance attribute definitions. + + Attributes defined in this method are exposed in + publish tab in the publisher UI. + """ + render_target_items = { "local": "Local machine rendering", "local_no_render": "Use existing frames (local)", @@ -89,12 +93,18 @@ class CreateArnoldRop(plugin.HoudiniCreator): items=render_target_items, label="Render target", default=self.render_target), + ] + + def get_pre_create_attr_defs(self): + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + attrs = [ EnumDef("image_format", image_format_enum, default=self.ext, label="Image Format Options"), ] - - def get_pre_create_attr_defs(self): - - return self.get_instance_attr_defs() + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index 0af2fe8aeb..48cf5057ab 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -19,7 +19,10 @@ class CreateKarmaROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - creator_attributes.update(pre_create_data) + + for key in ["render_target", "review"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "karma"}) @@ -92,10 +95,12 @@ class CreateKarmaROP(plugin.HoudiniCreator): self.lock_parameters(instance_node, to_lock) def get_instance_attr_defs(self): - image_format_enum = [ - "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", - "rad", "rat", "rta", "sgi", "tga", "tif", - ] + """get instance attribute definitions. + + Attributes defined in this method are exposed in + publish tab in the publisher UI. + """ + render_target_items = { "local": "Local machine rendering", "local_no_render": "Use existing frames (local)", @@ -110,7 +115,19 @@ class CreateKarmaROP(plugin.HoudiniCreator): EnumDef("render_target", items=render_target_items, label="Render target", - default=self.render_target), + default=self.render_target) + ] + + + def get_pre_create_attr_defs(self): + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs() + + attrs += [ EnumDef("image_format", image_format_enum, default="exr", @@ -127,8 +144,4 @@ class CreateKarmaROP(plugin.HoudiniCreator): label="Camera Resolution", default=False), ] - - - def get_pre_create_attr_defs(self): - - return self.get_instance_attr_defs() + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index eac7f06b90..05b4431aba 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -19,7 +19,9 @@ class CreateMantraROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - creator_attributes.update(pre_create_data) + for key in ["render_target", "review"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "ifd"}) @@ -80,10 +82,12 @@ class CreateMantraROP(plugin.HoudiniCreator): self.lock_parameters(instance_node, to_lock) def get_instance_attr_defs(self): - image_format_enum = [ - "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", - "rad", "rat", "rta", "sgi", "tga", "tif", - ] + """get instance attribute definitions. + + Attributes defined in this method are exposed in + publish tab in the publisher UI. + """ + render_target_items = { "local": "Local machine rendering", "local_no_render": "Use existing frames (local)", @@ -99,7 +103,18 @@ class CreateMantraROP(plugin.HoudiniCreator): EnumDef("render_target", items=render_target_items, label="Render target", - default=self.render_target), + default=self.render_target) + ] + + def get_pre_create_attr_defs(self): + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + attrs = super(CreateMantraROP, self).get_pre_create_attr_defs() + + attrs += [ EnumDef("image_format", image_format_enum, default="exr", @@ -110,7 +125,4 @@ class CreateMantraROP(plugin.HoudiniCreator): "resolution, recommended for IPR.", default=False), ] - - def get_pre_create_attr_defs(self): - - return self.get_instance_attr_defs() + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py index 2a87d2b35c..3ecb09ee9b 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_redshift_rop.py @@ -24,7 +24,9 @@ class CreateRedshiftROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - creator_attributes.update(pre_create_data) + for key in ["render_target", "review"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "Redshift_ROP"}) @@ -121,13 +123,12 @@ class CreateRedshiftROP(plugin.HoudiniCreator): return super(CreateRedshiftROP, self).remove_instances(instances) def get_instance_attr_defs(self): - image_format_enum = [ - "exr", "tif", "jpg", "png", - ] - multi_layered_mode = [ - "No Multi-Layered EXR File", - "Full Multi-Layered EXR File" - ] + """get instance attribute definitions. + + Attributes defined in this method are exposed in + publish tab in the publisher UI. + """ + render_target_items = { "local": "Local machine rendering", "local_no_render": "Use existing frames (local)", @@ -143,7 +144,22 @@ class CreateRedshiftROP(plugin.HoudiniCreator): EnumDef("render_target", items=render_target_items, label="Render target", - default=self.render_target), + default=self.render_target) + ] + + def get_pre_create_attr_defs(self): + + image_format_enum = [ + "exr", "tif", "jpg", "png", + ] + + multi_layered_mode = [ + "No Multi-Layered EXR File", + "Full Multi-Layered EXR File" + ] + + attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs() + attrs += [ EnumDef("image_format", image_format_enum, default=self.ext, @@ -153,7 +169,4 @@ class CreateRedshiftROP(plugin.HoudiniCreator): default=self.multi_layered_mode, label="Multi-Layered EXR"), ] - - def get_pre_create_attr_defs(self): - - return self.get_instance_attr_defs() + return attrs + self.get_instance_attr_defs() diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index cdaee7db06..9e4633e745 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -23,7 +23,9 @@ class CreateVrayROP(plugin.HoudiniCreator): # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) - creator_attributes.update(pre_create_data) + for key in ["render_target", "review"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] instance_data.pop("active", None) instance_data.update({"node_type": "vray_renderer"}) @@ -146,10 +148,13 @@ class CreateVrayROP(plugin.HoudiniCreator): return super(CreateVrayROP, self).remove_instances(instances) def get_instance_attr_defs(self): - image_format_enum = [ - "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", - "rad", "rat", "rta", "sgi", "tga", "tif", - ] + """get instance attribute definitions. + + Attributes defined in this method are exposed in + publish tab in the publisher UI. + """ + + render_target_items = { "local": "Local machine rendering", "local_no_render": "Use existing frames (local)", @@ -165,7 +170,18 @@ class CreateVrayROP(plugin.HoudiniCreator): EnumDef("render_target", items=render_target_items, label="Render target", - default=self.render_target), + default=self.render_target) + ] + + def get_pre_create_attr_defs(self): + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() + + attrs += [ EnumDef("image_format", image_format_enum, default=self.ext, @@ -179,9 +195,6 @@ class CreateVrayROP(plugin.HoudiniCreator): label="Render Element", tooltip="Create Render Element Node " "if enabled", - default=False), + default=False) ] - - def get_pre_create_attr_defs(self): - - return self.get_instance_attr_defs() + return attrs + self.get_instance_attr_defs() From 74fe21a2b3b6db779390966c3041fe6aef0b6bfa Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 23 Apr 2024 21:30:38 +0200 Subject: [PATCH 480/727] revert changes - add only 'multipartExr' flag --- .../plugins/publish/collect_arnold_rop.py | 6 +++- .../plugins/publish/collect_mantra_rop.py | 13 +++++--- .../plugins/publish/collect_redshift_rop.py | 30 +++++-------------- .../plugins/publish/collect_vray_rop.py | 5 ++-- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py index fa9a1eea0f..3a65b8d026 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -41,9 +41,11 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # Store whether we are splitting the render job (export + render) + split_render = bool(rop.parm("ar_ass_export_enable").eval()) + instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if instance.data["splitRender"]: + if split_render: export_prefix = evalParmNoFrame( rop, "ar_ass_file", pad_character="0" ) @@ -70,6 +72,8 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): multipartExr = True num_aovs = rop.evalParm("ar_aovs") + # TODO: Check the following logic. + # as it always assumes that all AOV are not merged. for index in range(1, num_aovs + 1): # Skip disabled AOVs if not rop.evalParm("ar_enable_aov{}".format(index)): diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py index e85751c08a..6112f0a581 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -45,9 +45,11 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # Store whether we are splitting the render job (export + render) + split_render = bool(rop.parm("soho_outputmode").eval()) + instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if instance.data["splitRender"]: + if split_render: export_prefix = evalParmNoFrame( rop, "soho_diskfile", pad_character="0" ) @@ -74,6 +76,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): # Assume it's a multipartExr Render. multipartExr = True + + # TODO: This logic doesn't take into considerations + # cryptomatte defined in 'Images > Cryptomatte' aov_numbers = rop.evalParm("vm_numaux") if aov_numbers > 0: # get the filenames of the AOVs @@ -85,9 +90,6 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): aov_enabled = rop.evalParm(aov_boolean) has_aov_path = rop.evalParm(aov_name) if has_aov_path and aov_enabled == 1: - # Set to False as soon as we have a separated aov. - multipartExr = False - aov_prefix = evalParmNoFrame(rop, aov_name) aov_product = self.get_render_product_name( prefix=aov_prefix, suffix=None @@ -96,6 +98,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa + # Set to False as soon as we have a separated aov. + multipartExr = False + # Review Logic expects this key to exist and be True # if render is a multipart Exr. # As long as we have one AOV then multipartExr should be True. diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py index aff9269fa5..89868b1c33 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -43,8 +43,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix") beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix") # Store whether we are splitting the render job (export + render) + split_render = bool(rop.parm("RS_archive_enable").eval()) + instance.data["splitRender"] = split_render export_products = [] - if instance.data["splitRender"]: + if split_render: export_prefix = evalParmNoFrame( rop, "RS_archive_file", pad_character="0" ) @@ -58,27 +60,11 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): instance.data["ifdFile"] = beauty_export_product instance.data["exportFiles"] = list(export_products) - # Set MultiLayer Mode. - creator_attribute = instance.data["creator_attributes"] - ext = creator_attribute.get("image_format") - multi_layered_mode = creator_attribute.get("multi_layered_mode") - full_exr_mode = False - if ext == "exr": - if multi_layered_mode == "No Multi-Layered EXR File": - rop.setParms({ - "RS_outputMultilayerMode": "1", - "RS_aovMultipart": False - }) - full_exr_mode = True - # Ignore beauty suffix if full mode is enabled - # As this is what the rop does. - beauty_suffix = "" - - elif multi_layered_mode == "Full Multi-Layered EXR File": - rop.setParms({ - "RS_outputMultilayerMode": "2", - "RS_aovMultipart": True - }) + full_exr_mode = (rop.evalParm("RS_outputMultilayerMode") == "2") + if full_exr_mode: + # Ignore beauty suffix if full mode is enabled + # As this is what the rop does. + beauty_suffix = "" # Assume it's a multipartExr Render. multipartExr = True diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py index 2eb5e3164a..13478a9d2b 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -46,9 +46,11 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): # TODO: add render elements if render element # Store whether we are splitting the render job in an export + render + split_render = rop.parm("render_export_mode").eval() == "2" + instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if instance.data["splitRender"]: + if split_render: export_prefix = evalParmNoFrame( rop, "render_export_filepath", pad_character="0" ) @@ -78,7 +80,6 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): render_products.append(renderpass) files_by_aov[aov] = self.generate_expected_files( instance, renderpass) - # Set to False as soon as we have a separated aov. multipartExr = False From effedd82c8ddb511d844dcdef8724c4c63c2355f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 23 Apr 2024 21:30:58 +0200 Subject: [PATCH 481/727] fix bug with aov_filter --- .../plugins/publish/collect_local_render_instances.py | 9 +++++---- server_addon/houdini/server/settings/publish.py | 9 ++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 073053188c..5a446fa0d3 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -25,8 +25,9 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): hosts = ["houdini"] label = "Collect local render instances" - override_deadline_aov_filter = False - aov_filter = {} + use_deadline_aov_filter = False + aov_filter = {"host_name": "houdini", + "value": [".*([Bb]eauty).*"]} @classmethod def apply_settings(cls, project_settings): @@ -37,11 +38,11 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): category="houdini") apply_plugin_settings_automatically(cls, settings, logger=cls.log) - if not cls.override_deadline_aov_filter: + if not cls.use_deadline_aov_filter: # get aov_filter from collector settings # and restructure it as match_aov_pattern requires. cls.aov_filter = { - "houdini": cls.aov_filter["value"] + cls.aov_filter["host_name"]: cls.aov_filter["value"] } else: # get aov_filter from deadline settings diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index 0912ecd997..9e8e796aff 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -24,6 +24,8 @@ class CollectChunkSizeModel(BaseSettingsModel): class AOVFilterSubmodel(BaseSettingsModel): + """You should use the same host name you are using for Houdini.""" + host_name: str = SettingsField("", title="Houdini Host name") value: list[str] = SettingsField( default_factory=list, title="AOV regex" @@ -31,9 +33,9 @@ class AOVFilterSubmodel(BaseSettingsModel): class CollectLocalRenderInstancesModel(BaseSettingsModel): - override_deadline_aov_filter: bool = SettingsField( + use_deadline_aov_filter: bool = SettingsField( False, - title="Override Deadline AOV Filter" + title="Use Deadline AOV Filter" ) aov_filter: AOVFilterSubmodel = SettingsField( @@ -109,8 +111,9 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { "chunk_size": 999999 }, "CollectLocalRenderInstances": { - "override_deadline_aov_filter": False, + "use_deadline_aov_filter": False, "aov_filter" : { + "host_name": "houdini", "value": [ ".*([Bb]eauty).*" ] From 47b27ce009ee2c8c5b50e10ac3ee32c91f292a8e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 23 Apr 2024 22:16:24 +0200 Subject: [PATCH 482/727] use 'creator_attributes' as the source of truth - use extract render to adjust parameters accordingly --- .../hosts/houdini/plugins/publish/collect_arnold_rop.py | 5 +---- .../hosts/houdini/plugins/publish/collect_mantra_rop.py | 5 +---- .../hosts/houdini/plugins/publish/collect_redshift_rop.py | 6 ++---- .../hosts/houdini/plugins/publish/collect_vray_rop.py | 5 +---- .../hosts/houdini/plugins/publish/extract_render.py | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py index 3a65b8d026..53a3e52717 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -40,12 +40,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "ar_picture") render_products = [] - # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("ar_ass_export_enable").eval()) - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "ar_ass_file", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py index 6112f0a581..7b247768fc 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -44,12 +44,9 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "vm_picture") render_products = [] - # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("soho_outputmode").eval()) - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "soho_diskfile", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py index 89868b1c33..ce90ae2413 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -42,11 +42,9 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix") beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix") - # Store whether we are splitting the render job (export + render) - split_render = bool(rop.parm("RS_archive_enable").eval()) - instance.data["splitRender"] = split_render + export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "RS_archive_file", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py index 13478a9d2b..c39b1db103 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -45,12 +45,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): render_products = [] # TODO: add render elements if render element - # Store whether we are splitting the render job in an export + render - split_render = rop.parm("render_export_mode").eval() == "2" - instance.data["splitRender"] = split_render export_prefix = None export_products = [] - if split_render: + if instance.data["splitRender"]: export_prefix = evalParmNoFrame( rop, "render_export_filepath", pad_character="0" ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 8a666541cb..7b4762a25f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -23,7 +23,7 @@ class ExtractRender(publish.Extractor): rop_node = hou.node(instance.data.get("instance_node")) # Align split parameter value on rop node to the render target. - if creator_attribute.get("render_target") == "farm_split": + if instance.data["splitRender"]: if product_type == "arnold_rop": rop_node.setParms({"ar_ass_export_enable": 1}) elif product_type == "mantra_rop": From e04c9285f1a1dcc4eaa3560ad964b057d9dc3478 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 23 Apr 2024 22:32:04 +0200 Subject: [PATCH 483/727] add a TODO about running plugins over wrong isntances --- .../hosts/houdini/plugins/publish/extract_opengl.py | 6 ++++++ .../houdini/plugins/publish/validate_review_colorspace.py | 5 +++++ .../hosts/houdini/plugins/publish/validate_scene_review.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py index 69bbb22340..d3b4b094b2 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_opengl.py @@ -17,6 +17,12 @@ class ExtractOpenGL(publish.Extractor): def process(self, instance): ropnode = hou.node(instance.data.get("instance_node")) + + # This plugin is triggered when marking render as reviewable. + # Therefore, this plugin will run on over wrong instances. + # TODO: Don't run this plugin on wrong instances. + # This plugin should run only on review product type + # with instance node of opengl type. if ropnode.type().name() != "opengl": self.log.debug("Skipping OpenGl extraction. Rop node {} " "is not an OpenGl node.".format(ropnode.path())) 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 e02ce93f0d..691b54ac05 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 @@ -35,6 +35,11 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, rop_node = hou.node(instance.data["instance_node"]) + # This plugin is triggered when marking render as reviewable. + # Therefore, this plugin will run on over wrong instances. + # TODO: Don't run this plugin on wrong instances. + # This plugin should run only on review product type + # with instance node of opengl type. if rop_node.type().name() != "opengl": self.log.debug("Skipping Validation. Rop node {} " "is not an OpenGl node.".format(rop_node.path())) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py index 9b81f0f8ed..0b09306b0d 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_scene_review.py @@ -19,6 +19,12 @@ class ValidateSceneReview(pyblish.api.InstancePlugin): report = [] instance_node = hou.node(instance.data.get("instance_node")) + + # This plugin is triggered when marking render as reviewable. + # Therefore, this plugin will run on over wrong instances. + # TODO: Don't run this plugin on wrong instances. + # This plugin should run only on review product type + # with instance node of opengl type. if instance_node.type().name() != "opengl": self.log.debug("Skipping Validation. Rop node {} " "is not an OpenGl node.".format(instance_node.path())) From f6411be0c26623502efe1e59ba66c2b894c27d71 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Apr 2024 01:36:42 +0200 Subject: [PATCH 484/727] 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 e194652f65d122f769c028e3df5aca539f186018 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:59:16 +0200 Subject: [PATCH 485/727] add path to folder keys --- client/ayon_core/pipeline/template_data.py | 1 + .../ayon_core/plugins/publish/collect_anatomy_instance_data.py | 1 + 2 files changed, 2 insertions(+) diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py index 02bccb5f49..d5f06d6a59 100644 --- a/client/ayon_core/pipeline/template_data.py +++ b/client/ayon_core/pipeline/template_data.py @@ -102,6 +102,7 @@ def get_folder_template_data(folder_entity, project_name): "folder": { "name": folder_name, "type": folder_entity["folderType"], + "path": path, }, "asset": folder_name, "hierarchy": hierarchy, diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py index f0119ef42e..ad5a5d43fc 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py @@ -406,6 +406,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "parent": parent_name, "folder": { "name": folder_name, + "path": instance.data["folderPath"], # TODO get folder type from hierarchy # Using 'Shot' is current default behavior of editorial # (or 'newAssetPublishing') publishing. From 6b2b28d7d235c1da4d684e638c863305ed3b2d35 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Apr 2024 14:57:58 +0800 Subject: [PATCH 486/727] 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 487/727] 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 488/727] 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 489/727] 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 8110a601bbc0d114c1bcd22be8122cc591ef51a7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 25 Apr 2024 11:02:31 +0200 Subject: [PATCH 490/727] add CollectLocalRenderInstances setting --- 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 491/727] 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 492/727] :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 493/727] :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 494/727] 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 495/727] 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 496/727] 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 497/727] 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 498/727] 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 499/727] 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 500/727] 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 501/727] 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 502/727] 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 503/727] 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 504/727] 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 505/727] 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 506/727] 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 767bbf070fb1a389f217d9e8b37d3c07a5e85f5f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 29 Apr 2024 10:39:16 +0300 Subject: [PATCH 507/727] add CollectLocalRenderInstances setting --- 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 4e441c76ae..6c81eba439 100644 --- a/server_addon/houdini/package.py +++ b/server_addon/houdini/package.py @@ -1,3 +1,3 @@ name = "houdini" title = "Houdini" -version = "0.2.13" +version = "0.2.14" From 5018db2f084ad7995d4827d6b326859a4eafa132 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Apr 2024 17:54:40 +0800 Subject: [PATCH 508/727] 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 509/727] 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 510/727] 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 511/727] 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 512/727] 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 513/727] 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 514/727] 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 515/727] 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 516/727] 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 517/727] 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 518/727] 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 519/727] 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 520/727] 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 521/727] 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 522/727] 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 523/727] 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 524/727] 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 525/727] 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 526/727] 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 527/727] 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 528/727] 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 529/727] 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 cf9707db85f637b3549a3b84ec30aca2ddeea400 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 Apr 2024 11:25:04 +0200 Subject: [PATCH 530/727] Refactor OCIO config settings and profiles Added new OCIO config profile types and paths for built-in and custom configurations. Updated default values accordingly. --- server/settings/main.py | 138 +++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 43 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 28a69e182d..85c1779999 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -54,9 +54,66 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): return value -class CoreImageIOConfigModel(BaseSettingsModel): - filepath: list[str] = SettingsField( - default_factory=list, title="Config path" +_ocio_config_profile_types = [ + {"value": "buildin_path", "label": "Ayon built-in OCIO config"}, + {"value": "custom_path", "label": "Path to OCIO config"}, + {"value": "product", "label": "Published product"}, +] + + +def _ocio_build_in_paths(): + return [ + { + "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + "label": "ACES 1.2", + "description": "Aces 1.2 OCIO config file." + }, + { + "value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", + "label": "Nuke default", + }, + ] + + +class CoreImageIOConfigProfilesModel(BaseSettingsModel): + host_names: list[str] = SettingsField( + default_factory=list, + title="Host names" + ) + task_types: list[str] = SettingsField( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = SettingsField( + default_factory=list, + title="Task names" + ) + type: str = SettingsField( + title="Profile type", + enum_resolver=lambda: _ocio_config_profile_types, + conditionalEnum=True, + default="buildin_path", + section="---", + ) + + buildin_path: str = SettingsField( + "ACES 1.2", + title="Built-in OCIO config", + enum_resolver=_ocio_build_in_paths, + ) + custom_path: str = SettingsField( + "", + title="OCIO config path", + description="Path to OCIO config. Anatomy formatting is supported.", + ) + product: str = SettingsField( + "", + title="Product name", + description=( + "Published product name to get OCIO config from. " + "Partial match is supported." + ), ) @@ -65,9 +122,8 @@ class CoreImageIOBaseModel(BaseSettingsModel): False, title="Enable Color Management" ) - ocio_config: CoreImageIOConfigModel = SettingsField( - default_factory=CoreImageIOConfigModel, - title="OCIO config" + ocio_config_profiles: list[CoreImageIOConfigProfilesModel] = SettingsField( + default_factory=list, title="OCIO config profiles" ) file_rules: CoreImageIOFileRulesModel = SettingsField( default_factory=CoreImageIOFileRulesModel, @@ -186,12 +242,17 @@ class CoreSettings(BaseSettingsModel): DEFAULT_VALUES = { "imageio": { "activate_global_color_management": False, - "ocio_config": { - "filepath": [ - "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", - "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio" - ] - }, + "ocio_config_profiles": [ + { + "host_names": [], + "task_types": [], + "task_names": [], + "type": "buildin_path", + "buildin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + "custom_path": "", + "product": "", + } + ], "file_rules": { "activate_global_file_rules": False, "rules": [ @@ -199,42 +260,33 @@ DEFAULT_VALUES = { "name": "example", "pattern": ".*(beauty).*", "colorspace": "ACES - ACEScg", - "ext": "exr" + "ext": "exr", } - ] - } + ], + }, }, "studio_name": "", "studio_code": "", - "environments": "{\n\"STUDIO_SW\": {\n \"darwin\": \"/mnt/REPO_SW\",\n \"linux\": \"/mnt/REPO_SW\",\n \"windows\": \"P:/REPO_SW\"\n }\n}", + "environments": '{\n"STUDIO_SW": {\n "darwin": "/mnt/REPO_SW",\n "linux": "/mnt/REPO_SW",\n "windows": "P:/REPO_SW"\n }\n}', "tools": DEFAULT_TOOLS_VALUES, - "version_start_category": { - "profiles": [] - }, + "version_start_category": {"profiles": []}, "publish": DEFAULT_PUBLISH_VALUES, - "project_folder_structure": json.dumps({ - "__project_root__": { - "prod": {}, - "resources": { - "footage": { - "plates": {}, - "offline": {} + "project_folder_structure": json.dumps( + { + "__project_root__": { + "prod": {}, + "resources": { + "footage": {"plates": {}, "offline": {}}, + "audio": {}, + "art_dept": {}, }, - "audio": {}, - "art_dept": {} - }, - "editorial": {}, - "assets": { - "characters": {}, - "locations": {} - }, - "shots": {} - } - }, indent=4), - "project_plugins": { - "windows": [], - "darwin": [], - "linux": [] - }, - "project_environments": "{}" + "editorial": {}, + "assets": {"characters": {}, "locations": {}}, + "shots": {}, + } + }, + indent=4, + ), + "project_plugins": {"windows": [], "darwin": [], "linux": []}, + "project_environments": "{}", } 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 531/727] :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 532/727] :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 533/727] 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 534/727] :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 535/727] 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 7b86da34ae86b0f866f610b0eb048511ddf1c743 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:59:04 +0200 Subject: [PATCH 536/727] fix buildin to builtin --- server/settings/main.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 85c1779999..8bbe13da30 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -55,13 +55,13 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): _ocio_config_profile_types = [ - {"value": "buildin_path", "label": "Ayon built-in OCIO config"}, + {"value": "builtin_path", "label": "Ayon built-in OCIO config"}, {"value": "custom_path", "label": "Path to OCIO config"}, {"value": "product", "label": "Published product"}, ] -def _ocio_build_in_paths(): +def _ocio_built_in_paths(): return [ { "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", @@ -93,14 +93,14 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel): title="Profile type", enum_resolver=lambda: _ocio_config_profile_types, conditionalEnum=True, - default="buildin_path", + default="builtin_path", section="---", ) - buildin_path: str = SettingsField( + builtin_path: str = SettingsField( "ACES 1.2", title="Built-in OCIO config", - enum_resolver=_ocio_build_in_paths, + enum_resolver=_ocio_built_in_paths, ) custom_path: str = SettingsField( "", @@ -247,8 +247,8 @@ DEFAULT_VALUES = { "host_names": [], "task_types": [], "task_names": [], - "type": "buildin_path", - "buildin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + "type": "builtin_path", + "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "custom_path": "", "product": "", } From 8c525987e58991d770208f93bedcc6ed1bcd3bde Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:59:16 +0200 Subject: [PATCH 537/727] change Ayon to AYON --- server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/main.py b/server/settings/main.py index 8bbe13da30..d6a38a90e6 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -55,7 +55,7 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): _ocio_config_profile_types = [ - {"value": "builtin_path", "label": "Ayon built-in OCIO config"}, + {"value": "builtin_path", "label": "AYON built-in OCIO config"}, {"value": "custom_path", "label": "Path to OCIO config"}, {"value": "product", "label": "Published product"}, ] From c0d9edad758559c15790c4cfd09f7c357ca84362 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:59:33 +0200 Subject: [PATCH 538/727] _ocio_config_profile_types is function --- server/settings/main.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index d6a38a90e6..230414ffe7 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -54,11 +54,12 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): return value -_ocio_config_profile_types = [ - {"value": "builtin_path", "label": "AYON built-in OCIO config"}, - {"value": "custom_path", "label": "Path to OCIO config"}, - {"value": "product", "label": "Published product"}, -] +def _ocio_config_profile_types(): + return [ + {"value": "builtin_path", "label": "AYON built-in OCIO config"}, + {"value": "custom_path", "label": "Path to OCIO config"}, + {"value": "product", "label": "Published product"}, + ] def _ocio_built_in_paths(): @@ -91,7 +92,7 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel): ) type: str = SettingsField( title="Profile type", - enum_resolver=lambda: _ocio_config_profile_types, + enum_resolver=_ocio_config_profile_types, conditionalEnum=True, default="builtin_path", section="---", From feb73842c377c401f2acdd1dc18c6852b3b23de7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:07:17 +0200 Subject: [PATCH 539/727] rename 'product' attribute to 'product_name' --- server/settings/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 230414ffe7..c9c86bdd0d 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -97,7 +97,6 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel): default="builtin_path", section="---", ) - builtin_path: str = SettingsField( "ACES 1.2", title="Built-in OCIO config", @@ -108,7 +107,7 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel): title="OCIO config path", description="Path to OCIO config. Anatomy formatting is supported.", ) - product: str = SettingsField( + product_name: str = SettingsField( "", title="Product name", description=( @@ -251,7 +250,7 @@ DEFAULT_VALUES = { "type": "builtin_path", "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "custom_path": "", - "product": "", + "product_name": "", } ], "file_rules": { 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 540/727] :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 c189d502fd93b9ea29fc115a7573534cae42541c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Apr 2024 15:37:08 +0100 Subject: [PATCH 541/727] Fix is_visible --- client/ayon_core/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 1defa3debd..525c156fe1 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -1299,7 +1299,7 @@ def is_visible(node, override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node)) override_visibility = cmds.getAttr('{}.overrideVisibility'.format( node)) - if override_enabled and override_visibility: + if override_enabled and not override_visibility: return False if parentHidden: 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 542/727] :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 543/727] 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 544/727] 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 545/727] 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 546/727] 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 9be28b8051124fd8d699b665299c6eeb98cbc63b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:00:16 +0200 Subject: [PATCH 547/727] Fix `publish_attributes` access --- .../plugins/publish/validate_alembic_options_defaults.py | 8 +------- 1 file changed, 1 insertion(+), 7 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 5197100406..a9de510e67 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 @@ -29,13 +29,7 @@ class ValidateAlembicDefaultsPointcache( @classmethod def _get_publish_attributes(cls, instance): - attributes = instance.data["publish_attributes"][ - cls.plugin_name( - instance.data["publish_attributes"] - ) - ] - - return attributes + return instance.data["publish_attributes"][cls.plugin_name] def process(self, instance): if not self.is_active(instance.data): From b05cac07b8b9cb596aa090c77b0425a216cadd65 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:01:52 +0200 Subject: [PATCH 548/727] Fix repair logic --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 4 +--- 1 file changed, 1 insertion(+), 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 a9de510e67..940e4f3869 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 @@ -69,13 +69,11 @@ class ValidateAlembicDefaultsPointcache( ) # Set the settings values on the create context then save to workfile. - publish_attributes = instance.data["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"] for key in attributes: - create_publish_attributes[plugin_name][key] = settings[key] + create_publish_attributes[cls.plugin_name][key] = settings[key] create_context.save_changes() From 8567e54bb45547bd6b6a31d05123d5c80af626fe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:04:33 +0200 Subject: [PATCH 549/727] Fix label --- .../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 940e4f3869..1045ef3c70 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 @@ -85,6 +85,6 @@ class ValidateAlembicDefaultsAnimation( The defaults are defined in the project settings. """ - label = "Validate Alembic Options Defaults" + label = "Validate Alembic Options Defaults" families = ["animation"] plugin_name = "ExtractAnimation" From 665a4e226a6cc42039afe938b57ea33d269592d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:12:17 +0200 Subject: [PATCH 550/727] Fix `writeCreases` support --- .../hosts/maya/plugins/publish/extract_pointcache.py | 8 ++++++++ server_addon/maya/server/settings/publishers.py | 5 +++++ 2 files changed, 13 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 d7f9594374..d34634bff8 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -63,6 +63,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): wholeFrameGeo = False worldSpace = True writeColorSets = False + writeCreases = False writeFaceSets = False writeNormals = True writeUVSets = False @@ -354,6 +355,13 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): default=cls.writeColorSets, tooltip="Write vertex colors with the geometry." ), + "writeCreases": BoolDef( + "writeCreases", + label="Write Creases", + default=cls.writeCreases, + tooltip="Write the geometry's edge and vertex crease " + "information." + ), "writeFaceSets": BoolDef( "writeFaceSets", label="Write Face Sets", diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 8dcffbb59a..ee74eaa553 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -406,6 +406,10 @@ class ExtractAlembicModel(BaseSettingsModel): title="Write Color Sets", description="Write vertex colors with the geometry." ) + writeCreases: bool = SettingsField( + title="Write Creases", + description="Write the geometry's edge and vertex crease information." + ) writeFaceSets: bool = SettingsField( title="Write Face Sets", description="Write face sets with the geometry." @@ -1643,6 +1647,7 @@ DEFAULT_PUBLISH_SETTINGS = { "wholeFrameGeo": False, "worldSpace": True, "writeColorSets": False, + "writeCreases": False, "writeFaceSets": False, "writeNormals": True, "writeUVSets": False, From 141767ef717cf8b15baec850de71f0dc2f013871 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:13:49 +0200 Subject: [PATCH 551/727] Cosmetics --- .../maya/plugins/publish/validate_alembic_options_defaults.py | 1 - 1 file changed, 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 1045ef3c70..476f837135 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 @@ -36,7 +36,6 @@ class ValidateAlembicDefaultsPointcache( return settings = self._get_settings(instance.context) - attributes = self._get_publish_attributes(instance) msg = ( From 0352c17a09108cfcf768aac58da2fef5c67cf12a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:18:20 +0200 Subject: [PATCH 552/727] Add support for extracting the user attributes --- client/ayon_core/hosts/maya/api/alembic.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index bf887df4c7..954c0a0888 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -63,6 +63,8 @@ def extract_alembic( startFrame=None, step=1.0, stripNamespaces=True, + userAttr=None, + userAttrPrefix=None, uvWrite=True, verbose=False, wholeFrameGeo=False, @@ -137,6 +139,12 @@ def extract_alembic( object with the namespace taco:foo:bar appears as bar in the Alembic file. + userAttr (list of str, optional): A specific user defined attribute to + write out. Defaults to []. + + userAttrPrefix (list of str, optional): Prefix filter for determining + which user defined attributes to write out. Defaults to []. + 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. @@ -183,6 +191,8 @@ def extract_alembic( # Ensure list arguments are valid. attr = attr or [] attrPrefix = attrPrefix or [] + userAttr = userAttr or [] + userAttrPrefix = userAttrPrefix or [] root = root or [] # Pass the start and end frame on as `frameRange` so that it @@ -226,6 +236,8 @@ def extract_alembic( "step": step, "attr": attr, "attrPrefix": attrPrefix, + "userAttr": userAttr, + "userAttrPrefix": userAttrPrefix, "stripNamespaces": stripNamespaces, "verbose": verbose, "preRollStartFrame": preRollStartFrame From 367eba0f493d196525a308bac3085d87772553d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:25:00 +0200 Subject: [PATCH 553/727] Remove `autoSubd` in favor of legacy `writeCreases` usage across the codebase and existing instances --- .../plugins/publish/extract_pointcache.py | 20 +------------------ .../maya/server/settings/publishers.py | 13 ------------ 2 files changed, 1 insertion(+), 32 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 d34634bff8..cff32ebb67 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -40,7 +40,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): # From settings attr = [] attrPrefix = [] - autoSubd = False bake_attributes = [] bake_attribute_prefixes = [] dataFormat = "ogawa" @@ -174,9 +173,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "writeVisibility": attribute_values.get( "writeVisibility", self.writeVisibility ), - "autoSubd": attribute_values.get( - "autoSubd", self.autoSubd - ), "uvsOnly": attribute_values.get( "uvsOnly", self.uvsOnly ), @@ -250,7 +246,7 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): with maintained_selection(): cmds.select(instance.data["proxy"]) extract_alembic(**kwargs) - + raise RuntimeError("FAIL") representation = { "name": "proxy", "ext": "abc", @@ -269,20 +265,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): return [] override_defs = OrderedDict({ - "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." - ) - ), "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 ee74eaa553..3a82d649e1 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -46,7 +46,6 @@ def extract_alembic_overrides_enum(): return [ {"label": "Custom Attributes", "value": "attr"}, {"label": "Custom Attributes Prefix", "value": "attrPrefix"}, - {"label": "Auto Subd", "value": "autoSubd"}, {"label": "Data Format", "value": "dataFormat"}, {"label": "Euler Filter", "value": "eulerFilter"}, {"label": "Mel Per Frame Callback", "value": "melPerFrameCallback"}, @@ -344,17 +343,6 @@ class ExtractAlembicModel(BaseSettingsModel): families: list[str] = SettingsField( default_factory=list, title="Families") - 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." - ) - ) eulerFilter: bool = SettingsField( title="Euler Filter", description="Apply Euler filter while sampling rotations." @@ -1615,7 +1603,6 @@ DEFAULT_PUBLISH_SETTINGS = { ], "attr": "", "attrPrefix": "", - "autoSubd": False, "bake_attributes": [], "bake_attribute_prefixes": [], "dataFormat": "ogawa", From 26eb91781365c44036b02e8644f3fb2774e78c5a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:26:53 +0200 Subject: [PATCH 554/727] Add `uvsOnly` argument --- client/ayon_core/hosts/maya/api/alembic.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index 954c0a0888..a815dc7465 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -65,6 +65,7 @@ def extract_alembic( stripNamespaces=True, userAttr=None, userAttrPrefix=None, + uvsOnly=False, uvWrite=True, verbose=False, wholeFrameGeo=False, @@ -145,6 +146,9 @@ def extract_alembic( userAttrPrefix (list of str, optional): Prefix filter for determining which user defined attributes to write out. Defaults to []. + uvsOnly (bool): When on, only uv data for PolyMesh and SubD shapes + will be written to 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. @@ -225,6 +229,7 @@ def extract_alembic( "preRoll": preRoll, "renderableOnly": renderableOnly, "uvWrite": uvWrite, + "uvsOnly": uvsOnly, "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, "wholeFrameGeo": wholeFrameGeo, From e43a4ba481e7f307ec114cae2b735dfbdbfae704 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:28:27 +0200 Subject: [PATCH 555/727] Fix usage of `root` argument --- client/ayon_core/hosts/maya/api/alembic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index a815dc7465..3722774bba 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -227,6 +227,7 @@ def extract_alembic( "eulerFilter": eulerFilter, "noNormals": noNormals, "preRoll": preRoll, + "root": root, "renderableOnly": renderableOnly, "uvWrite": uvWrite, "uvsOnly": uvsOnly, From 828f7bbce2038a1a38a6fbde66d71fc8143083ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:37:42 +0200 Subject: [PATCH 556/727] Fix `writeNormals` usage --- .../hosts/maya/plugins/publish/extract_pointcache.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 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 cff32ebb67..3c0350ec65 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -176,9 +176,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): "uvsOnly": attribute_values.get( "uvsOnly", self.uvsOnly ), - "writeNormals": attribute_values.get( - "writeNormals", self.writeNormals - ), "melPerFrameCallback": attribute_values.get( "melPerFrameCallback", self.melPerFrameCallback ), @@ -190,7 +187,12 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): ), "pythonPostJobCallback": attribute_values.get( "pythonPostJobCallback", self.pythonPostJobCallback - ) + ), + # Note that this converts `writeNormals` to `noNormals` for the + # `AbcExport` equivalent in `extract_alembic` + "noNormals": not attribute_values.get( + "writeNormals", self.writeNormals + ), } if instance.data.get("visibleOnly", False): From 5193d2664fed856399690869572f6a33dbb9f94a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:38:01 +0200 Subject: [PATCH 557/727] Fix passing of callbacks --- client/ayon_core/hosts/maya/api/alembic.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index 3722774bba..e9bf6d71ca 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -54,9 +54,13 @@ def extract_alembic( endFrame=None, eulerFilter=True, frameRange="", + melPerFrameCallback=None, + melPostJobCallback=None, noNormals=False, preRoll=False, preRollStartFrame=0, + pythonPerFrameCallback=None, + pythonPostJobCallback=None, renderableOnly=False, root=None, selection=True, @@ -105,6 +109,11 @@ def extract_alembic( string formatted as: "startFrame endFrame". This argument overrides `startFrame` and `endFrame` arguments. + melPerFrameCallback (Optional[str]): MEL callback run per frame. + + melPostJobCallback (Optional[str]): MEL callback after last frame is + written. + noNormals (bool): When on, normal data from the original polygon objects is not included in the exported Alembic cache file. @@ -116,6 +125,11 @@ def extract_alembic( dependent translations and can be used to evaluate run-up that isn't actually translated. Defaults to 0. + pythonPerFrameCallback (Optional[str]): Python callback run per frame. + + pythonPostJobCallback (Optional[str]): Python callback after last frame + is written. + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, such as hidden objects, are not included in the Alembic file. Defaults to False. @@ -282,6 +296,17 @@ def extract_alembic( if maya_version >= 2018: options['autoSubd'] = options.pop('writeCreases', False) + # Only add callbacks if they are set so that we're not passing `None` + callbacks = { + "melPerFrameCallback": melPerFrameCallback, + "melPostJobCallback": melPostJobCallback, + "pythonPerFrameCallback": pythonPerFrameCallback, + "pythonPostJobCallback": pythonPostJobCallback, + } + for key, callback in callbacks.items(): + if callback: + options[key] = str(callback) + # Format the job string from options job_args = list() for key, value in options.items(): From ba50a64a3842930ef77cf985093f139aa4cc1961 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:39:45 +0200 Subject: [PATCH 558/727] Remove debugging error --- .../ayon_core/hosts/maya/plugins/publish/extract_pointcache.py | 1 - 1 file changed, 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 3c0350ec65..05deaa0834 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -248,7 +248,6 @@ class ExtractAlembic(publish.Extractor, AYONPyblishPluginMixin): with maintained_selection(): cmds.select(instance.data["proxy"]) extract_alembic(**kwargs) - raise RuntimeError("FAIL") representation = { "name": "proxy", "ext": "abc", From 02ccceaf24caaa8ca52c447a21065001029fc57f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 16:48:36 +0200 Subject: [PATCH 559/727] Bump Maya addon 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 5c6ce923aa..fe3e3039f5 100644 --- a/server_addon/maya/package.py +++ b/server_addon/maya/package.py @@ -1,3 +1,3 @@ name = "maya" title = "Maya" -version = "0.1.17" +version = "0.1.18" From 2bf6ed71156d912aa2ce4329e6091761fc47fd5b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 17:18:23 +0200 Subject: [PATCH 560/727] Do not modify `roots` in-place so that `roots` remains the roots and doesn't contain the descendendants + ignore intermediate objects since they would not be exported anyway --- .../hosts/maya/plugins/publish/extract_pointcache.py | 7 +++---- 1 file changed, 3 insertions(+), 4 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 05deaa0834..cc930e49cc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_pointcache.py @@ -6,6 +6,7 @@ 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 ( + get_all_children, suspended_refresh, maintained_selection, iter_visible_nodes_in_range @@ -518,9 +519,7 @@ class ExtractAnimation(ExtractAlembic): roots = cmds.sets(out_set, query=True) or [] # Include all descendants - nodes = roots - nodes += cmds.listRelatives( - roots, allDescendents=True, fullPath=True - ) or [] + nodes = roots.copy() + nodes.extend(get_all_children(roots, ignore_intermediate_objects=True)) return nodes, roots From 1513660b7e842f31bc33bacf464e44ea0edc62b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 22:51:36 +0200 Subject: [PATCH 561/727] Pass `preRollStartFrame` as separate argument --- client/ayon_core/hosts/maya/api/alembic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index e9bf6d71ca..6bd00e1cb1 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -22,7 +22,6 @@ ALEMBIC_ARGS = { "melPostJobCallback": str, "noNormals": bool, "preRoll": bool, - "preRollStartFrame": int, "pythonPerFrameCallback": str, "pythonPostJobCallback": str, "renderableOnly": bool, @@ -259,8 +258,7 @@ def extract_alembic( "userAttr": userAttr, "userAttrPrefix": userAttrPrefix, "stripNamespaces": stripNamespaces, - "verbose": verbose, - "preRollStartFrame": preRollStartFrame + "verbose": verbose } # Validate options @@ -340,7 +338,11 @@ def extract_alembic( # exports are made. (PLN-31) # TODO: Make sure this actually fixes the issues with evaluation("off"): - cmds.AbcExport(j=job_str, verbose=verbose) + cmds.AbcExport( + j=job_str, + verbose=verbose, + preRollStartFrame=preRollStartFrame + ) if verbose: log.debug("Extracted Alembic to: %s", file) From 0e23615cbd037af3638972bf9694ebaa674989bd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 1 May 2024 22:54:45 +0200 Subject: [PATCH 562/727] Enable uvWrite in settings by default --- 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 3a82d649e1..c3983e0067 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1626,7 +1626,7 @@ DEFAULT_PUBLISH_SETTINGS = { "renderableOnly": False, "stripNamespaces": True, "uvsOnly": False, - "uvWrite": False, + "uvWrite": True, "userAttr": "", "userAttrPrefix": "", "verbose": False, From 3812f229240f7dc7eb4c71979973a355c831a0fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 May 2024 10:33:32 +0200 Subject: [PATCH 563/727] implemented 'get_imageio_config_preset' with new profiles handling --- client/ayon_core/pipeline/colorspace.py | 317 ++++++++++++++++++++---- 1 file changed, 272 insertions(+), 45 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index efa3bbf968..8dcbaeb877 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -8,14 +8,19 @@ import tempfile import warnings from copy import deepcopy +import ayon_api + from ayon_core import AYON_CORE_ROOT from ayon_core.settings import get_project_settings from ayon_core.lib import ( + filter_profiles, StringTemplate, run_ayon_launcher_process, - Logger + Logger, ) from ayon_core.pipeline import Anatomy +from ayon_core.pipeline.template_data import get_template_data +from ayon_core.pipeline.load import get_representation_path_with_anatomy from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS @@ -758,6 +763,9 @@ def get_imageio_config( Config path is formatted in `path` key and original settings input is saved into `template` key. + Deprecated: + Deprecated since '0.3.1' . Use `get_imageio_config_preset` instead. + Args: project_name (str): project name host_name (str): host name @@ -768,88 +776,307 @@ def get_imageio_config( Returns: dict: config path data or empty dict - """ - project_settings = project_settings or get_project_settings(project_name) - anatomy = anatomy or Anatomy(project_name) + """ if not anatomy_data: from ayon_core.pipeline.context_tools import ( get_current_context_template_data) anatomy_data = get_current_context_template_data() - formatting_data = deepcopy(anatomy_data) + task_name = anatomy_data["task"]["name"] + folder_path = anatomy_data["folder"]["path"] + return get_imageio_config_preset( + project_name, + folder_path, + task_name, + host_name, + anatomy=anatomy, + project_settings=project_settings, + template_data=anatomy_data, + env=env, + ) - # Add project roots to anatomy data - formatting_data["root"] = anatomy.roots - formatting_data["platform"] = platform.system().lower() + +def _get_global_config_data( + project_name, + host_name, + anatomy, + template_data, + imageio_global, + folder_id, + log, +): + """Get global config data. + + Global config from core settings is using profiles that are based on + host name, task name and task type. The filtered profile can define 3 + types of config sources: + 1. AYON ocio addon configs. + 2. Custom path to ocio config. + 3. Path to 'ocioconfig' representation on product. Name of product can be + defined in settings. Product name can be regex but exact match is + always preferred. + + None is returned when no profile is found, when path + + Args: + project_name (str): Project name. + host_name (str): Host name. + anatomy (Anatomy): Project anatomy object. + template_data (dict[str, Any]): Template data. + imageio_global (dict[str, Any]): Core imagio settings. + folder_id (Union[dict[str, Any], None]): Folder id. + log (logging.Logger): Logger object. + + Returns: + Union[dict[str, str], None]: Config data with path and template + or None. + + """ + task_name = task_type = None + task_data = template_data.get("task") + if task_data: + task_name = task_data["name"] + task_type = task_data["type"] + + filter_values = { + "task_names": task_name, + "task_types": task_type, + "host_names": host_name, + } + profile = filter_profiles( + imageio_global["ocio_config_profiles"], filter_values + ) + if profile is None: + log.info(f"No config profile matched filters {str(filter_values)}") + return None + + profile_type = profile["type"] + if profile_type in ("builtin_path", "custom_path"): + template = profile[profile_type] + result = StringTemplate.format_strict_template( + template, template_data + ) + normalized_path = str(result.normalized()) + if not os.path.exists(normalized_path): + log.warning(f"Path was not found '{normalized_path}'.") + return None + + return { + "path": normalized_path, + "template": template + } + + # TODO decide if this is the right name for representation + repre_name = "ocioconfig" + + folder_info = template_data.get("folder") + if not folder_info: + log.warning("Folder info is missing.") + return None + folder_path = folder_info["path"] + + product_name = profile["product_name"] + if folder_id is None: + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"id"} + ) + if not folder_entity: + log.warning(f"Folder entity '{folder_path}' was not found..") + return None + folder_id = folder_entity["id"] + + product_entities_by_name = { + product_entity["name"]: product_entity + for product_entity in ayon_api.get_products( + project_name, + folder_ids={folder_id}, + product_name_regex=product_name, + fields={"id", "name"} + ) + } + if not product_entities_by_name: + log.debug( + f"No product entities were found for folder '{folder_path}' with" + f" product name filter '{product_name}'." + ) + return None + + # Try to use exact match first, otherwise use first available product + product_entity = product_entities_by_name.get(product_name) + if product_entity is None: + product_entity = next(iter(product_entities_by_name.values())) + + product_name = product_entity["name"] + # Find last product version + version_entity = ayon_api.get_last_version_by_product_id( + project_name, + product_id=product_entity["id"], + fields={"id"} + ) + if not version_entity: + log.info( + f"Product '{product_name}' does not have available any versions." + ) + return None + + # Find 'ocioconfig' representation entity + repre_entity = ayon_api.get_representation_by_name( + project_name, + representation_name=repre_name, + version_id=version_entity["id"], + ) + if not repre_entity: + log.debug( + f"Representation '{repre_name}'" + f" not found on product '{product_name}'." + ) + return None + + path = get_representation_path_with_anatomy(repre_entity, anatomy) + template = repre_entity["attrib"]["template"] + return { + "path": path, + "template": template, + } + + +def get_imageio_config_preset( + project_name, + folder_path, + task_name, + host_name, + anatomy=None, + project_settings=None, + template_data=None, + env=None, + folder_id=None, +): + """Returns config data from settings + + Output contains 'path' key and 'template' key holds its template. + + Template data can be prepared with 'get_template_data'. + + Args: + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + host_name (str): Host name. + anatomy (Optional[Anatomy]): Project anatomy object. + project_settings (Optional[dict]): Project settings. + template_data (Optional[dict]): Template data used for + template formatting. + env (Optional[dict]): Environment variables. Environments are used + for template formatting too. Values from 'os.environ' are used + when not provided. + folder_id (Optional[str]): Folder id. Is used only when config path + is received from published representation. Is autofilled when + not provided. + + Returns: + dict: config path data or empty dict + + """ + if not project_settings: + project_settings = get_project_settings(project_name) # Get colorspace settings imageio_global, imageio_host = _get_imageio_settings( - project_settings, host_name) + project_settings, host_name + ) + # Global color management must be enabled to be able to use host settings + if not imageio_global["activate_global_color_management"]: + log.info("Colorspace management is disabled globally.") + return {} # Host 'ocio_config' is optional host_ocio_config = imageio_host.get("ocio_config") or {} - - # Global color management must be enabled to be able to use host settings - activate_color_management = imageio_global.get( - "activate_global_color_management") - # TODO: remove this in future - backward compatibility - # For already saved overrides from previous version look for 'enabled' - # on host settings. - if activate_color_management is None: - activate_color_management = host_ocio_config.get("enabled", False) - - if not activate_color_management: - # if global settings are disabled return empty dict because - # it is expected that no colorspace management is needed - log.info("Colorspace management is disabled globally.") - return {} + # TODO remove + # - backward compatibility when host settings had only 'enabled' flag + # the flag was split into 'activate_global_color_management' + # and 'override_global_config' + host_ocio_config_enabled = host_ocio_config.get("enabled", False) # Check if host settings group is having 'activate_host_color_management' # - if it does not have activation key then default it to True so it uses # global settings - # This is for backward compatibility. - # TODO: in future rewrite this to be more explicit activate_host_color_management = imageio_host.get( - "activate_host_color_management") - - # TODO: remove this in future - backward compatibility + "activate_host_color_management" + ) if activate_host_color_management is None: - activate_host_color_management = host_ocio_config.get("enabled", False) + activate_host_color_management = host_ocio_config_enabled if not activate_host_color_management: # if host settings are disabled return False because # it is expected that no colorspace management is needed log.info( - "Colorspace management for host '{}' is disabled.".format( - host_name) + f"Colorspace management for host '{host_name}' is disabled." ) return {} - # get config path from either global or host settings - # depending on override flag + project_entity = None + if anatomy is None: + project_entity = ayon_api.get_project(project_name) + anatomy = Anatomy(project_name, project_entity) + + if env is None: + env = dict(os.environ.items()) + + if template_data: + template_data = deepcopy(template_data) + else: + if not project_entity: + project_entity = ayon_api.get_project(project_name) + + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path + ) + folder_id = folder_entity["id"] + task_entity = ayon_api.get_task_by_name( + project_name, folder_id, task_name + ) + template_data = get_template_data( + project_entity, + folder_entity, + task_entity, + host_name, + project_settings, + ) + + # Add project roots to anatomy data + template_data["root"] = anatomy.roots + template_data["platform"] = platform.system().lower() + + # Add environment variables to template data + template_data.update(env) + + # Get config path from core or host settings + # - based on override flag in host settings # TODO: in future rewrite this to be more explicit override_global_config = host_ocio_config.get("override_global_config") if override_global_config is None: - # for already saved overrides from previous version - # TODO: remove this in future - backward compatibility - override_global_config = host_ocio_config.get("enabled") + override_global_config = host_ocio_config_enabled - if override_global_config: - config_data = _get_config_data( - host_ocio_config["filepath"], formatting_data, env + if not override_global_config: + config_data = _get_global_config_data( + project_name, + host_name, + anatomy, + template_data, + imageio_global, + folder_id, + log, ) else: - # get config path from global - config_global = imageio_global["ocio_config"] config_data = _get_config_data( - config_global["filepath"], formatting_data, env + host_ocio_config["filepath"], template_data, env ) if not config_data: raise FileExistsError( - "No OCIO config found in settings. It is " - "either missing or there is typo in path inputs" + "No OCIO config found in settings. It is" + " either missing or there is typo in path inputs" ) return config_data From 71f2c074c55938672025f6ed7af450a32d3584b8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 May 2024 10:58:51 +0200 Subject: [PATCH 564/727] implemented conversion of settings overrides --- server/__init__.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server/__init__.py b/server/__init__.py index 152cc77218..f6f89f2049 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -1,3 +1,5 @@ +from typing import Any + from ayon_server.addons import BaseServerAddon from .settings import CoreSettings, DEFAULT_VALUES @@ -9,3 +11,46 @@ class CoreAddon(BaseServerAddon): async def get_default_settings(self): settings_model_cls = self.get_settings_model() return settings_model_cls(**DEFAULT_VALUES) + + async def convert_settings_overrides( + self, + source_version: str, + overrides: dict[str, Any], + ) -> dict[str, Any]: + self._convert_imagio_configs_0_3_1(overrides) + # Use super conversion + return await super().convert_settings_overrides( + source_version, overrides + ) + + def _convert_imagio_configs_0_3_1(self, overrides): + """Imageio config settings did change to profiles since 0.3.1. .""" + imageio_overrides = overrides.get("imageio") or {} + if "ocio_config" not in imageio_overrides: + return + + ocio_config = imageio_overrides.pop("ocio_config") + filepath = ocio_config["filepath"] + if not filepath: + return + first_filepath = filepath[0] + ocio_config_profiles = imageio_overrides.setdefault( + "ocio_config_profiles", [] + ) + base_value = { + "type": "builtin_path", + "product": "", + "host_names": [], + "task_names": [], + "task_types": [], + "custom_path": "", + "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio" + } + if first_filepath not in ( + "{BUILTIN_OCIO_ROOT}/aces_1.2/config.oci", + "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", + ): + base_value["type"] = "custom_path" + base_value["custom_path"] = first_filepath + + ocio_config_profiles.append(base_value) 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 565/727] :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 566/727] :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 567/727] 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 568/727] 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 569/727] 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 570/727] 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 399cb47b05499412a1fad4d3030d69f24ada879c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 3 May 2024 11:43:38 +0200 Subject: [PATCH 571/727] :bug: fix undefined task name --- client/ayon_core/pipeline/create/context.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index b8618738fb..e66e15b8b1 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1987,12 +1987,16 @@ class CreateContext: "Folder '{}' was not found".format(folder_path) ) - task_name = None if task_entity is None: task_name = self.get_current_task_name() - task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name - ) + if task_name: + task_entity = ayon_api.get_task_by_name( + project_name, folder_entity["id"], task_name + ) + + task_name = None + if task_entity: + task_name = task_entity["name"] if pre_create_data is None: pre_create_data = {} @@ -2022,6 +2026,7 @@ class CreateContext: "productType": creator.product_type, "variant": variant } + print("Create instance data", instance_data) return creator.create( product_name, instance_data, From def2a2c10a574cc96b1dc16ae1601af04c5cbdc7 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 3 May 2024 11:12:23 +0100 Subject: [PATCH 572/727] Working version --- .../create/create_arnold_scene_source.py | 20 ++- .../maya/plugins/load/load_arnold_standin.py | 51 +++---- .../publish/collect_arnold_scene_source.py | 39 +++--- .../publish/extract_arnold_scene_source.py | 126 ++++++++++++------ .../publish/validate_arnold_scene_source.py | 74 +++++----- .../validate_arnold_scene_source_cbid.py | 14 +- client/ayon_core/plugins/publish/integrate.py | 3 +- 7 files changed, 193 insertions(+), 134 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/create/create_arnold_scene_source.py index dc0ffb02c1..e321c13ca0 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_arnold_scene_source.py @@ -1,3 +1,5 @@ +from maya import cmds + from ayon_core.hosts.maya.api import ( lib, plugin @@ -87,16 +89,24 @@ class CreateArnoldSceneSource(plugin.MayaCreator): return defs + +class CreateArnoldSceneSourceProxy(CreateArnoldSceneSource): + """Arnold Scene Source Proxy + + This product type facilitates working with proxy geometry in the viewport. + """ + + identifier = "io.openpype.creators.maya.assproxy" + label = "Arnold Scene Source Proxy" + product_type = "assProxy" + icon = "cube" + def create(self, product_name, instance_data, pre_create_data): - - from maya import cmds - instance = super(CreateArnoldSceneSource, self).create( product_name, instance_data, pre_create_data ) instance_node = instance.get("instance_node") - content = cmds.sets(name=instance_node + "_content_SET", empty=True) proxy = cmds.sets(name=instance_node + "_proxy_SET", empty=True) - cmds.sets([content, proxy], forceElement=instance_node) + cmds.sets([proxy], forceElement=instance_node) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py index 4b7d2f42ab..ae3b68965a 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py @@ -12,6 +12,7 @@ from ayon_core.hosts.maya.api.lib import ( unique_namespace, get_attribute_input, maintained_selection, + get_fps_for_current_context ) from ayon_core.hosts.maya.api.pipeline import containerise from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type @@ -29,7 +30,13 @@ class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" product_types = { - "ass", "animation", "model", "proxyAbc", "pointcache", "usd" + "ass", + "assProxy", + "animation", + "model", + "proxyAbc", + "pointcache", + "usd" } representations = {"ass", "abc", "usda", "usdc", "usd"} @@ -95,8 +102,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(repre_path))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version_attributes.get("fps")) or 25 - cmds.setAttr(standin_shape + ".abcFPS", fps) + fps = ( + version_attributes.get("fps") or get_fps_for_current_context() + ) + cmds.setAttr(standin_shape + ".abcFPS", float(fps)) nodes = [root, standin, standin_shape] if operator is not None: @@ -128,6 +137,18 @@ class ArnoldStandinLoader(load.LoaderPlugin): proxy_path = "/".join([os.path.dirname(path), proxy_basename]) return proxy_basename, proxy_path + def _update_operators(self, string_replace_operator, proxy_basename, path): + cmds.setAttr( + string_replace_operator + ".match", + proxy_basename.split(".")[0], + type="string" + ) + cmds.setAttr( + string_replace_operator + ".replace", + os.path.basename(path).split(".")[0], + type="string" + ) + def _setup_proxy(self, shape, path, namespace): proxy_basename, proxy_path = self._get_proxy_path(path) @@ -150,16 +171,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): "*.(@node=='{}')".format(node_type), type="string" ) - cmds.setAttr( - string_replace_operator + ".match", - proxy_basename, - type="string" - ) - cmds.setAttr( - string_replace_operator + ".replace", - os.path.basename(path), - type="string" - ) + self._update_operators(string_replace_operator, proxy_basename, path) cmds.connectAttr( string_replace_operator + ".out", @@ -194,18 +206,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): path = get_representation_path(repre_entity) proxy_basename, proxy_path = self._get_proxy_path(path) - # Whether there is proxy or so, we still update the string operator. + # Whether there is proxy or not, we still update the string operator. # If no proxy exists, the string operator won't replace anything. - cmds.setAttr( - string_replace_operator + ".match", - proxy_basename, - type="string" - ) - cmds.setAttr( - string_replace_operator + ".replace", - os.path.basename(path), - type="string" - ) + self._update_operators(string_replace_operator, proxy_basename, path) dso_path = path if os.path.exists(proxy_path): diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 0db89bee31..fb71e128eb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -10,21 +10,23 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): # Offset to be after renderable camera collection. order = pyblish.api.CollectorOrder + 0.2 label = "Collect Arnold Scene Source" - families = ["ass"] + families = ["ass", "assProxy"] def process(self, instance): - objsets = instance.data["setMembers"] + instance.data["members"] = [] + for set_member in instance.data["setMembers"]: + if cmds.nodeType(set_member) != "objectSet": + instance.data["members"].extend(self.get_hierarchy(set_member)) + continue - for objset in objsets: - objset = str(objset) - members = cmds.sets(objset, query=True) + members = cmds.sets(set_member, query=True) members = cmds.ls(members, long=True) if members is None: - self.log.warning("Skipped empty instance: \"%s\" " % objset) + self.log.warning( + "Skipped empty instance: \"%s\" " % set_member + ) continue - if objset.endswith("content_SET"): - instance.data["contentMembers"] = self.get_hierarchy(members) - if objset.endswith("proxy_SET"): + if set_member.endswith("proxy_SET"): instance.data["proxy"] = self.get_hierarchy(members) # Use camera in object set if present else default to render globals @@ -33,7 +35,7 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] if renderable: camera = renderable[0] - for node in instance.data["contentMembers"]: + for node in instance.data["members"]: camera_shapes = cmds.listRelatives( node, shapes=True, type="camera" ) @@ -46,18 +48,11 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.debug("data: {}".format(instance.data)) def get_hierarchy(self, nodes): - """Return nodes with all their children. - - Arguments: - nodes (List[str]): List of nodes to collect children hierarchy for - - Returns: - list: Input nodes with their children hierarchy - - """ + """Return nodes with all their children""" nodes = cmds.ls(nodes, long=True) if not nodes: return [] - - children = get_all_children(nodes, ignore_intermediate_objects=True) - return list(children.union(nodes)) + children = get_all_children(nodes) + # Make sure nodes merged with children only + # contains unique entries + return list(set(nodes + list(children))) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/publish/extract_arnold_scene_source.py index ed8f2ad40c..fb4c41f1de 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -17,8 +17,7 @@ class ExtractArnoldSceneSource(publish.Extractor): families = ["ass"] asciiAss = False - def process(self, instance): - staging_dir = self.staging_dir(instance) + def _pre_process(self, instance, staging_dir): file_path = os.path.join(staging_dir, "{}.ass".format(instance.name)) # Mask @@ -70,24 +69,38 @@ class ExtractArnoldSceneSource(publish.Extractor): "mask": mask } - filenames, nodes_by_id = self._extract( - instance.data["contentMembers"], attribute_data, kwargs - ) - if "representations" not in instance.data: instance.data["representations"] = [] + return attribute_data, kwargs + + def process(self, instance): + staging_dir = self.staging_dir(instance) + attribute_data, kwargs = self._pre_process(instance, staging_dir) + + filenames = self._extract( + instance.data["members"], attribute_data, kwargs + ) + + self._post_process( + instance, filenames, staging_dir, kwargs["startFrame"] + ) + + def _post_process(self, instance, filenames, staging_dir, frame_start): + nodes_by_id = self._nodes_by_id(instance[:]) representation = { "name": "ass", "ext": "ass", "files": filenames if len(filenames) > 1 else filenames[0], "stagingDir": staging_dir, - "frameStart": kwargs["startFrame"] + "frameStart": frame_start } instance.data["representations"].append(representation) - json_path = os.path.join(staging_dir, "{}.json".format(instance.name)) + json_path = os.path.join( + staging_dir, "{}.json".format(instance.name) + ) with open(json_path, "w") as f: json.dump(nodes_by_id, f) @@ -104,13 +117,68 @@ class ExtractArnoldSceneSource(publish.Extractor): "Extracted instance {} to: {}".format(instance.name, staging_dir) ) - # Extract proxy. - if not instance.data.get("proxy", []): - return + def _nodes_by_id(self, nodes): + nodes_by_id = defaultdict(list) - kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") + for node in nodes: + id = lib.get_id(node) - filenames, _ = self._extract( + if id is None: + continue + + # Converting Maya hierarchy separator "|" to Arnold separator "/". + nodes_by_id[id].append(node.replace("|", "/")) + + return nodes_by_id + + def _extract(self, nodes, attribute_data, kwargs): + filenames = [] + with lib.attribute_values(attribute_data): + with lib.maintained_selection(): + self.log.debug( + "Writing: {}".format(nodes) + ) + cmds.select(nodes, noExpand=True) + + self.log.debug( + "Extracting ass sequence with: {}".format(kwargs) + ) + + exported_files = cmds.arnoldExportAss(**kwargs) + + for file in exported_files: + filenames.append(os.path.split(file)[1]) + + self.log.debug("Exported: {}".format(filenames)) + + return filenames + + +class ExtractArnoldSceneSourceProxy(ExtractArnoldSceneSource): + """Extract the content of the instance to an Arnold Scene Source file.""" + + label = "Extract Arnold Scene Source Proxy" + hosts = ["maya"] + families = ["assProxy"] + asciiAss = True + + def process(self, instance): + staging_dir = self.staging_dir(instance) + attribute_data, kwargs = self._pre_process(instance, staging_dir) + + filenames, _ = self._duplicate_extract( + instance.data["members"], attribute_data, kwargs + ) + + self._post_process( + instance, filenames, staging_dir, kwargs["startFrame"] + ) + + kwargs["filename"] = os.path.join( + staging_dir, "{}_proxy.ass".format(instance.name) + ) + + filenames, _ = self._duplicate_extract( instance.data["proxy"], attribute_data, kwargs ) @@ -125,12 +193,11 @@ class ExtractArnoldSceneSource(publish.Extractor): instance.data["representations"].append(representation) - def _extract(self, nodes, attribute_data, kwargs): + def _duplicate_extract(self, nodes, attribute_data, kwargs): self.log.debug( "Writing {} with:\n{}".format(kwargs["filename"], kwargs) ) filenames = [] - nodes_by_id = defaultdict(list) # Duplicating nodes so they are direct children of the world. This # makes the hierarchy of any exported ass file the same. with lib.delete_after() as delete_bin: @@ -147,7 +214,9 @@ class ExtractArnoldSceneSource(publish.Extractor): if not shapes: continue - duplicate_transform = cmds.duplicate(node)[0] + basename = cmds.duplicate(node)[0] + parents = cmds.ls(node, long=True)[0].split("|")[:-1] + duplicate_transform = "|".join(parents + [basename]) if cmds.listRelatives(duplicate_transform, parent=True): duplicate_transform = cmds.parent( @@ -172,28 +241,7 @@ class ExtractArnoldSceneSource(publish.Extractor): duplicate_nodes.extend(shapes) delete_bin.append(duplicate_transform) - # Copy cbId to mtoa_constant. - for node in duplicate_nodes: - # Converting Maya hierarchy separator "|" to Arnold - # separator "/". - nodes_by_id[lib.get_id(node)].append(node.replace("|", "/")) - - with lib.attribute_values(attribute_data): - with lib.maintained_selection(): - self.log.debug( - "Writing: {}".format(duplicate_nodes) - ) - cmds.select(duplicate_nodes, noExpand=True) - - self.log.debug( - "Extracting ass sequence with: {}".format(kwargs) - ) - - exported_files = cmds.arnoldExportAss(**kwargs) - - for file in exported_files: - filenames.append(os.path.split(file)[1]) - - self.log.debug("Exported: {}".format(filenames)) + nodes_by_id = self._nodes_by_id(duplicate_nodes) + filenames = self._extract(duplicate_nodes, attribute_data, kwargs) return filenames, nodes_by_id diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source.py index 92b4922492..8574b3ecc8 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source.py @@ -1,30 +1,56 @@ +from maya import cmds + import pyblish.api + from ayon_core.pipeline.publish import ( ValidateContentsOrder, PublishValidationError ) +from ayon_core.hosts.maya.api.lib import is_visible class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): """Validate Arnold Scene Source. - We require at least 1 root node/parent for the meshes. This is to ensure we - can duplicate the nodes and preserve the names. + Ensure no nodes are hidden. + """ - If using proxies we need the nodes to share the same names and not be + order = ValidateContentsOrder + hosts = ["maya"] + families = ["ass", "assProxy"] + label = "Validate Arnold Scene Source" + + def process(self, instance): + # Validate against having nodes hidden, which will result in the + # extraction to ignore the node. + nodes = instance.data["members"] + instance.data.get("proxy", []) + nodes = [x for x in nodes if cmds.objectType(x, isAType='dagNode')] + hidden_nodes = [ + x for x in nodes if not is_visible(x, intermediateObject=False) + ] + if hidden_nodes: + raise PublishValidationError( + "Found hidden nodes:\n\n{}\n\nPlease unhide for" + " publishing.".format("\n".join(hidden_nodes)) + ) + + +class ValidateArnoldSceneSourceProxy(pyblish.api.InstancePlugin): + """Validate Arnold Scene Source Proxy. + + When using proxies we need the nodes to share the same names and not be parent to the world. This ends up needing at least two groups with content nodes and proxy nodes in another. """ order = ValidateContentsOrder hosts = ["maya"] - families = ["ass"] - label = "Validate Arnold Scene Source" + families = ["assProxy"] + label = "Validate Arnold Scene Source Proxy" def _get_nodes_by_name(self, nodes): ungrouped_nodes = [] nodes_by_name = {} parents = [] - same_named_nodes = {} for node in nodes: node_split = node.split("|") if len(node_split) == 2: @@ -35,33 +61,16 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): parents.append(parent) node_name = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] - - # Check for same same nodes, which can happen in different - # hierarchies. - if node_name in nodes_by_name: - try: - same_named_nodes[node_name].append(node) - except KeyError: - same_named_nodes[node_name] = [ - nodes_by_name[node_name], node - ] - nodes_by_name[node_name] = node - if same_named_nodes: - message = "Found nodes with the same name:" - for name, nodes in same_named_nodes.items(): - message += "\n\n\"{}\":\n{}".format(name, "\n".join(nodes)) - - raise PublishValidationError(message) - return ungrouped_nodes, nodes_by_name, parents def process(self, instance): + # Validate against nodes directly parented to world. ungrouped_nodes = [] nodes, content_nodes_by_name, content_parents = ( - self._get_nodes_by_name(instance.data["contentMembers"]) + self._get_nodes_by_name(instance.data["members"]) ) ungrouped_nodes.extend(nodes) @@ -70,24 +79,21 @@ class ValidateArnoldSceneSource(pyblish.api.InstancePlugin): ) ungrouped_nodes.extend(nodes) - # Validate against nodes directly parented to world. if ungrouped_nodes: raise PublishValidationError( "Found nodes parented to the world: {}\n" "All nodes need to be grouped.".format(ungrouped_nodes) ) - # Proxy validation. - if not instance.data.get("proxy", []): - return - # Validate for content and proxy nodes amount being the same. - if len(instance.data["contentMembers"]) != len(instance.data["proxy"]): + if len(instance.data["members"]) != len(instance.data["proxy"]): raise PublishValidationError( "Amount of content nodes ({}) and proxy nodes ({}) needs to " - "be the same.".format( - len(instance.data["contentMembers"]), - len(instance.data["proxy"]) + "be the same.\nContent nodes: {}\nProxy nodes:{}".format( + len(instance.data["members"]), + len(instance.data["proxy"]), + instance.data["members"], + instance.data["proxy"] ) ) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py index a9d896952d..e5dbe178fc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py @@ -17,7 +17,7 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin, order = ValidateContentsOrder hosts = ["maya"] - families = ["ass"] + families = ["assProxy"] label = "Validate Arnold Scene Source CBID" actions = [RepairAction] optional = False @@ -40,15 +40,11 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin, @classmethod def get_invalid_couples(cls, instance): - content_nodes_by_name = cls._get_nodes_by_name( - instance.data["contentMembers"] - ) - proxy_nodes_by_name = cls._get_nodes_by_name( - instance.data.get("proxy", []) - ) + nodes_by_name = cls._get_nodes_by_name(instance.data["members"]) + proxy_nodes_by_name = cls._get_nodes_by_name(instance.data["proxy"]) invalid_couples = [] - for content_name, content_node in content_nodes_by_name.items(): + for content_name, content_node in nodes_by_name.items(): proxy_node = proxy_nodes_by_name.get(content_name, None) if not proxy_node: @@ -70,7 +66,7 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return # Proxy validation. - if not instance.data.get("proxy", []): + if not instance.data["proxy"]: return # Validate for proxy nodes sharing the same cbId as content nodes. diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 764168edd3..9ae96e1a20 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -42,7 +42,7 @@ def prepare_changes(old_entity, new_entity): Returns: dict[str, Any]: Changes that have new entity. - + """ changes = {} for key in set(new_entity.keys()): @@ -121,6 +121,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "setdress", "layout", "ass", + "assProxy", "vdbcache", "scene", "vrayproxy", 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 573/727] :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 574/727] :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": [], From 230611e91c6a89f73b8fc9c80a9414107018a0ac Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 15:58:26 +0200 Subject: [PATCH 575/727] AY-4801 - new creator for editorial_pckg Should publish folder with otio file and (.mov) resources. --- .../create/create_editorial_package.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py new file mode 100644 index 0000000000..6a581b59d1 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py @@ -0,0 +1,66 @@ +from pathlib import Path + +from ayon_core.pipeline import ( + CreatedInstance, +) + +from ayon_core.lib.attribute_definitions import FileDef +from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator + + +class EditorialPackageCreator(TrayPublishCreator): + """Creates instance for OTIO file from published folder. + + Folder contains OTIO file and exported .mov files. Process should publish + whole folder as single `editorial_pckg` product type and (possibly) convert + .mov files into different format and copy them into `publish` `resources` + subfolder. + """ + identifier = "editorial_pckg" + label = "Editorial package" + product_type = "editorial_pckg" + description = "Publish folder with OTIO file and resources" + + # Position batch creator after simple creators + order = 120 + + + def get_icon(self): + return "fa.folder" + + def create(self, product_name, instance_data, pre_create_data): + folder_path = pre_create_data.get("folder_path") + if not folder_path: + return + + instance_data["creator_attributes"] = { + "path": (Path(folder_path["directory"]) / + Path(folder_path["filenames"][0])).as_posix() + } + + # Create new instance + new_instance = CreatedInstance(self.product_type, product_name, + instance_data, self) + self._store_new_instance(new_instance) + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes + return [ + FileDef( + "folder_path", + folders=True, + single_item=True, + extensions=[], + allow_sequences=False, + label="Folder path" + ) + ] + + def get_detail_description(self): + return """# Publish folder with OTIO file and video clips + + Folder contains OTIO file and exported .mov files. Process should + publish whole folder as single `editorial_pckg` product type and + (possibly) convert .mov files into different format and copy them into + `publish` `resources` subfolder. + """ From 5189325225dad5c9ad917496711ab2d4282190ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 May 2024 15:58:53 +0200 Subject: [PATCH 576/727] Fix comp repair folder settings --- client/ayon_core/hosts/fusion/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/api/lib.py b/client/ayon_core/hosts/fusion/api/lib.py index 08722463e1..7f7d20010d 100644 --- a/client/ayon_core/hosts/fusion/api/lib.py +++ b/client/ayon_core/hosts/fusion/api/lib.py @@ -169,7 +169,7 @@ def validate_comp_prefs(comp=None, force_repair=False): def _on_repair(): attributes = dict() for key, comp_key, _label in validations: - value = folder_value[key] + value = folder_attributes[key] comp_key_full = "Comp.FrameFormat.{}".format(comp_key) attributes[comp_key_full] = value comp.SetPrefs(attributes) From 4318218881c6d4c0ad0f83bd23395b1c8936f5b7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 16:02:56 +0200 Subject: [PATCH 577/727] AY-4801 - new collector for editorial_pckg Collects otio_path and resource_paths from folder. Doesn't parse and collect otio_data yet, to not carry too much data over.(Might be changed) --- .../publish/collect_editorial_package.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py new file mode 100644 index 0000000000..101f58b6d1 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py @@ -0,0 +1,58 @@ +"""Produces instance.data["editorial_pckg"] data used during integration. + +Requires: + instance.data["creator_attributes"]["path"] - from creator + +Provides: + instance -> editorial_pckg (dict): + folder_path (str) + otio_path (str) - from dragged folder + resource_paths (list) + +""" +import os + +import pyblish.api + +from ayon_core.lib.transcoding import VIDEO_EXTENSIONS + + +class CollectEditorialPackage(pyblish.api.InstancePlugin): + """Collects path to OTIO file and resources""" + + label = "Collect Editorial Package" + order = pyblish.api.CollectorOrder - 0.1 + + hosts = ["traypublisher"] + families = ["editorial_pckg"] + + def process(self, instance): + folder_path = instance.data["creator_attributes"].get("path") + if not folder_path or not os.path.exists(folder_path): + self.log.info(( + "Instance doesn't contain collected existing folder path." + )) + return + + instance.data["editorial_pckg"] = {} + instance.data["editorial_pckg"]["folder_path"] = folder_path + + otio_path, resource_paths = ( + self._get_otio_and_resource_paths(folder_path)) + + instance.data["editorial_pckg"]["otio_path"] = otio_path + instance.data["editorial_pckg"]["resource_paths"] = resource_paths + + def _get_otio_and_resource_paths(self, folder_path): + otio_path = None + resource_paths = [] + + file_names = os.listdir(folder_path) + for filename in file_names: + _, ext = os.path.splitext(filename) + file_path = os.path.join(folder_path, filename) + if ext == ".otio": + otio_path = file_path + elif ext in VIDEO_EXTENSIONS: + resource_paths.append(file_path) + return otio_path, resource_paths From 1ff4d63091fbcbdcabc434317181fd19f00a916d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 16:11:12 +0200 Subject: [PATCH 578/727] AY-4801 - new validator for editorial_pckg Currently checks only by file names and expects flat structure. It ignores path to resources in otio file as folder might be dragged in and published from different location than it was created. --- .../publish/validate_editorial_package.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py new file mode 100644 index 0000000000..869dc73811 --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py @@ -0,0 +1,70 @@ +import os +import opentimelineio + +import pyblish.api +from ayon_core.pipeline import PublishValidationError + + +class ValidateEditorialPackage(pyblish.api.InstancePlugin): + """Checks that published folder contains all resources from otio + + Currently checks only by file names and expects flat structure. + It ignores path to resources in otio file as folder might be dragged in and + published from different location than it was created. + """ + + label = "Validate Editorial Package" + order = pyblish.api.ValidatorOrder - 0.49 + + hosts = ["traypublisher"] + families = ["editorial_pckg"] + + def process(self, instance): + editorial_pckg_data = instance.data.get("editorial_pckg") + if not editorial_pckg_data: + raise PublishValidationError( + f"Editorial package not collected") + + folder_path = editorial_pckg_data["folder_path"] + + otio_path = editorial_pckg_data["otio_path"] + if not otio_path: + raise PublishValidationError( + f"Folder {folder_path} missing otio file") + + resource_paths = editorial_pckg_data["resource_paths"] + + resource_file_names = {os.path.basename(path) + for path in resource_paths} + + otio_data = opentimelineio.adapters.read_from_file(otio_path) + + target_urls = self._get_all_target_urls(otio_data) + missing_files = set() + for target_url in target_urls: + target_basename = os.path.basename(target_url) + if target_basename not in resource_file_names: + missing_files.add(target_basename) + + if missing_files: + raise PublishValidationError("Otio file contains missing files " + f"'{missing_files}'.") + + instance.data["editorial_pckg"]["otio_data"] = otio_data + + def _get_all_target_urls(self, otio_data): + target_urls = [] + + # Iterate through tracks, clips, or other elements + for track in otio_data.tracks: + for clip in track: + # Check if the clip has a media reference + if clip.media_reference is not None: + # Access the target_url from the media reference + target_url = clip.media_reference.target_url + if target_url: + target_urls.append(target_url) + + return target_urls + + From 5fed3d7b2f6ed60739cea1de53c4d2d26d0f01ca Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 16:31:42 +0200 Subject: [PATCH 579/727] AY-745 - added missed blender for credential collection --- .../modules/deadline/plugins/publish/collect_user_credentials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 7a506ab645..5d03523c89 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -25,6 +25,7 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): targets = ["local"] hosts = ["aftereffects", + "blender", "fusion", "harmony", "nuke", From 22d1837db52d55b806765321ecef98e1967b0923 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 16:36:09 +0200 Subject: [PATCH 580/727] AY-745 - fixe for cache submissions --- .../deadline/plugins/publish/submit_publish_cache_job.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index c73fb253f1..16fb66a59a 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -346,15 +346,17 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, deadline_publish_job_id = None if submission_type == "deadline": - deadline_url = instance.data["deadline"]["url"] - assert deadline_url, "Requires Deadline Webservice URL" + self.deadline_url = instance.data["deadline"]["url"] + assert self.deadline_url, "Requires Deadline Webservice URL" deadline_publish_job_id = \ self._submit_deadline_post_job(instance, render_job) # Inject deadline url to instances. for inst in instances: - inst["deadlineUrl"] = self.deadline_url + if "deadline" not in inst: + inst["deadline"] = {} + inst["deadline"] = instance.data["deadline"] # publish job file publish_job = { From 99e5cf99602c3b2321445776bf63e6cefe85a19c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 May 2024 16:49:23 +0200 Subject: [PATCH 581/727] Tweak the validation report --- .../validate_alembic_options_defaults.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 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 476f837135..c19bc6a0f4 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,3 +1,4 @@ +import inspect import pyblish.api from ayon_core.pipeline import OptionalPyblishPluginMixin @@ -39,11 +40,11 @@ class ValidateAlembicDefaultsPointcache( attributes = self._get_publish_attributes(instance) msg = ( - "Alembic Extract setting \"{}\" is not the default value:" - "\nCurrent: {}" - "\nDefault Value: {}\n" + "Alembic extract setting \"{}\" is not the default value:" + "\n- Current: {}" + "\n- Default: {}\n" ) - errors = [] + invalid = False for key, value in attributes.items(): default_value = settings[key] @@ -54,10 +55,32 @@ class ValidateAlembicDefaultsPointcache( default_value = sorted(default_value) if value != default_value: - errors.append(msg.format(key, value, default_value)) + self.log.error( + msg.format(key, value, default_value) + ) + invalid = True - if errors: - raise PublishValidationError("\n".join(errors)) + if invalid: + raise PublishValidationError( + "Detected alembic options that differ from the default value.", + description=self.get_description() + ) + + @staticmethod + def get_description(): + return inspect.cleandoc( + """### Alembic Extract settings differ from defaults + + The alembic export options differ from the project default values. + + If this is intentional you can disable this validation by + disabling **Validate Alembic Options Default**. + + If not you may use the "Repair" action to revert all the options to + their default values. + + """ + ) @classmethod def repair(cls, instance): From 4ac8123cb92c0220781b5023b0616d01ce68e042 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 May 2024 16:57:17 +0200 Subject: [PATCH 582/727] Simplify messaging --- .../validate_alembic_options_defaults.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 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 c19bc6a0f4..800c05c8a6 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 @@ -39,12 +39,7 @@ class ValidateAlembicDefaultsPointcache( settings = self._get_settings(instance.context) attributes = self._get_publish_attributes(instance) - msg = ( - "Alembic extract setting \"{}\" is not the default value:" - "\n- Current: {}" - "\n- Default: {}\n" - ) - invalid = False + invalid = {} for key, value in attributes.items(): default_value = settings[key] @@ -55,14 +50,17 @@ class ValidateAlembicDefaultsPointcache( default_value = sorted(default_value) if value != default_value: - self.log.error( - msg.format(key, value, default_value) - ) - invalid = True + invalid[key] = value, default_value if invalid: + non_defaults = "\n".join( + f"- {key}: {value} \t(default: {default_value})" + for key, (value, default_value) in invalid.items() + ) + raise PublishValidationError( - "Detected alembic options that differ from the default value.", + "Alembic extract options differ from default values:\n" + f"{non_defaults}", description=self.get_description() ) From b4b1e2af4cab5b442ce84592f97b9c27b51b16b3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 May 2024 17:02:26 +0200 Subject: [PATCH 583/727] Ignore attributes not found in settings with a warning --- .../publish/validate_alembic_options_defaults.py | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 800c05c8a6..0abb734c9b 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 @@ -41,6 +41,17 @@ class ValidateAlembicDefaultsPointcache( invalid = {} for key, value in attributes.items(): + if key not in settings: + # This may occur if attributes have changed over time and an + # existing instance has older legacy attributes that do not + # match the current settings definition. + self.log.warning( + "Publish attribute %s not found in Alembic Export " + "default settings. Ignoring validation for attribute.", + key + ) + continue + default_value = settings[key] # Lists are best to compared sorted since we cant rely on the order From 20dad59947e4fb13d026670baa73820e9e378ecd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 18:31:40 +0200 Subject: [PATCH 584/727] AY-4801 - added editorial_pckg to integrate --- client/ayon_core/plugins/publish/integrate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 764168edd3..5a9d8eae2b 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -169,6 +169,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "yeticacheUE", "tycache", "csv_ingest_file", + "editorial_pckg" ] default_template_name = "publish" From 345f5f31f1a395c4f4d468166bc343933be9974e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 May 2024 18:44:48 +0200 Subject: [PATCH 585/727] AY-4801 - added editorial_pckg extractor Modifies otio file with rootless publish paths, prepares for integration. --- .../plugins/publish/extract_editorial_pckg.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py new file mode 100644 index 0000000000..dc8163e1ff --- /dev/null +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -0,0 +1,122 @@ +import os.path +import opentimelineio + +import pyblish.api + +from ayon_core.pipeline import publish + + +class ExtractEditorialPackage(publish.Extractor): + """Replaces movie paths in otio file with publish rootless + + Prepares movie resources for integration. + TODO introduce conversion to .mp4 + """ + + label = "Extract Editorial Package" + order = pyblish.api.ExtractorOrder - 0.45 + hosts = ["traypublisher"] + families = ["editorial_pckg"] + + def process(self, instance): + editorial_pckg_data = instance.data.get("editorial_pckg") + + otio_path = editorial_pckg_data["otio_path"] + otio_basename = os.path.basename(otio_path) + staging_dir = self.staging_dir(instance) + + editorial_pckg_repre = { + 'name': "editorial_pckg", + 'ext': "otio", + 'files': otio_basename, + "stagingDir": staging_dir, + } + otio_staging_path = os.path.join(staging_dir, otio_basename) + + instance.data["representations"].append(editorial_pckg_repre) + + publish_path = self._get_published_path(instance) + publish_folder = os.path.dirname(publish_path) + publish_resource_folder = os.path.join(publish_folder, "resources") + + resource_paths = editorial_pckg_data["resource_paths"] + transfers = self._get_transfers(resource_paths, + publish_resource_folder) + if not "transfers" in instance.data: + instance.data["transfers"] = [] + instance.data["transfers"] = transfers + + source_to_rootless = self._get_resource_path_mapping(instance, + transfers) + + otio_data = editorial_pckg_data["otio_data"] + otio_data = self._replace_target_urls(otio_data, source_to_rootless) + + opentimelineio.adapters.write_to_file(otio_data, otio_staging_path) + + self.log.info("Added Editorial Package representation: {}".format( + editorial_pckg_repre)) + + def _get_resource_path_mapping(self, instance, transfers): + """Returns dict of {source_mov_path: rootless_published_path}.""" + replace_paths = {} + anatomy = instance.context.data["anatomy"] + for source, destination in transfers: + rootless_path = self._get_rootless(anatomy, destination) + source_file_name = os.path.basename(source) + replace_paths[source_file_name] = rootless_path + return replace_paths + + def _get_transfers(self, resource_paths, publish_resource_folder): + """Returns list of tuples (source, destination) movie paths.""" + transfers = [] + for res_path in resource_paths: + res_basename = os.path.basename(res_path) + pub_res_path = os.path.join(publish_resource_folder, res_basename) + transfers.append((res_path, pub_res_path)) + return transfers + + def _replace_target_urls(self, otio_data, replace_paths): + """Replace original movie paths with published rootles ones.""" + for track in otio_data.tracks: + for clip in track: + # Check if the clip has a media reference + if clip.media_reference is not None: + # Access the target_url from the media reference + target_url = clip.media_reference.target_url + if not target_url: + continue + file_name = os.path.basename(target_url) + replace_value = replace_paths.get(file_name) + if replace_value: + clip.media_reference.target_url = replace_value + + return otio_data + + def _get_rootless(self, anatomy, path): + """Try to find rootless {root[work]} path from `path`""" + success, rootless_path = anatomy.find_root_template_from_path( + path) + if not success: + # `rootless_path` is not set to `output_dir` if none of roots match + self.log.warning( + f"Could not find root path for remapping '{path}'." + ) + rootless_path = path + + return rootless_path + + def _get_published_path(self, instance): + """Calculates expected `publish` folder""" + # determine published path from Anatomy. + template_data = instance.data.get("anatomyData") + rep = instance.data["representations"][0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None + + anatomy = instance.context.data["anatomy"] + template_data["root"] = anatomy.roots + template = anatomy.get_template_item("publish", "default", "path") + template_filled = template.format_strict(template_data) + return os.path.normpath(template_filled) From 004a4feb9dc85472f022cb9501322f7e82c7081d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 6 May 2024 10:58:07 +0200 Subject: [PATCH 586/727] Fix order of collect render Must be collected before DL metadata --- .../hosts/aftereffects/plugins/publish/collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py index 5ef044459d..ebd4b8f944 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py @@ -24,7 +24,7 @@ class AERenderInstance(RenderInstance): class CollectAERender(publish.AbstractCollectRender): - order = pyblish.api.CollectorOrder + 0.405 + order = pyblish.api.CollectorOrder + 0.100 label = "Collect After Effects Render Layers" hosts = ["aftereffects"] From 831e46a9a2cbe40e9799018343b8045a658a5ee4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 12:08:05 +0200 Subject: [PATCH 587/727] added simple api to cache thumbnails --- client/ayon_core/pipeline/thumbnails.py | 263 ++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 client/ayon_core/pipeline/thumbnails.py diff --git a/client/ayon_core/pipeline/thumbnails.py b/client/ayon_core/pipeline/thumbnails.py new file mode 100644 index 0000000000..dbb38615d8 --- /dev/null +++ b/client/ayon_core/pipeline/thumbnails.py @@ -0,0 +1,263 @@ +import os +import time +import collections + +import ayon_api + +from ayon_core.lib.local_settings import get_ayon_appdirs + + +FileInfo = collections.namedtuple( + "FileInfo", + ("path", "size", "modification_time") +) + + +class ThumbnailsCache: + """Cache of thumbnails on local storage. + + Thumbnails are cached to appdirs to predefined directory. Each project has + own subfolder with thumbnails -> that's because each project has own + thumbnail id validation and file names are thumbnail ids with matching + extension. Extensions are predefined (.png and .jpeg). + + Cache has cleanup mechanism which is triggered on initialized by default. + + The cleanup has 2 levels: + 1. soft cleanup which remove all files that are older then 'days_alive' + 2. max size cleanup which remove all files until the thumbnails folder + contains less then 'max_filesize' + - this is time consuming so it's not triggered automatically + + Args: + cleanup (bool): Trigger soft cleanup (Cleanup expired thumbnails). + """ + + # Lifetime of thumbnails (in seconds) + # - default 3 days + days_alive = 3 + # Max size of thumbnail directory (in bytes) + # - default 2 Gb + max_filesize = 2 * 1024 * 1024 * 1024 + + def __init__(self, cleanup=True): + self._thumbnails_dir = None + self._days_alive_secs = self.days_alive * 24 * 60 * 60 + if cleanup: + self.cleanup() + + def get_thumbnails_dir(self): + """Root directory where thumbnails are stored. + + Returns: + str: Path to thumbnails root. + """ + + if self._thumbnails_dir is None: + self._thumbnails_dir = get_ayon_appdirs("thumbnails") + return self._thumbnails_dir + + thumbnails_dir = property(get_thumbnails_dir) + + def get_thumbnails_dir_file_info(self): + """Get information about all files in thumbnails directory. + + Returns: + List[FileInfo]: List of file information about all files. + """ + + thumbnails_dir = self.thumbnails_dir + files_info = [] + if not os.path.exists(thumbnails_dir): + return files_info + + for root, _, filenames in os.walk(thumbnails_dir): + for filename in filenames: + path = os.path.join(root, filename) + files_info.append(FileInfo( + path, os.path.getsize(path), os.path.getmtime(path) + )) + return files_info + + def get_thumbnails_dir_size(self, files_info=None): + """Got full size of thumbnail directory. + + Args: + files_info (List[FileInfo]): Prepared file information about + files in thumbnail directory. + + Returns: + int: File size of all files in thumbnail directory. + """ + + if files_info is None: + files_info = self.get_thumbnails_dir_file_info() + + if not files_info: + return 0 + + return sum( + file_info.size + for file_info in files_info + ) + + def cleanup(self, check_max_size=False): + """Cleanup thumbnails directory. + + Args: + check_max_size (bool): Also cleanup files to match max size of + thumbnails directory. + """ + + thumbnails_dir = self.get_thumbnails_dir() + # Skip if thumbnails dir does not exist yet + if not os.path.exists(thumbnails_dir): + return + + self._soft_cleanup(thumbnails_dir) + if check_max_size: + self._max_size_cleanup(thumbnails_dir) + + def _soft_cleanup(self, thumbnails_dir): + current_time = time.time() + for root, _, filenames in os.walk(thumbnails_dir): + for filename in filenames: + path = os.path.join(root, filename) + modification_time = os.path.getmtime(path) + if current_time - modification_time > self._days_alive_secs: + os.remove(path) + + def _max_size_cleanup(self, thumbnails_dir): + files_info = self.get_thumbnails_dir_file_info() + size = self.get_thumbnails_dir_size(files_info) + if size < self.max_filesize: + return + + sorted_file_info = collections.deque( + sorted(files_info, key=lambda item: item.modification_time) + ) + diff = size - self.max_filesize + while diff > 0: + if not sorted_file_info: + break + + file_info = sorted_file_info.popleft() + diff -= file_info.size + os.remove(file_info.path) + + def get_thumbnail_filepath(self, project_name, thumbnail_id): + """Get thumbnail by thumbnail id. + + Args: + project_name (str): Name of project. + thumbnail_id (str): Thumbnail id. + + Returns: + Union[str, None]: Path to thumbnail image or None if thumbnail + is not cached yet. + """ + + if not thumbnail_id: + return None + + for ext in ( + ".png", + ".jpeg", + ): + filepath = os.path.join( + self.thumbnails_dir, project_name, thumbnail_id + ext + ) + if os.path.exists(filepath): + return filepath + return None + + def get_project_dir(self, project_name): + """Path to root directory for specific project. + + Args: + project_name (str): Name of project for which root directory path + should be returned. + + Returns: + str: Path to root of project's thumbnails. + """ + + return os.path.join(self.thumbnails_dir, project_name) + + def make_sure_project_dir_exists(self, project_name): + project_dir = self.get_project_dir(project_name) + if not os.path.exists(project_dir): + os.makedirs(project_dir) + return project_dir + + def store_thumbnail(self, project_name, thumbnail_id, content, mime_type): + """Store thumbnail to cache folder. + + Args: + project_name (str): Project where the thumbnail belong to. + thumbnail_id (str): Thumbnail id. + content (bytes): Byte content of thumbnail file. + mime_type (str): Type of content. + + Returns: + str: Path to cached thumbnail image file. + """ + + if mime_type == "image/png": + ext = ".png" + elif mime_type == "image/jpeg": + ext = ".jpeg" + else: + raise ValueError( + "Unknown mime type for thumbnail \"{}\"".format(mime_type)) + + project_dir = self.make_sure_project_dir_exists(project_name) + thumbnail_path = os.path.join(project_dir, thumbnail_id + ext) + with open(thumbnail_path, "wb") as stream: + stream.write(content) + + current_time = time.time() + os.utime(thumbnail_path, (current_time, current_time)) + + return thumbnail_path + + +class _CacheItems: + thumbnails_cache = ThumbnailsCache() + + +def get_thumbnail_path(project_name, thumbnail_id): + """Get path to thumbnail image. + + Args: + project_name (str): Project where thumbnail belongs to. + thumbnail_id (Union[str, None]): Thumbnail id. + + Returns: + Union[str, None]: Path to thumbnail image or None if thumbnail + id is not valid or thumbnail was not possible to receive. + + """ + if not thumbnail_id: + return None + + filepath = _CacheItems.thumbnails_cache.get_thumbnail_filepath( + project_name, thumbnail_id + ) + if filepath is not None: + return filepath + + # 'ayon_api' had a bug, public function + # 'get_thumbnail_by_id' did not return output of + # 'ServerAPI' method. + con = ayon_api.get_server_api_connection() + result = con.get_thumbnail_by_id(project_name, thumbnail_id) + + if result is not None and result.is_valid: + return _CacheItems.thumbnails_cache.store_thumbnail( + project_name, + thumbnail_id, + result.content, + result.content_type + ) + return None From 913352cce3445a724fe28a4e1971890ce73720a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 12:08:21 +0200 Subject: [PATCH 588/727] use the implementation in common models --- .../tools/common_models/thumbnails.py | 244 +----------------- 1 file changed, 2 insertions(+), 242 deletions(-) diff --git a/client/ayon_core/tools/common_models/thumbnails.py b/client/ayon_core/tools/common_models/thumbnails.py index 6d14783b9a..2fa1e36e5c 100644 --- a/client/ayon_core/tools/common_models/thumbnails.py +++ b/client/ayon_core/tools/common_models/thumbnails.py @@ -1,234 +1,15 @@ -import os -import time import collections import ayon_api -import appdirs from ayon_core.lib import NestedCacheItem - -FileInfo = collections.namedtuple( - "FileInfo", - ("path", "size", "modification_time") -) - - -class ThumbnailsCache: - """Cache of thumbnails on local storage. - - Thumbnails are cached to appdirs to predefined directory. Each project has - own subfolder with thumbnails -> that's because each project has own - thumbnail id validation and file names are thumbnail ids with matching - extension. Extensions are predefined (.png and .jpeg). - - Cache has cleanup mechanism which is triggered on initialized by default. - - The cleanup has 2 levels: - 1. soft cleanup which remove all files that are older then 'days_alive' - 2. max size cleanup which remove all files until the thumbnails folder - contains less then 'max_filesize' - - this is time consuming so it's not triggered automatically - - Args: - cleanup (bool): Trigger soft cleanup (Cleanup expired thumbnails). - """ - - # Lifetime of thumbnails (in seconds) - # - default 3 days - days_alive = 3 - # Max size of thumbnail directory (in bytes) - # - default 2 Gb - max_filesize = 2 * 1024 * 1024 * 1024 - - def __init__(self, cleanup=True): - self._thumbnails_dir = None - self._days_alive_secs = self.days_alive * 24 * 60 * 60 - if cleanup: - self.cleanup() - - def get_thumbnails_dir(self): - """Root directory where thumbnails are stored. - - Returns: - str: Path to thumbnails root. - """ - - if self._thumbnails_dir is None: - # TODO use generic function - directory = appdirs.user_data_dir("AYON", "Ynput") - self._thumbnails_dir = os.path.join(directory, "thumbnails") - return self._thumbnails_dir - - thumbnails_dir = property(get_thumbnails_dir) - - def get_thumbnails_dir_file_info(self): - """Get information about all files in thumbnails directory. - - Returns: - List[FileInfo]: List of file information about all files. - """ - - thumbnails_dir = self.thumbnails_dir - files_info = [] - if not os.path.exists(thumbnails_dir): - return files_info - - for root, _, filenames in os.walk(thumbnails_dir): - for filename in filenames: - path = os.path.join(root, filename) - files_info.append(FileInfo( - path, os.path.getsize(path), os.path.getmtime(path) - )) - return files_info - - def get_thumbnails_dir_size(self, files_info=None): - """Got full size of thumbnail directory. - - Args: - files_info (List[FileInfo]): Prepared file information about - files in thumbnail directory. - - Returns: - int: File size of all files in thumbnail directory. - """ - - if files_info is None: - files_info = self.get_thumbnails_dir_file_info() - - if not files_info: - return 0 - - return sum( - file_info.size - for file_info in files_info - ) - - def cleanup(self, check_max_size=False): - """Cleanup thumbnails directory. - - Args: - check_max_size (bool): Also cleanup files to match max size of - thumbnails directory. - """ - - thumbnails_dir = self.get_thumbnails_dir() - # Skip if thumbnails dir does not exist yet - if not os.path.exists(thumbnails_dir): - return - - self._soft_cleanup(thumbnails_dir) - if check_max_size: - self._max_size_cleanup(thumbnails_dir) - - def _soft_cleanup(self, thumbnails_dir): - current_time = time.time() - for root, _, filenames in os.walk(thumbnails_dir): - for filename in filenames: - path = os.path.join(root, filename) - modification_time = os.path.getmtime(path) - if current_time - modification_time > self._days_alive_secs: - os.remove(path) - - def _max_size_cleanup(self, thumbnails_dir): - files_info = self.get_thumbnails_dir_file_info() - size = self.get_thumbnails_dir_size(files_info) - if size < self.max_filesize: - return - - sorted_file_info = collections.deque( - sorted(files_info, key=lambda item: item.modification_time) - ) - diff = size - self.max_filesize - while diff > 0: - if not sorted_file_info: - break - - file_info = sorted_file_info.popleft() - diff -= file_info.size - os.remove(file_info.path) - - def get_thumbnail_filepath(self, project_name, thumbnail_id): - """Get thumbnail by thumbnail id. - - Args: - project_name (str): Name of project. - thumbnail_id (str): Thumbnail id. - - Returns: - Union[str, None]: Path to thumbnail image or None if thumbnail - is not cached yet. - """ - - if not thumbnail_id: - return None - - for ext in ( - ".png", - ".jpeg", - ): - filepath = os.path.join( - self.thumbnails_dir, project_name, thumbnail_id + ext - ) - if os.path.exists(filepath): - return filepath - return None - - def get_project_dir(self, project_name): - """Path to root directory for specific project. - - Args: - project_name (str): Name of project for which root directory path - should be returned. - - Returns: - str: Path to root of project's thumbnails. - """ - - return os.path.join(self.thumbnails_dir, project_name) - - def make_sure_project_dir_exists(self, project_name): - project_dir = self.get_project_dir(project_name) - if not os.path.exists(project_dir): - os.makedirs(project_dir) - return project_dir - - def store_thumbnail(self, project_name, thumbnail_id, content, mime_type): - """Store thumbnail to cache folder. - - Args: - project_name (str): Project where the thumbnail belong to. - thumbnail_id (str): Id of thumbnail. - content (bytes): Byte content of thumbnail file. - mime_data (str): Type of content. - - Returns: - str: Path to cached thumbnail image file. - """ - - if mime_type == "image/png": - ext = ".png" - elif mime_type == "image/jpeg": - ext = ".jpeg" - else: - raise ValueError( - "Unknown mime type for thumbnail \"{}\"".format(mime_type)) - - project_dir = self.make_sure_project_dir_exists(project_name) - thumbnail_path = os.path.join(project_dir, thumbnail_id + ext) - with open(thumbnail_path, "wb") as stream: - stream.write(content) - - current_time = time.time() - os.utime(thumbnail_path, (current_time, current_time)) - - return thumbnail_path +from ayon_core.pipeline.thumbnails import get_thumbnail_path class ThumbnailsModel: entity_cache_lifetime = 240 # In seconds def __init__(self): - self._thumbnail_cache = ThumbnailsCache() self._paths_cache = collections.defaultdict(dict) self._folders_cache = NestedCacheItem( levels=2, lifetime=self.entity_cache_lifetime) @@ -283,28 +64,7 @@ class ThumbnailsModel: if thumbnail_id in project_cache: return project_cache[thumbnail_id] - filepath = self._thumbnail_cache.get_thumbnail_filepath( - project_name, thumbnail_id - ) - if filepath is not None: - project_cache[thumbnail_id] = filepath - return filepath - - # 'ayon_api' had a bug, public function - # 'get_thumbnail_by_id' did not return output of - # 'ServerAPI' method. - con = ayon_api.get_server_api_connection() - result = con.get_thumbnail_by_id(project_name, thumbnail_id) - if result is None: - pass - - elif result.is_valid: - filepath = self._thumbnail_cache.store_thumbnail( - project_name, - thumbnail_id, - result.content, - result.content_type - ) + filepath = get_thumbnail_path(project_name, thumbnail_id) project_cache[thumbnail_id] = filepath return filepath From 21981205674b7787f457a1134f3fd5b137b52e99 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 May 2024 18:27:14 +0800 Subject: [PATCH 589/727] support adding tool group env per task level --- .../applications/client/ayon_applications/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/utils.py b/server_addon/applications/client/ayon_applications/utils.py index 234fa6c683..2487db67ad 100644 --- a/server_addon/applications/client/ayon_applications/utils.py +++ b/server_addon/applications/client/ayon_applications/utils.py @@ -281,13 +281,15 @@ def prepare_app_environments( app.environment ] - folder_entity = data.get("folder_entity") + entity = data.get("task_entity") + if not entity: + entity = data.get("folder_entity") # Add tools environments groups_by_name = {} tool_by_group_name = collections.defaultdict(dict) - if folder_entity: + if entity: # Make sure each tool group can be added only once - for key in folder_entity["attrib"].get("tools") or []: + for key in entity["attrib"].get("tools") or []: tool = app.manager.tools.get(key) if not tool or not tool.is_valid_for_app(app): continue From 39a3271d0cc5dbca7027a1e67b14c9a5486f6de4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 May 2024 19:02:19 +0800 Subject: [PATCH 590/727] support adding tool group env per task level --- .../client/ayon_applications/utils.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/utils.py b/server_addon/applications/client/ayon_applications/utils.py index 2487db67ad..751b8c0835 100644 --- a/server_addon/applications/client/ayon_applications/utils.py +++ b/server_addon/applications/client/ayon_applications/utils.py @@ -281,28 +281,23 @@ def prepare_app_environments( app.environment ] - entity = data.get("task_entity") - if not entity: - entity = data.get("folder_entity") + task_entity = data.get("task_entity") + folder_entity = data.get("folder_entity") # Add tools environments groups_by_name = {} tool_by_group_name = collections.defaultdict(dict) - if entity: - # Make sure each tool group can be added only once - for key in entity["attrib"].get("tools") or []: - tool = app.manager.tools.get(key) - if not tool or not tool.is_valid_for_app(app): - continue - groups_by_name[tool.group.name] = tool.group - tool_by_group_name[tool.group.name][tool.name] = tool - - for group_name in sorted(groups_by_name.keys()): - group = groups_by_name[group_name] - environments.append(group.environment) - for tool_name in sorted(tool_by_group_name[group_name].keys()): - tool = tool_by_group_name[group_name][tool_name] - environments.append(tool.environment) - app_and_tool_labels.append(tool.full_name) + if task_entity: + groups_by_name, environments, app_and_tool_labels = ( + get_tool_group_enviornment_by_entities(app, task_entity, groups_by_name, + tool_by_group_name, environments, + app_and_tool_labels) + ) + elif folder_entity: + groups_by_name, environments, app_and_tool_labels = ( + get_tool_group_enviornment_by_entities(app, folder_entity, groups_by_name, + tool_by_group_name, environments, + app_and_tool_labels) + ) log.debug( "Will add environments for apps and tools: {}".format( @@ -356,6 +351,37 @@ def prepare_app_environments( data["env"].pop(key, None) +def get_tool_group_enviornment_by_entities(app, entity, groups_by_name, + tool_by_group_name, environments, + app_and_tool_labels): + """Function to get tool group environment by entities + + Args: + app (dict): application + entity (dict): entity + groups_by_name (dict): group by name + tool_by_group_name (dict): tools by group name + environments (list): enviornments + app_and_tool_labels (list): full name of the application + """ + # Make sure each tool group can be added only once + for key in entity["attrib"].get("tools") or []: + tool = app.manager.tools.get(key) + if not tool or not tool.is_valid_for_app(app): + continue + groups_by_name[tool.group.name] = tool.group + tool_by_group_name[tool.group.name][tool.name] = tool + + for group_name in sorted(groups_by_name.keys()): + group = groups_by_name[group_name] + environments.append(group.environment) + for tool_name in sorted(tool_by_group_name[group_name].keys()): + tool = tool_by_group_name[group_name][tool_name] + environments.append(tool.environment) + app_and_tool_labels.append(tool.full_name) + return groups_by_name, environments, app_and_tool_labels + + def apply_project_environments_value( project_name, env, project_settings=None, env_group=None ): From e669ac7ab231b6114d8d289c505af8aaa2a4f65e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 May 2024 19:04:51 +0800 Subject: [PATCH 591/727] support adding tool group env per task level --- .../client/ayon_applications/utils.py | 60 ++++++------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/utils.py b/server_addon/applications/client/ayon_applications/utils.py index 751b8c0835..e05bbee1ca 100644 --- a/server_addon/applications/client/ayon_applications/utils.py +++ b/server_addon/applications/client/ayon_applications/utils.py @@ -287,17 +287,24 @@ def prepare_app_environments( groups_by_name = {} tool_by_group_name = collections.defaultdict(dict) if task_entity: - groups_by_name, environments, app_and_tool_labels = ( - get_tool_group_enviornment_by_entities(app, task_entity, groups_by_name, - tool_by_group_name, environments, - app_and_tool_labels) - ) - elif folder_entity: - groups_by_name, environments, app_and_tool_labels = ( - get_tool_group_enviornment_by_entities(app, folder_entity, groups_by_name, - tool_by_group_name, environments, - app_and_tool_labels) - ) + # Make sure each tool group can be added only once + tools_group_by_entity = task_entity["attrib"].get("tools") + if folder_entity and not tools_group_by_entity: + tools_group_by_entity = folder_entity["attrib"].get("tools") + for key in tools_group_by_entity or []: + tool = app.manager.tools.get(key) + if not tool or not tool.is_valid_for_app(app): + continue + groups_by_name[tool.group.name] = tool.group + tool_by_group_name[tool.group.name][tool.name] = tool + + for group_name in sorted(groups_by_name.keys()): + group = groups_by_name[group_name] + environments.append(group.environment) + for tool_name in sorted(tool_by_group_name[group_name].keys()): + tool = tool_by_group_name[group_name][tool_name] + environments.append(tool.environment) + app_and_tool_labels.append(tool.full_name) log.debug( "Will add environments for apps and tools: {}".format( @@ -351,37 +358,6 @@ def prepare_app_environments( data["env"].pop(key, None) -def get_tool_group_enviornment_by_entities(app, entity, groups_by_name, - tool_by_group_name, environments, - app_and_tool_labels): - """Function to get tool group environment by entities - - Args: - app (dict): application - entity (dict): entity - groups_by_name (dict): group by name - tool_by_group_name (dict): tools by group name - environments (list): enviornments - app_and_tool_labels (list): full name of the application - """ - # Make sure each tool group can be added only once - for key in entity["attrib"].get("tools") or []: - tool = app.manager.tools.get(key) - if not tool or not tool.is_valid_for_app(app): - continue - groups_by_name[tool.group.name] = tool.group - tool_by_group_name[tool.group.name][tool.name] = tool - - for group_name in sorted(groups_by_name.keys()): - group = groups_by_name[group_name] - environments.append(group.environment) - for tool_name in sorted(tool_by_group_name[group_name].keys()): - tool = tool_by_group_name[group_name][tool_name] - environments.append(tool.environment) - app_and_tool_labels.append(tool.full_name) - return groups_by_name, environments, app_and_tool_labels - - def apply_project_environments_value( project_name, env, project_settings=None, env_group=None ): From d6ae1db1b64becb58ac4daef9f2a01fbdbdde707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 6 May 2024 13:43:14 +0200 Subject: [PATCH 592/727] :recycle: refactor var name and the logic a little bit --- client/ayon_core/pipeline/create/context.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index e66e15b8b1..dd005d250c 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1988,16 +1988,12 @@ class CreateContext: ) if task_entity is None: - task_name = self.get_current_task_name() - if task_name: + current_task_name = self.get_current_task_name() + if current_task_name: task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name + project_name, folder_entity["id"], current_task_name ) - task_name = None - if task_entity: - task_name = task_entity["name"] - if pre_create_data is None: pre_create_data = {} @@ -2022,11 +2018,10 @@ class CreateContext: instance_data = { "folderPath": folder_entity["path"], - "task": task_name, + "task": task_entity["name"] if task_entity else None, "productType": creator.product_type, "variant": variant } - print("Create instance data", instance_data) return creator.create( product_name, instance_data, From 037edc37225b92aae47cb557fa19702d1cef3142 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 May 2024 20:01:17 +0800 Subject: [PATCH 593/727] code tweak -Jakub's comment --- .../applications/client/ayon_applications/utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/server_addon/applications/client/ayon_applications/utils.py b/server_addon/applications/client/ayon_applications/utils.py index e05bbee1ca..185779a949 100644 --- a/server_addon/applications/client/ayon_applications/utils.py +++ b/server_addon/applications/client/ayon_applications/utils.py @@ -286,12 +286,15 @@ def prepare_app_environments( # Add tools environments groups_by_name = {} tool_by_group_name = collections.defaultdict(dict) + tools = None if task_entity: - # Make sure each tool group can be added only once - tools_group_by_entity = task_entity["attrib"].get("tools") - if folder_entity and not tools_group_by_entity: - tools_group_by_entity = folder_entity["attrib"].get("tools") - for key in tools_group_by_entity or []: + tools = task_entity["attrib"].get("tools") + + if tools is None and folder_entity: + tools = folder_entity["attrib"].get("tools") + + if tools: + for key in tools: tool = app.manager.tools.get(key) if not tool or not tool.is_valid_for_app(app): continue From 5e0ab289293b37a195c3c59bf42221c4e04e823d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:04:36 +0200 Subject: [PATCH 594/727] renamed 'tools__get_config_data_name' to '_get_host_config_data' --- client/ayon_core/pipeline/colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 8dcbaeb877..f17e9d5f7b 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1069,7 +1069,7 @@ def get_imageio_config_preset( log, ) else: - config_data = _get_config_data( + config_data = _get_host_config_data( host_ocio_config["filepath"], template_data, env ) @@ -1082,7 +1082,7 @@ def get_imageio_config_preset( return config_data -def _get_config_data(path_list, anatomy_data, env=None): +def _get_host_config_data(path_list, anatomy_data, env=None): """Return first existing path in path list. If template is used in path inputs, From 1589ee5c0e9ccf35e723d7f703fad4118470c8bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:05:01 +0200 Subject: [PATCH 595/727] simplified '_get_host_config_data' --- client/ayon_core/pipeline/colorspace.py | 43 ++++++++++--------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index f17e9d5f7b..595c50606c 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1082,39 +1082,30 @@ def get_imageio_config_preset( return config_data -def _get_host_config_data(path_list, anatomy_data, env=None): +def _get_host_config_data(templates, template_data): """Return first existing path in path list. - If template is used in path inputs, - then it is formatted by anatomy data - and environment variables + Use template data to fill possible formatting in paths. Args: - path_list (list[str]): list of abs paths - anatomy_data (dict): formatting data - env (Optional[dict]): Environment variables. + templates (list[str]): List of templates to config paths. + template_data (dict): Template data used to format templates. Returns: - dict: config data + Union[dict, None]: Config data or 'None' if templates are empty + or any path exists. + """ - formatting_data = deepcopy(anatomy_data) - - environment_vars = env or dict(**os.environ) - - # format the path for potential env vars - formatting_data.update(environment_vars) - - # first try host config paths - for path_ in path_list: - formatted_path = _format_path(path_, formatting_data) - - if not os.path.exists(formatted_path): - continue - - return { - "path": os.path.normpath(formatted_path), - "template": path_ - } + for template in templates: + formatted_path = StringTemplate.format_strict_template( + template, template_data + ) + path = os.path.abspath(formatted_path) + if os.path.exists(path): + return { + "path": os.path.normpath(path), + "template": template + } def _format_path(template_path, formatting_data): From aaeaa1e7f0c6c977716b5e240b3610090d32190c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:10:14 +0200 Subject: [PATCH 596/727] removed unused '_format_path' --- client/ayon_core/pipeline/colorspace.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 595c50606c..363012dad5 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1108,24 +1108,6 @@ def _get_host_config_data(templates, template_data): } -def _format_path(template_path, formatting_data): - """Single template path formatting. - - Args: - template_path (str): template string - formatting_data (dict): data to be used for - template formatting - - Returns: - str: absolute formatted path - """ - # format path for anatomy keys - formatted_path = StringTemplate(template_path).format( - formatting_data) - - return os.path.abspath(formatted_path) - - def get_imageio_file_rules(project_name, host_name, project_settings=None): """Get ImageIO File rules from project settings From c6b6ca1e3cfbb3d400b65ec3ead9d5bd5518650b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:10:36 +0200 Subject: [PATCH 597/727] use safer option to format template path --- client/ayon_core/pipeline/colorspace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 363012dad5..1ab93c7844 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1097,9 +1097,12 @@ def _get_host_config_data(templates, template_data): """ for template in templates: - formatted_path = StringTemplate.format_strict_template( + formatted_path = StringTemplate.format_template( template, template_data ) + if not formatted_path.solved: + continue + path = os.path.abspath(formatted_path) if os.path.exists(path): return { From bc01bf08f2a45339b476af61094904551a73a12b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:50:00 +0200 Subject: [PATCH 598/727] updated 'get_colorspace_settings_from_publish_context' to use new function --- client/ayon_core/pipeline/colorspace.py | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 1ab93c7844..5793fd1143 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1236,27 +1236,41 @@ def get_colorspace_settings_from_publish_context(context_data): Returns: tuple | bool: config, file rules or None + """ if "imageioSettings" in context_data and context_data["imageioSettings"]: return context_data["imageioSettings"] project_name = context_data["projectName"] + folder_path = context_data["folderPath"] + task_name = context_data["task"] host_name = context_data["hostName"] - anatomy_data = context_data["anatomyData"] - project_settings_ = context_data["project_settings"] + anatomy = context_data["anatomy"] + template_data = context_data["anatomyData"] + project_settings = context_data["project_settings"] + folder_id = None + folder_entity = context_data.get("folderEntity") + if folder_entity: + folder_id = folder_entity["id"] - config_data = get_imageio_config( - project_name, host_name, - project_settings=project_settings_, - anatomy_data=anatomy_data + config_data = get_imageio_config_preset( + project_name, + folder_path, + task_name, + host_name, + anatomy=anatomy, + project_settings=project_settings, + template_data=template_data, + folder_id=folder_id, ) # caching invalid state, so it's not recalculated all the time file_rules = None if config_data: file_rules = get_imageio_file_rules( - project_name, host_name, - project_settings=project_settings_ + project_name, + host_name, + project_settings=project_settings ) # caching settings for future instance processing From 178e30d8e7fdba1012908f1f7574e824ab03055a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:52:31 +0200 Subject: [PATCH 599/727] fix call of '_get_host_config_data' --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 5793fd1143..906f9f96fa 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1070,7 +1070,7 @@ def get_imageio_config_preset( ) else: config_data = _get_host_config_data( - host_ocio_config["filepath"], template_data, env + host_ocio_config["filepath"], template_data ) if not config_data: From 571658b1290c69b9444ecaabc35247722dd15aca Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:53:12 +0200 Subject: [PATCH 600/727] make context optional --- client/ayon_core/pipeline/colorspace.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 906f9f96fa..a715651f4a 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -783,8 +783,8 @@ def get_imageio_config( get_current_context_template_data) anatomy_data = get_current_context_template_data() - task_name = anatomy_data["task"]["name"] - folder_path = anatomy_data["folder"]["path"] + task_name = anatomy_data.get("task", {}).get("name") + folder_path = anatomy_data.get("folder", {}).get("path") return get_imageio_config_preset( project_name, folder_path, @@ -1029,13 +1029,17 @@ def get_imageio_config_preset( if not project_entity: project_entity = ayon_api.get_project(project_name) - folder_entity = ayon_api.get_folder_by_path( - project_name, folder_path - ) - folder_id = folder_entity["id"] - task_entity = ayon_api.get_task_by_name( - project_name, folder_id, task_name - ) + folder_entity = task_entity = folder_id = None + if folder_path: + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path + ) + folder_id = folder_entity["id"] + + if folder_id and task_name: + task_entity = ayon_api.get_task_by_name( + project_name, folder_id, task_name + ) template_data = get_template_data( project_entity, folder_entity, From cd857753ae6f24bf066514ecaf26d32bed827217 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:54:00 +0200 Subject: [PATCH 601/727] 'config_data' are now required in other functions --- client/ayon_core/pipeline/colorspace.py | 86 ++++++++++++++----------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index a715651f4a..9e33b2e531 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -126,42 +126,48 @@ def get_ocio_config_script_path(): def get_colorspace_name_from_filepath( - filepath, host_name, project_name, - config_data=None, file_rules=None, + filepath, + host_name, + project_name, + config_data, + file_rules=None, project_settings=None, validate=True ): """Get colorspace name from filepath Args: - filepath (str): path string, file rule pattern is tested on it - host_name (str): host name - project_name (str): project name - config_data (Optional[dict]): config path and template in dict. - Defaults to None. - file_rules (Optional[dict]): file rule data from settings. - Defaults to None. - project_settings (Optional[dict]): project settings. Defaults to None. + filepath (str): Path string, file rule pattern is tested on it. + host_name (str): Host name. + project_name (str): Project name. + config_data (dict): Config path and template in dict. + file_rules (Optional[dict]): File rule data from settings. + project_settings (Optional[dict]): Project settings. validate (Optional[bool]): should resulting colorspace be validated - with config file? Defaults to True. + with config file? Defaults to True. Returns: - str: name of colorspace - """ - project_settings, config_data, file_rules = _get_context_settings( - host_name, project_name, - config_data=config_data, file_rules=file_rules, - project_settings=project_settings - ) + Union[str, None]: name of colorspace + """ if not config_data: # in case global or host color management is not enabled return None + if file_rules is None: + if project_settings is None: + project_settings = get_project_settings(project_name) + file_rules = get_imageio_file_rules( + project_name, host_name, project_settings + ) + # use ImageIO file rules colorspace_name = get_imageio_file_rules_colorspace_from_filepath( - filepath, host_name, project_name, - config_data=config_data, file_rules=file_rules, + filepath, + host_name, + project_name, + config_data=config_data, + file_rules=file_rules, project_settings=project_settings ) @@ -187,7 +193,8 @@ def get_colorspace_name_from_filepath( # validate matching colorspace with config if validate: validate_imageio_colorspace_in_config( - config_data["path"], colorspace_name) + config_data["path"], colorspace_name + ) return colorspace_name @@ -226,8 +233,11 @@ def _get_context_settings( def get_imageio_file_rules_colorspace_from_filepath( - filepath, host_name, project_name, - config_data=None, file_rules=None, + filepath, + host_name, + project_name, + config_data, + file_rules=None, project_settings=None ): """Get colorspace name from filepath @@ -235,28 +245,28 @@ def get_imageio_file_rules_colorspace_from_filepath( ImageIO Settings file rules are tested for matching rule. Args: - filepath (str): path string, file rule pattern is tested on it - host_name (str): host name - project_name (str): project name - config_data (Optional[dict]): config path and template in dict. - Defaults to None. - file_rules (Optional[dict]): file rule data from settings. - Defaults to None. - project_settings (Optional[dict]): project settings. Defaults to None. + filepath (str): Path string, file rule pattern is tested on it. + host_name (str): Host name. + project_name (str): Project name. + config_data (dict): Config path and template in dict. + file_rules (Optional[dict]): File rule data from settings. + project_settings (Optional[dict]): Project settings. Returns: - str: name of colorspace - """ - project_settings, config_data, file_rules = _get_context_settings( - host_name, project_name, - config_data=config_data, file_rules=file_rules, - project_settings=project_settings - ) + Union[str, None]: Name of colorspace. + """ if not config_data: # in case global or host color management is not enabled return None + if file_rules is None: + if project_settings is None: + project_settings = get_project_settings(project_name) + file_rules = get_imageio_file_rules( + project_name, host_name, project_settings + ) + # match file rule from path colorspace_name = None for file_rule in file_rules: From 5d6993d1112c7e24285f43b78ae68193abe29e7b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:54:18 +0200 Subject: [PATCH 602/727] removed unused '_get_context_settings' --- client/ayon_core/pipeline/colorspace.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 9e33b2e531..0e745b625f 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -210,28 +210,6 @@ def get_colorspace_from_filepath(*args, **kwargs): return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) -def _get_context_settings( - host_name, project_name, - config_data=None, file_rules=None, - project_settings=None -): - project_settings = project_settings or get_project_settings( - project_name - ) - - config_data = config_data or get_imageio_config( - project_name, host_name, project_settings) - - # in case host color management is not enabled - if not config_data: - return (None, None, None) - - file_rules = file_rules or get_imageio_file_rules( - project_name, host_name, project_settings) - - return project_settings, config_data, file_rules - - def get_imageio_file_rules_colorspace_from_filepath( filepath, host_name, From 1586b316c8ab700b1f9e42dc57e813454010356a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:54:47 +0200 Subject: [PATCH 603/727] implemented function for current context --- client/ayon_core/pipeline/colorspace.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 0e745b625f..e0fa613ae8 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -23,6 +23,7 @@ from ayon_core.pipeline.template_data import get_template_data from ayon_core.pipeline.load import get_representation_path_with_anatomy from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS +from .context_tools import get_current_context, get_current_host_name log = Logger.get_logger(__name__) @@ -1402,3 +1403,37 @@ def get_display_view_colorspace_subprocess(config_path, display, view): # return default view colorspace name with open(tmp_json_path, "r") as f: return json.load(f) + + +# --- Current context functions --- +def get_current_context_imageio_config_preset( + anatomy=None, + project_settings=None, + template_data=None, + env=None, +): + """Get ImageIO config preset for current context. + + Args: + anatomy (Optional[Anatomy]): Current project anatomy. + project_settings (Optional[dict[str, Any]]): Current project settings. + template_data (Optional[dict[str, Any]]): Prepared template data + for current context. + env (Optional[dict[str, str]]): Custom environment variable values. + + Returns: + dict: ImageIO config preset. + + """ + context = get_current_context() + host_name = get_current_host_name() + return get_imageio_config_preset( + context["project_name"], + context["folder_path"], + context["task_name"], + host_name, + anatomy=anatomy, + project_settings=project_settings, + template_data=template_data, + env=env, + ) From 3d1fa6471cbf8be68906a94aacea80bc90dc0a53 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 15:59:50 +0200 Subject: [PATCH 604/727] use 'get_current_context_imageio_config_preset' in host integrations --- client/ayon_core/hosts/hiero/api/lib.py | 5 +--- client/ayon_core/hosts/max/api/lib.py | 11 ++------ .../hosts/maya/plugins/load/load_image.py | 5 ++-- .../plugins/create/create_colorspace_look.py | 7 +----- .../publish/collect_explicit_colorspace.py | 25 +++++++++---------- 5 files changed, 18 insertions(+), 35 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py index aaf99546c7..456a68f125 100644 --- a/client/ayon_core/hosts/hiero/api/lib.py +++ b/client/ayon_core/hosts/hiero/api/lib.py @@ -1110,10 +1110,7 @@ def apply_colorspace_project(): ''' # backward compatibility layer # TODO: remove this after some time - config_data = get_imageio_config( - project_name=get_current_project_name(), - host_name="hiero" - ) + config_data = get_current_context_imageio_config_preset() if config_data: presets.update({ diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index d9a3af3336..4170a992a5 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -372,12 +372,8 @@ def reset_colorspace(): """ if int(get_max_version()) < 2024: return - project_name = get_current_project_name() - colorspace_mgr = rt.ColorPipelineMgr - project_settings = get_project_settings(project_name) - max_config_data = colorspace.get_imageio_config( - project_name, "max", project_settings) + max_config_data = colorspace.get_current_context_imageio_config_preset() if max_config_data: ocio_config_path = max_config_data["path"] colorspace_mgr = rt.ColorPipelineMgr @@ -392,10 +388,7 @@ def check_colorspace(): "because Max main window can't be found.") if int(get_max_version()) >= 2024: color_mgr = rt.ColorPipelineMgr - project_name = get_current_project_name() - project_settings = get_project_settings(project_name) - max_config_data = colorspace.get_imageio_config( - project_name, "max", project_settings) + max_config_data = colorspace.get_current_context_imageio_config_preset() if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"): if not is_headless(): from ayon_core.tools.utils import SimplePopup diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image.py b/client/ayon_core/hosts/maya/plugins/load/load_image.py index 5b0858ce70..171920f747 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py @@ -8,7 +8,7 @@ from ayon_core.pipeline import ( from ayon_core.pipeline.load.utils import get_representation_path_from_context from ayon_core.pipeline.colorspace import ( get_imageio_file_rules_colorspace_from_filepath, - get_imageio_config, + get_current_context_imageio_config_preset, get_imageio_file_rules ) from ayon_core.settings import get_project_settings @@ -270,8 +270,7 @@ class FileNodeLoader(load.LoaderPlugin): host_name = get_current_host_name() project_settings = get_project_settings(project_name) - config_data = get_imageio_config( - project_name, host_name, + config_data = get_current_context_imageio_config_preset( project_settings=project_settings ) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py index 4d865c1c5c..da05afe86b 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_colorspace_look.py @@ -156,14 +156,9 @@ This creator publishes color space look file (LUT). ] def apply_settings(self, project_settings): - host = self.create_context.host - host_name = host.name - project_name = host.get_current_project_name() - config_data = colorspace.get_imageio_config( - project_name, host_name, + config_data = colorspace.get_current_context_imageio_config_preset( project_settings=project_settings ) - if not config_data: self.enabled = False return diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py index 8e29a0048d..5fbb9a6f4c 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_explicit_colorspace.py @@ -1,10 +1,7 @@ import pyblish.api -from ayon_core.pipeline import ( - publish, - registered_host -) from ayon_core.lib import EnumDef from ayon_core.pipeline import colorspace +from ayon_core.pipeline import publish from ayon_core.pipeline.publish import KnownPublishError @@ -19,9 +16,10 @@ class CollectColorspace(pyblish.api.InstancePlugin, families = ["render", "plate", "reference", "image", "online"] enabled = False - colorspace_items = [ + default_colorspace_items = [ (None, "Don't override") ] + colorspace_items = list(default_colorspace_items) colorspace_attr_show = False config_items = None @@ -69,14 +67,13 @@ class CollectColorspace(pyblish.api.InstancePlugin, @classmethod def apply_settings(cls, project_settings): - host = registered_host() - host_name = host.name - project_name = host.get_current_project_name() - config_data = colorspace.get_imageio_config( - project_name, host_name, + config_data = colorspace.get_current_context_imageio_config_preset( project_settings=project_settings ) + enabled = False + colorspace_items = list(cls.default_colorspace_items) + config_items = None if config_data: filepath = config_data["path"] config_items = colorspace.get_ocio_config_colorspaces(filepath) @@ -85,9 +82,11 @@ class CollectColorspace(pyblish.api.InstancePlugin, include_aliases=True, include_roles=True ) - cls.config_items = config_items - cls.colorspace_items.extend(labeled_colorspaces) - cls.enabled = True + colorspace_items.extend(labeled_colorspaces) + + cls.config_items = config_items + cls.colorspace_items = colorspace_items + cls.enabled = enabled @classmethod def get_attribute_defs(cls): From f827dc2060488de74421cd7d0b8fc826a499975e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 16:55:59 +0200 Subject: [PATCH 605/727] use new function in pre launch hook --- client/ayon_core/hooks/pre_ocio_hook.py | 52 ++++++++++++++----------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 0817afec71..6c30b267bc 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -1,7 +1,7 @@ from ayon_applications import PreLaunchHook -from ayon_core.pipeline.colorspace import get_imageio_config -from ayon_core.pipeline.template_data import get_template_data_with_names +from ayon_core.pipeline.colorspace import get_imageio_config_preset +from ayon_core.pipeline.template_data import get_template_data class OCIOEnvHook(PreLaunchHook): @@ -26,32 +26,38 @@ class OCIOEnvHook(PreLaunchHook): def execute(self): """Hook entry method.""" - template_data = get_template_data_with_names( - project_name=self.data["project_name"], - folder_path=self.data["folder_path"], - task_name=self.data["task_name"], + folder_entity = self.data["folder_entity"] + + template_data = get_template_data( + self.data["project_entity"], + folder_entity=folder_entity, + task_entity=self.data["task_entity"], host_name=self.host_name, - settings=self.data["project_settings"] + settings=self.data["project_settings"], ) - config_data = get_imageio_config( - project_name=self.data["project_name"], - host_name=self.host_name, - project_settings=self.data["project_settings"], - anatomy_data=template_data, + config_data = get_imageio_config_preset( + self.data["project_name"], + self.data["folder_path"], + self.data["task_name"], + self.host_name, anatomy=self.data["anatomy"], + project_settings=self.data["project_settings"], + template_data=template_data, env=self.launch_context.env, + folder_id=folder_entity["id"], ) - if config_data: - ocio_path = config_data["path"] - - if self.host_name in ["nuke", "hiero"]: - ocio_path = ocio_path.replace("\\", "/") - - self.log.info( - f"Setting OCIO environment to config path: {ocio_path}") - - self.launch_context.env["OCIO"] = ocio_path - else: + if not config_data: self.log.debug("OCIO not set or enabled") + return + + ocio_path = config_data["path"] + + if self.host_name in ["nuke", "hiero"]: + ocio_path = ocio_path.replace("\\", "/") + + self.log.info( + f"Setting OCIO environment to config path: {ocio_path}") + + self.launch_context.env["OCIO"] = ocio_path From 5a43242bda200a79454fbef579e00156c29fd144 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 16:56:43 +0200 Subject: [PATCH 606/727] use 'get_current_context_imageio_config_preset' in nuke --- client/ayon_core/hosts/nuke/api/lib.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index e3505a16f2..0a4755c166 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -43,7 +43,9 @@ from ayon_core.pipeline import ( from ayon_core.pipeline.context_tools import ( get_current_context_custom_workfile_template ) -from ayon_core.pipeline.colorspace import get_imageio_config +from ayon_core.pipeline.colorspace import ( + get_current_context_imageio_config_preset +) from ayon_core.pipeline.workfile import BuildWorkfile from . import gizmo_menu from .constants import ASSIST @@ -1552,10 +1554,7 @@ class WorkfileSettings(object): imageio_host (dict): host colorspace configurations ''' - config_data = get_imageio_config( - project_name=get_current_project_name(), - host_name="nuke" - ) + config_data = get_current_context_imageio_config_preset() workfile_settings = imageio_host["workfile"] color_management = workfile_settings["color_management"] From 1568d40c98c07919dc90fdffc85f1c9a39af59c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 16:57:03 +0200 Subject: [PATCH 607/727] temp json fole wrapper is safe --- client/ayon_core/pipeline/colorspace.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index e0fa613ae8..e985bdfcf5 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -87,28 +87,25 @@ def deprecated(new_destination): def _make_temp_json_file(): """Wrapping function for json temp file """ + temporary_json_file = None try: # Store dumped json to temporary file - temporary_json_file = tempfile.NamedTemporaryFile( + with tempfile.NamedTemporaryFile( mode="w", suffix=".json", delete=False - ) - temporary_json_file.close() - temporary_json_filepath = temporary_json_file.name.replace( - "\\", "/" - ) + ) as tmpfile: + temporary_json_filepath = tmpfile.name.replace("\\", "/") yield temporary_json_filepath - except IOError as _error: + except IOError as exc: raise IOError( - "Unable to create temp json file: {}".format( - _error - ) + "Unable to create temp json file: {}".format(exc) ) finally: # Remove the temporary json - os.remove(temporary_json_filepath) + if temporary_json_file is not None: + os.remove(temporary_json_filepath) def get_ocio_config_script_path(): From c0d5e77177463ca075ee1fed2e9c61416dd6196d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 16:57:27 +0200 Subject: [PATCH 608/727] simplified 'get_ocio_config_script_path' --- client/ayon_core/pipeline/colorspace.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index e985bdfcf5..cbd63c851f 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -113,13 +113,12 @@ def get_ocio_config_script_path(): Returns: str: path string + """ - return os.path.normpath( - os.path.join( - AYON_CORE_ROOT, - "scripts", - "ocio_wrapper.py" - ) + return os.path.join( + os.path.normpath(AYON_CORE_ROOT), + "scripts", + "ocio_wrapper.py" ) From f828f5a767321ca37954bacf7db66378cef557f3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 6 May 2024 23:27:15 +0800 Subject: [PATCH 609/727] upgrade the patch version --- 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 500f609fc6..983749355e 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,6 +1,6 @@ name = "applications" title = "Applications" -version = "0.2.1" +version = "0.2.2" ayon_server_version = ">=1.0.7" ayon_launcher_version = ">=1.0.2" From 5c46ae9e62989778cfe39f0c127e8f3a0f35d832 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 6 May 2024 16:59:24 +0100 Subject: [PATCH 610/727] Fix deselect all function with context override --- client/ayon_core/hosts/blender/api/plugin.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/blender/api/plugin.py b/client/ayon_core/hosts/blender/api/plugin.py index 6c9bfb6569..4a13d16805 100644 --- a/client/ayon_core/hosts/blender/api/plugin.py +++ b/client/ayon_core/hosts/blender/api/plugin.py @@ -143,13 +143,19 @@ def deselect_all(): if obj.mode != 'OBJECT': modes.append((obj, obj.mode)) bpy.context.view_layer.objects.active = obj - bpy.ops.object.mode_set(mode='OBJECT') + context_override = create_blender_context(active=obj) + with bpy.context.temp_override(**context_override): + bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') + context_override = create_blender_context() + with bpy.context.temp_override(**context_override): + bpy.ops.object.select_all(action='DESELECT') for p in modes: bpy.context.view_layer.objects.active = p[0] - bpy.ops.object.mode_set(mode=p[1]) + context_override = create_blender_context(active=p[0]) + with bpy.context.temp_override(**context_override): + bpy.ops.object.mode_set(mode=p[1]) bpy.context.view_layer.objects.active = active From 0c84c32e15ce1732139afaef5533c8f16e8f0c78 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:00:09 +0200 Subject: [PATCH 611/727] pass config data to 'get_imageio_file_rules_colorspace_from_filepath' in nuke loader --- client/ayon_core/hosts/nuke/plugins/load/load_clip.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index df8f2ab018..1f707c25cf 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -9,7 +9,8 @@ from ayon_core.pipeline import ( get_representation_path, ) from ayon_core.pipeline.colorspace import ( - get_imageio_file_rules_colorspace_from_filepath + get_imageio_file_rules_colorspace_from_filepath, + get_current_context_imageio_config_preset, ) from ayon_core.hosts.nuke.api.lib import ( get_imageio_input_colorspace, @@ -547,9 +548,10 @@ class LoadClip(plugin.NukeLoader): f"Colorspace from representation colorspaceData: {colorspace}" ) + config_data = get_current_context_imageio_config_preset() # check if any filerules are not applicable new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa - filepath, "nuke", project_name + filepath, "nuke", project_name, config_data=config_data ) self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}") From 5599d773c7f49f4ecee35a6cca1a0c1186b92a2e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:02:21 +0200 Subject: [PATCH 612/727] use 'get_imageio_file_rules_colorspace_from_filepath' instead of 'get_imageio_colorspace_from_filepath' --- client/ayon_core/pipeline/colorspace.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index cbd63c851f..7b0d4c8491 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1328,12 +1328,15 @@ def set_colorspace_data_to_representation( filename = filename[0] # get matching colorspace from rules - colorspace = colorspace or get_imageio_colorspace_from_filepath( - filename, host_name, project_name, - config_data=config_data, - file_rules=file_rules, - project_settings=project_settings - ) + if colorspace is None: + colorspace = get_imageio_file_rules_colorspace_from_filepath( + filename, + host_name, + project_name, + config_data=config_data, + file_rules=file_rules, + project_settings=project_settings + ) # infuse data to representation if colorspace: From bf8b2fb3fafd6d9ee8cd7a868b7b44f26514147a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:02:41 +0200 Subject: [PATCH 613/727] 'get_display_view_colorspace_subprocess' is private --- client/ayon_core/pipeline/colorspace.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 7b0d4c8491..4034527282 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1364,15 +1364,16 @@ def get_display_view_colorspace_name(config_path, display, view): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - return get_display_view_colorspace_subprocess(config_path, - display, view) + return _get_display_view_colorspace_subprocess( + config_path, display, view + ) from ayon_core.scripts.ocio_wrapper import _get_display_view_colorspace_name # noqa return _get_display_view_colorspace_name(config_path, display, view) -def get_display_view_colorspace_subprocess(config_path, display, view): +def _get_display_view_colorspace_subprocess(config_path, display, view): """Returns the colorspace attribute of the (display, view) pair via subprocess. From 322e36128a8ed2497fa862e1ad150f834ecd73be Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:04:12 +0200 Subject: [PATCH 614/727] space sufficient cache logic --- client/ayon_core/pipeline/colorspace.py | 29 +++++++++++-------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 4034527282..77c830d44a 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -509,16 +509,15 @@ def get_ocio_config_colorspaces(config_path): if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess - CachedData.ocio_config_colorspaces[config_path] = \ - _get_wrapped_with_subprocess( - "config", "get_colorspace", in_path=config_path + config_colorspaces = _get_wrapped_with_subprocess( + "config", "get_colorspace", in_path=config_path ) else: # TODO: refactor this so it is not imported but part of this file from ayon_core.scripts.ocio_wrapper import _get_colorspace_data - CachedData.ocio_config_colorspaces[config_path] = \ - _get_colorspace_data(config_path) + config_colorspaces = _get_colorspace_data(config_path) + CachedData.ocio_config_colorspaces[config_path] = config_colorspaces return CachedData.ocio_config_colorspaces[config_path] @@ -1160,16 +1159,15 @@ def get_remapped_colorspace_to_native( Union[str, None]: native colorspace name defined in remapping or None """ - CachedData.remapping.setdefault(host_name, {}) - if CachedData.remapping[host_name].get("to_native") is None: + host_mapping = CachedData.remapping.setdefault(host_name, {}) + if "to_native" not in host_mapping: remapping_rules = imageio_host_settings["remapping"]["rules"] - CachedData.remapping[host_name]["to_native"] = { + host_mapping["to_native"] = { rule["ocio_name"]: rule["host_native_name"] for rule in remapping_rules } - return CachedData.remapping[host_name]["to_native"].get( - ocio_colorspace_name) + return host_mapping["to_native"].get(ocio_colorspace_name) def get_remapped_colorspace_from_native( @@ -1184,18 +1182,17 @@ def get_remapped_colorspace_from_native( Returns: Union[str, None]: Ocio colorspace name defined in remapping or None. - """ - CachedData.remapping.setdefault(host_name, {}) - if CachedData.remapping[host_name].get("from_native") is None: + """ + host_mapping = CachedData.remapping.setdefault(host_name, {}) + if "from_native" not in host_mapping: remapping_rules = imageio_host_settings["remapping"]["rules"] - CachedData.remapping[host_name]["from_native"] = { + host_mapping["from_native"] = { rule["host_native_name"]: rule["ocio_name"] for rule in remapping_rules } - return CachedData.remapping[host_name]["from_native"].get( - host_native_colorspace_name) + return host_mapping["from_native"].get(host_native_colorspace_name) def _get_imageio_settings(project_settings, host_name): From b679b06919bfbde31790f920d6fb95555849f675 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:04:42 +0200 Subject: [PATCH 615/727] formatting changes --- client/ayon_core/pipeline/colorspace.py | 48 +++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 77c830d44a..ed590758a3 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -334,10 +334,10 @@ def parse_colorspace_from_filepath( pattern = "|".join( # Allow to match spaces also as underscores because the # integrator replaces spaces with underscores in filenames - re.escape(colorspace) for colorspace in + re.escape(colorspace) # Sort by longest first so the regex matches longer matches # over smaller matches, e.g. matching 'Output - sRGB' over 'sRGB' - sorted(colorspaces, key=len, reverse=True) + for colorspace in sorted(colorspaces, key=len, reverse=True) ) return re.compile(pattern) @@ -529,11 +529,12 @@ def convert_colorspace_enumerator_item( """Convert colorspace enumerator item to dictionary Args: - colorspace_item (str): colorspace and family in couple - config_items (dict[str,dict]): colorspace data + colorspace_enum_item (str): Colorspace and family in couple. + config_items (dict[str,dict]): Colorspace data. Returns: dict: colorspace data + """ if "::" not in colorspace_enum_item: return None @@ -1103,13 +1104,13 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): """Get ImageIO File rules from project settings Args: - project_name (str): project name - host_name (str): host name - project_settings (dict, optional): project settings. - Defaults to None. + project_name (str): Project name. + host_name (str): Host name. + project_settings (Optional[dict]): Project settings. Returns: list[dict[str, Any]]: file rules data + """ project_settings = project_settings or get_project_settings(project_name) @@ -1151,7 +1152,7 @@ def get_remapped_colorspace_to_native( """Return native colorspace name. Args: - ocio_colorspace_name (str | None): ocio colorspace name + ocio_colorspace_name (str | None): OCIO colorspace name. host_name (str): Host name. imageio_host_settings (dict[str, Any]): ImageIO host settings. @@ -1199,12 +1200,12 @@ def _get_imageio_settings(project_settings, host_name): """Get ImageIO settings for global and host Args: - project_settings (dict): project settings. - Defaults to None. - host_name (str): host name + project_settings (dict[str, Any]): Project settings. + host_name (str): Host name. Returns: - tuple[dict, dict]: image io settings for global and host + tuple[dict, dict]: Image io settings for global and host. + """ # get image io from global and host_name imageio_global = project_settings["core"]["imageio"] @@ -1266,18 +1267,13 @@ def get_colorspace_settings_from_publish_context(context_data): def set_colorspace_data_to_representation( - representation, context_data, + representation, + context_data, colorspace=None, log=None ): """Sets colorspace data to representation. - Args: - representation (dict): publishing representation - context_data (publish.Context.data): publishing context data - colorspace (str, optional): colorspace name. Defaults to None. - log (logging.Logger, optional): logger instance. Defaults to None. - Example: ``` { @@ -1292,6 +1288,12 @@ def set_colorspace_data_to_representation( } ``` + Args: + representation (dict): publishing representation + context_data (publish.Context.data): publishing context data + colorspace (Optional[str]): Colorspace name. + log (Optional[logging.Logger]): logger instance. + """ log = log or Logger.get_logger(__name__) @@ -1355,9 +1357,9 @@ def get_display_view_colorspace_name(config_path, display, view): view (str): view name e.g. "sRGB" Returns: - view color space name (str) e.g. "Output - sRGB" - """ + str: View color space name. e.g. "Output - sRGB" + """ if not compatibility_check(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess @@ -1381,8 +1383,8 @@ def _get_display_view_colorspace_subprocess(config_path, display, view): Returns: view color space name (str) e.g. "Output - sRGB" - """ + """ with _make_temp_json_file() as tmp_json_path: # Prepare subprocess arguments args = [ From ff05fafb77e5901e34e2dbcb070435f5fc72cd96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 May 2024 18:04:52 +0200 Subject: [PATCH 616/727] mark 'get_imageio_config' as deprecated --- client/ayon_core/pipeline/colorspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index ed590758a3..e9da194984 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -735,6 +735,7 @@ def get_views_data_subprocess(config_path): ) +@deprecated("get_imageio_config_preset") def get_imageio_config( project_name, host_name, From bd1f7dce4a55407c3e200b84a64083957b9b3234 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 6 May 2024 23:52:18 +0200 Subject: [PATCH 617/727] Fix repair for instances with older publish attributes that mismatch settings --- .../publish/validate_alembic_options_defaults.py | 15 ++++++++++++--- 1 file changed, 12 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 0abb734c9b..11f4c313fa 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 @@ -100,11 +100,20 @@ class ValidateAlembicDefaultsPointcache( ) # Set the settings values on the create context then save to workfile. - attributes = cls._get_publish_attributes(instance) settings = cls._get_settings(instance.context) - create_publish_attributes = create_instance.data["publish_attributes"] + attributes = cls._get_publish_attributes(create_instance) for key in attributes: - create_publish_attributes[cls.plugin_name][key] = settings[key] + if key not in settings: + # This may occur if attributes have changed over time and an + # existing instance has older legacy attributes that do not + # match the current settings definition. + cls.log.warning( + "Publish attribute %s not found in Alembic Export " + "default settings. Ignoring repair for attribute.", + key + ) + continue + attributes[key] = settings[key] create_context.save_changes() From a3c481732fc705ff65b9504f55472ff411d4207c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 May 2024 12:49:21 +0200 Subject: [PATCH 618/727] Update proper exception to tame linter Generic exception will be most likely JSON broken response, which is caught in ValueError in ws_stub --- .../hosts/photoshop/plugins/create/create_image.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py index 97543e96de..e18644d038 100644 --- a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py @@ -35,7 +35,10 @@ class ImageCreator(Creator): create_empty_group = False stub = api.stub() # only after PS is up - top_level_selected_items = stub.get_selected_layers() + try: + top_level_selected_items = stub.get_selected_layers() + except ValueError: + raise CreatorError("Cannot group locked Background layer!") if pre_create_data.get("use_selection"): only_single_item_selected = len(top_level_selected_items) == 1 if ( @@ -51,10 +54,8 @@ class ImageCreator(Creator): groups_to_create.append(group) else: stub.select_layers(stub.get_layers()) - try: - group = stub.group_selected_layers(product_name_from_ui) - except: # noqa E722 - raise CreatorError("Cannot group locked Background layer!") + group = stub.group_selected_layers(product_name_from_ui) + groups_to_create.append(group) # create empty group if nothing selected From f1afd1653e9f4afd8f9f2ca1564684c7f8d9a8cb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 May 2024 12:55:18 +0200 Subject: [PATCH 619/727] Fix exception even for different use case --- .../photoshop/plugins/create/create_image.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py index e18644d038..a44c3490c6 100644 --- a/client/ayon_core/hosts/photoshop/plugins/create/create_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/create/create_image.py @@ -35,11 +35,12 @@ class ImageCreator(Creator): create_empty_group = False stub = api.stub() # only after PS is up - try: - top_level_selected_items = stub.get_selected_layers() - except ValueError: - raise CreatorError("Cannot group locked Background layer!") if pre_create_data.get("use_selection"): + try: + top_level_selected_items = stub.get_selected_layers() + except ValueError: + raise CreatorError("Cannot group locked Background layer!") + only_single_item_selected = len(top_level_selected_items) == 1 if ( only_single_item_selected or @@ -53,8 +54,11 @@ class ImageCreator(Creator): group = stub.group_selected_layers(product_name_from_ui) groups_to_create.append(group) else: - stub.select_layers(stub.get_layers()) - group = stub.group_selected_layers(product_name_from_ui) + try: + stub.select_layers(stub.get_layers()) + group = stub.group_selected_layers(product_name_from_ui) + except ValueError: + raise CreatorError("Cannot group locked Background layer!") groups_to_create.append(group) From 2facf91bcb4a5ea5812dc93b3b2c026978a7a7f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 May 2024 13:51:36 +0200 Subject: [PATCH 620/727] AY-4801-Added conversion of resources Added similar configuration as for ExtractReview to control possible conversion from .mov to target format (.mp4) --- .../plugins/publish/extract_editorial_pckg.py | 151 ++++++++++++++++-- .../server/settings/publish_plugins.py | 89 ++++++++++- 2 files changed, 230 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index dc8163e1ff..02f953d579 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -1,16 +1,20 @@ +import copy import os.path +import subprocess + import opentimelineio import pyblish.api +from ayon_core.lib import filter_profiles, get_ffmpeg_tool_args, run_subprocess from ayon_core.pipeline import publish -class ExtractEditorialPackage(publish.Extractor): +class ExtractEditorialPckgConversion(publish.Extractor): """Replaces movie paths in otio file with publish rootless - Prepares movie resources for integration. - TODO introduce conversion to .mp4 + Prepares movie resources for integration (adds them to `transfers`). + Converts .mov files according to output definition. """ label = "Extract Editorial Package" @@ -35,13 +39,22 @@ class ExtractEditorialPackage(publish.Extractor): instance.data["representations"].append(editorial_pckg_repre) - publish_path = self._get_published_path(instance) - publish_folder = os.path.dirname(publish_path) - publish_resource_folder = os.path.join(publish_folder, "resources") - + publish_resource_folder = self._get_publish_resource_folder(instance) resource_paths = editorial_pckg_data["resource_paths"] transfers = self._get_transfers(resource_paths, publish_resource_folder) + + project_settings = instance.context.data["project_settings"] + profiles = (project_settings["traypublisher"] + ["publish"] + ["ExtractEditorialPckgConversion"] + .get("profiles")) + output_def = None + if profiles: + output_def = self._get_output_definition(instance, profiles) + if output_def: + transfers = self._convert_resources(output_def, transfers) + if not "transfers" in instance.data: instance.data["transfers"] = [] instance.data["transfers"] = transfers @@ -57,6 +70,36 @@ class ExtractEditorialPackage(publish.Extractor): self.log.info("Added Editorial Package representation: {}".format( editorial_pckg_repre)) + def _get_publish_resource_folder(self, instance): + """Calculates publish folder and create it.""" + publish_path = self._get_published_path(instance) + publish_folder = os.path.dirname(publish_path) + publish_resource_folder = os.path.join(publish_folder, "resources") + + if not os.path.exists(publish_resource_folder): + os.makedirs(publish_resource_folder, exist_ok=True) + return publish_resource_folder + + def _get_output_definition(self, instance, profiles): + """Return appropriate profile by context information.""" + product_type = instance.data["productType"] + product_name = instance.data["productName"] + task_entity = instance.data["taskEntity"] or {} + task_name = task_entity.get("name") + task_type = task_entity.get("taskType") + filtering_criteria = { + "product_types": product_type, + "product_names": product_name, + "task_names": task_name, + "task_types": task_type, + } + profile = filter_profiles( + profiles, + filtering_criteria, + logger=self.log + ) + return profile + def _get_resource_path_mapping(self, instance, transfers): """Returns dict of {source_mov_path: rootless_published_path}.""" replace_paths = {} @@ -68,7 +111,7 @@ class ExtractEditorialPackage(publish.Extractor): return replace_paths def _get_transfers(self, resource_paths, publish_resource_folder): - """Returns list of tuples (source, destination) movie paths.""" + """Returns list of tuples (source, destination) with movie paths.""" transfers = [] for res_path in resource_paths: res_basename = os.path.basename(res_path) @@ -77,7 +120,7 @@ class ExtractEditorialPackage(publish.Extractor): return transfers def _replace_target_urls(self, otio_data, replace_paths): - """Replace original movie paths with published rootles ones.""" + """Replace original movie paths with published rootless ones.""" for track in otio_data.tracks: for clip in track: # Check if the clip has a media reference @@ -120,3 +163,93 @@ class ExtractEditorialPackage(publish.Extractor): template = anatomy.get_template_item("publish", "default", "path") template_filled = template.format_strict(template_data) return os.path.normpath(template_filled) + + def _convert_resources(self, output_def, transfers): + """Converts all resource files to configured format.""" + outputs = output_def["outputs"] + if not outputs: + self.log.warning("No output configured in " + "ayon+settings://traypublisher/publish/ExtractEditorialPckgConversion/profiles/0/outputs") # noqa + return transfers + + final_transfers = [] + # most likely only single output is expected + for output in outputs: + out_extension = output["ext"] + out_def_ffmpeg_args = output["ffmpeg_args"] + ffmpeg_input_args = [ + value.strip() + for value in out_def_ffmpeg_args["input"] + if value.strip() + ] + ffmpeg_video_filters = [ + value.strip() + for value in out_def_ffmpeg_args["video_filters"] + if value.strip() + ] + ffmpeg_audio_filters = [ + value.strip() + for value in out_def_ffmpeg_args["audio_filters"] + if value.strip() + ] + ffmpeg_output_args = [ + value.strip() + for value in out_def_ffmpeg_args["output"] + if value.strip() + ] + ffmpeg_input_args = self._split_ffmpeg_args(ffmpeg_input_args) + + generic_args = [ + subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg")) + ] + generic_args.extend(ffmpeg_input_args) + if ffmpeg_video_filters: + generic_args.append("-filter:v") + generic_args.append( + "\"{}\"".format(",".join(ffmpeg_video_filters))) + + if ffmpeg_audio_filters: + generic_args.append("-filter:a") + generic_args.append( + "\"{}\"".format(",".join(ffmpeg_audio_filters))) + + for source, destination in transfers: + base_name = os.path.basename(destination) + file_name, ext = os.path.splitext(base_name) + dest_path = os.path.join(os.path.dirname(destination), + f"{file_name}.{out_extension}") + final_transfers.append((source, dest_path)) + + all_args = copy.deepcopy(generic_args) + all_args.append(f"-i {source}") + all_args.extend(ffmpeg_output_args) # order matters + all_args.append(f"{dest_path}") + subprcs_cmd = " ".join(all_args) + + # run subprocess + self.log.debug("Executing: {}".format(subprcs_cmd)) + run_subprocess(subprcs_cmd, shell=True, logger=self.log) + return final_transfers + + def _split_ffmpeg_args(self, in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args + diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py index f413c86227..9869f54620 100644 --- a/server_addon/traypublisher/server/settings/publish_plugins.py +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -1,4 +1,11 @@ -from ayon_server.settings import BaseSettingsModel, SettingsField +from pydantic import validator + +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, + ensure_unique_names +) class ValidatePluginModel(BaseSettingsModel): @@ -14,6 +21,74 @@ class ValidateFrameRangeModel(ValidatePluginModel): 'my_asset_to_publish.mov')""" +class ExtractEditorialPckgFFmpegModel(BaseSettingsModel): + video_filters: list[str] = SettingsField( + default_factory=list, + title="Video filters" + ) + audio_filters: list[str] = SettingsField( + default_factory=list, + title="Audio filters" + ) + input: list[str] = SettingsField( + default_factory=list, + title="Input arguments" + ) + output: list[str] = SettingsField( + default_factory=list, + title="Output arguments" + ) + + +class ExtractEditorialPckgOutputDefModel(BaseSettingsModel): + """Set extension and ffmpeg arguments. See `ExtractReview` for example.""" + _layout = "expanded" + name: str = SettingsField("", title="Name") + ext: str = SettingsField("", title="Output extension") + + ffmpeg_args: ExtractEditorialPckgFFmpegModel = SettingsField( + default_factory=ExtractEditorialPckgFFmpegModel, + title="FFmpeg arguments" + ) + + +class ExtractEditorialPckgProfileModel(BaseSettingsModel): + product_types: list[str] = SettingsField( + default_factory=list, + title="Product types" + ) + task_types: list[str] = SettingsField( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = SettingsField( + default_factory=list, + title="Task names" + ) + product_names: list[str] = SettingsField( + default_factory=list, + title="Product names" + ) + outputs: list[ExtractEditorialPckgOutputDefModel] = SettingsField( + default_factory=list, + title="Output Definitions", + ) + + @validator("outputs") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ExtractEditorialPckgConversionModel(BaseSettingsModel): + """Conversion of input movie files into expected format.""" + enabled: bool = SettingsField(True) + profiles: list[ExtractEditorialPckgProfileModel] = SettingsField( + default_factory=list, title="Profiles" + ) + + class TrayPublisherPublishPlugins(BaseSettingsModel): CollectFrameDataFromAssetEntity: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, @@ -28,6 +103,13 @@ class TrayPublisherPublishPlugins(BaseSettingsModel): default_factory=ValidatePluginModel, ) + ExtractEditorialPckgConversion: ExtractEditorialPckgConversionModel = ( + SettingsField( + default_factory=ExtractEditorialPckgConversionModel, + title="Extract Editorial Package Conversion" + ) + ) + DEFAULT_PUBLISH_PLUGINS = { "CollectFrameDataFromAssetEntity": { @@ -44,5 +126,10 @@ DEFAULT_PUBLISH_PLUGINS = { "enabled": True, "optional": True, "active": True + }, + "ExtractEditorialPckgConversion": { + "enabled": True, + "optional": True, + "active": True } } From c3910256b117487bff677ff81ef7b4c2c67a07a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 May 2024 14:40:45 +0200 Subject: [PATCH 621/727] fix circular import --- client/ayon_core/pipeline/colorspace.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index e9da194984..705d1570b0 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -23,7 +23,6 @@ from ayon_core.pipeline.template_data import get_template_data from ayon_core.pipeline.load import get_representation_path_with_anatomy from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS -from .context_tools import get_current_context, get_current_host_name log = Logger.get_logger(__name__) @@ -765,8 +764,7 @@ def get_imageio_config( """ if not anatomy_data: - from ayon_core.pipeline.context_tools import ( - get_current_context_template_data) + from .context_tools import get_current_context_template_data anatomy_data = get_current_context_template_data() task_name = anatomy_data.get("task", {}).get("name") @@ -1425,6 +1423,8 @@ def get_current_context_imageio_config_preset( dict: ImageIO config preset. """ + from .context_tools import get_current_context, get_current_host_name + context = get_current_context() host_name = get_current_host_name() return get_imageio_config_preset( From 393897f2a9b2f4af3533ba2f6f00716d2de151d2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 May 2024 18:08:55 +0200 Subject: [PATCH 622/727] don't change controller project on close --- client/ayon_core/tools/loader/ui/window.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index 3a6f4679fa..8529a53b06 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -335,9 +335,7 @@ class LoaderWindow(QtWidgets.QWidget): def closeEvent(self, event): super(LoaderWindow, self).closeEvent(event) - # Deselect project so current context will be selected - # on next 'showEvent' - self._controller.set_selected_project(None) + self._reset_on_show = True def keyPressEvent(self, event): From 4d502a55481adbfdbd5fe06aeefe2f7ad14d381e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 May 2024 21:42:04 +0800 Subject: [PATCH 623/727] add reset max file and clear undo buffer in the callback of starting new scene --- client/ayon_core/hosts/max/api/lib.py | 3 --- client/ayon_core/hosts/max/api/pipeline.py | 11 +++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index d9a3af3336..0e3abe25ec 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -6,12 +6,9 @@ import json from typing import Any, Dict, Union import six -import ayon_api from ayon_core.pipeline import ( get_current_project_name, - get_current_folder_path, - get_current_task_name, colorspace ) from ayon_core.settings import get_project_settings diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index dc13f47795..c6298bf590 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -52,11 +52,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): self._has_been_setup = True - def context_setting(): - return lib.set_context_setting() - rt.callbacks.addScript(rt.Name('systemPostNew'), - context_setting) + on_post_open) rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) @@ -163,6 +160,12 @@ def ls() -> list: yield lib.read(container) +def on_post_open(): + lib.set_context_setting() + rt.resetMaxFile(rt.Name("noPrompt")) + rt.clearUndoBuffer() + + def containerise(name: str, nodes: list, context, namespace=None, loader=None, suffix="_CON"): data = { From 7dadac74ac23db64d89512e792e7725d403a84f7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 May 2024 21:50:43 +0800 Subject: [PATCH 624/727] restore unncessary change --- client/ayon_core/hosts/max/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 0e3abe25ec..d9a3af3336 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -6,9 +6,12 @@ import json from typing import Any, Dict, Union import six +import ayon_api from ayon_core.pipeline import ( get_current_project_name, + get_current_folder_path, + get_current_task_name, colorspace ) from ayon_core.settings import get_project_settings From d25e8f508eeef612f99cca12976774fd3e401c5f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 May 2024 22:21:07 +0800 Subject: [PATCH 625/727] use on_new as name of the function --- client/ayon_core/hosts/max/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index c6298bf590..776565bade 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -52,8 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): self._has_been_setup = True - rt.callbacks.addScript(rt.Name('systemPostNew'), - on_post_open) + rt.callbacks.addScript(rt.Name('systemPostNew'), on_new) rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) @@ -160,7 +159,7 @@ def ls() -> list: yield lib.read(container) -def on_post_open(): +def on_new(): lib.set_context_setting() rt.resetMaxFile(rt.Name("noPrompt")) rt.clearUndoBuffer() From 36cbdcfde77580e86a512b292603a5f810c8f77d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 May 2024 22:56:26 +0800 Subject: [PATCH 626/727] only reset max file and clear undo buffer when there is unsaved change before starting new scene --- client/ayon_core/hosts/max/api/pipeline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index 776565bade..4782159ef8 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -161,8 +161,9 @@ def ls() -> list: def on_new(): lib.set_context_setting() - rt.resetMaxFile(rt.Name("noPrompt")) - rt.clearUndoBuffer() + if rt.checkForSave(): + rt.resetMaxFile(rt.Name("noPrompt")) + rt.clearUndoBuffer() def containerise(name: str, nodes: list, context, From 46ed96cad8ac8fc34804bf52232fa302e1c39071 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 8 May 2024 23:10:44 +0800 Subject: [PATCH 627/727] add redraw views for new scene --- client/ayon_core/hosts/max/api/pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index 4782159ef8..d9cfc3407f 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -164,6 +164,7 @@ def on_new(): if rt.checkForSave(): rt.resetMaxFile(rt.Name("noPrompt")) rt.clearUndoBuffer() + rt.redrawViews() def containerise(name: str, nodes: list, context, From 8b9a45e7152bb5c83d3eafaa11c466742ea1df2b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 9 May 2024 15:20:56 +0800 Subject: [PATCH 628/727] color channel from baking write node should be aligning with that from the write node created from creator --- client/ayon_core/hosts/nuke/api/plugin.py | 3 +++ .../ayon_core/hosts/nuke/plugins/publish/collect_writes.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/client/ayon_core/hosts/nuke/api/plugin.py index fb56dec833..02f41ff865 100644 --- a/client/ayon_core/hosts/nuke/api/plugin.py +++ b/client/ayon_core/hosts/nuke/api/plugin.py @@ -778,6 +778,7 @@ class ExporterReviewMov(ExporterReview): # deal with now lut defined in viewer lut self.viewer_lut_raw = klass.viewer_lut_raw self.write_colorspace = instance.data["colorspace"] + self.color_channels = instance.data["color_channels"] self.name = name or "baked" self.ext = ext or "mov" @@ -947,6 +948,8 @@ class ExporterReviewMov(ExporterReview): self.log.debug("Path: {}".format(self.path)) write_node["file"].setValue(str(self.path)) write_node["file_type"].setValue(str(self.ext)) + write_node["channels"].setValue(str(self.color_channels)) + # Knobs `meta_codec` and `mov64_codec` are not available on centos. # TODO shouldn't this come from settings on outputs? try: diff --git a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py b/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py index 745351dc49..27525bcad1 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/collect_writes.py @@ -153,6 +153,9 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, # Determine defined file type ext = write_node["file_type"].value() + # determine defined channel type + color_channels = write_node["channels"].value() + # get frame range data handle_start = instance.context.data["handleStart"] handle_end = instance.context.data["handleEnd"] @@ -172,7 +175,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, "path": write_file_path, "outputDir": output_dir, "ext": ext, - "colorspace": colorspace + "colorspace": colorspace, + "color_channels": color_channels }) if product_type == "render": From ab1b49b988183d0de95db27a249e89cfb59ed364 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Thu, 29 Feb 2024 12:23:57 +1300 Subject: [PATCH 629/727] enhancement/AY-1456_double_click_at_instance_switch_to_publish_tab --- .../tools/publisher/widgets/card_view_widgets.py | 11 +++++++++++ .../tools/publisher/widgets/list_view_widgets.py | 6 ++++++ .../tools/publisher/widgets/overview_widget.py | 10 ++++++++++ 3 files changed, 27 insertions(+) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 47c5399cf7..7d178e98e5 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -534,6 +534,8 @@ class InstanceCardView(AbstractInstanceView): Wrapper of all widgets in card view. """ + double_clicked = QtCore.Signal() + def __init__(self, controller, parent): super(InstanceCardView, self).__init__(parent) @@ -578,6 +580,9 @@ class InstanceCardView(AbstractInstanceView): self.sizePolicy().verticalPolicy() ) + def mouseDoubleClickEvent(self, event): + self.double_clicked.emit() + def sizeHint(self): """Modify sizeHint based on visibility of scroll bars.""" # Calculate width hint by content widget and vertical scroll bar @@ -715,6 +720,7 @@ class InstanceCardView(AbstractInstanceView): ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) + # group_widget.double_clicked.connect(self._on_widget_double_clicked) self._content_layout.insertWidget(widget_idx, group_widget) self._widgets_by_group[group_name] = group_widget @@ -825,6 +831,11 @@ class InstanceCardView(AbstractInstanceView): self.selection_changed.emit() + # def _on_widget_double_clicked(self): + # print("_on_widget_double_clicked") + # widgets = self._get_selected_widgets() + # print(widgets) + def _select_item_clear(self, instance_id, group_name, new_widget): """Select specific item by instance id and clear previous selection. diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 3322a73be6..8dfabed8e9 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -425,6 +425,9 @@ class InstanceListView(AbstractInstanceView): This is public access to and from list view. """ + + double_clicked = QtCore.Signal() + def __init__(self, controller, parent): super(InstanceListView, self).__init__(parent) @@ -474,6 +477,9 @@ class InstanceListView(AbstractInstanceView): self._active_toggle_enabled = True + def mouseDoubleClickEvent(self, event): + self.double_clicked.emit() + def _on_expand(self, index): self._update_widget_expand_state(index, True) diff --git a/client/ayon_core/tools/publisher/widgets/overview_widget.py b/client/ayon_core/tools/publisher/widgets/overview_widget.py index dd82185830..82e4153681 100644 --- a/client/ayon_core/tools/publisher/widgets/overview_widget.py +++ b/client/ayon_core/tools/publisher/widgets/overview_widget.py @@ -113,9 +113,15 @@ class OverviewWidget(QtWidgets.QFrame): product_list_view.selection_changed.connect( self._on_product_change ) + product_list_view.double_clicked.connect( + self._on_double_clicked + ) product_view_cards.selection_changed.connect( self._on_product_change ) + product_view_cards.double_clicked.connect( + self._on_double_clicked + ) # Active instances changed product_list_view.active_changed.connect( self._on_active_changed @@ -293,6 +299,10 @@ class OverviewWidget(QtWidgets.QFrame): instances, context_selected, convertor_identifiers ) + def _on_double_clicked(self): + from ayon_core.tools.utils import host_tools + host_tools.show_publisher(tab="publish") + def _on_active_changed(self): if self._refreshing_instances: return From 427d36c5ac796fdb6993ae34483e8eebf06ff874 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Thu, 29 Feb 2024 12:25:02 +1300 Subject: [PATCH 630/727] enhancement/AY-1456_double_click_at_instance_switch_to_publish_tab --- .../ayon_core/tools/publisher/widgets/card_view_widgets.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 7d178e98e5..d1d062c18f 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -720,7 +720,6 @@ class InstanceCardView(AbstractInstanceView): ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) - # group_widget.double_clicked.connect(self._on_widget_double_clicked) self._content_layout.insertWidget(widget_idx, group_widget) self._widgets_by_group[group_name] = group_widget @@ -831,11 +830,6 @@ class InstanceCardView(AbstractInstanceView): self.selection_changed.emit() - # def _on_widget_double_clicked(self): - # print("_on_widget_double_clicked") - # widgets = self._get_selected_widgets() - # print(widgets) - def _select_item_clear(self, instance_id, group_name, new_widget): """Select specific item by instance id and clear previous selection. From 58998d970c41d1a17275722c3eb97df7dd645325 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Thu, 29 Feb 2024 15:25:43 +1300 Subject: [PATCH 631/727] enhancement/AY-1456_double_click_at_instance_switch_to_publish_tab --- client/ayon_core/tools/publisher/widgets/list_view_widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 8dfabed8e9..90f5f3fab9 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -457,6 +457,7 @@ class InstanceListView(AbstractInstanceView): instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) instance_view.toggle_requested.connect(self._on_toggle_request) + instance_view.doubleClicked.connect(self._on_double_clicked) self._group_items = {} self._group_widgets = {} @@ -477,7 +478,7 @@ class InstanceListView(AbstractInstanceView): self._active_toggle_enabled = True - def mouseDoubleClickEvent(self, event): + def _on_double_clicked(self, event): self.double_clicked.emit() def _on_expand(self, index): From 2f326c1467ca26dbedf76a56784d2a37b9dbf2b8 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Tue, 5 Mar 2024 09:57:08 +1300 Subject: [PATCH 632/727] enhancement/AY-1456_double_click_at_instance_switch_to_publish_tab --- .../ayon_core/tools/publisher/widgets/list_view_widgets.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 90f5f3fab9..7c4c07baea 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -457,7 +457,7 @@ class InstanceListView(AbstractInstanceView): instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) instance_view.toggle_requested.connect(self._on_toggle_request) - instance_view.doubleClicked.connect(self._on_double_clicked) + instance_view.doubleClicked.connect(self.double_clicked) self._group_items = {} self._group_widgets = {} @@ -478,9 +478,6 @@ class InstanceListView(AbstractInstanceView): self._active_toggle_enabled = True - def _on_double_clicked(self, event): - self.double_clicked.emit() - def _on_expand(self, index): self._update_widget_expand_state(index, True) From dd776b728ba510283ea17b96dc6b65fa65a8809e Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Tue, 5 Mar 2024 10:02:23 +1300 Subject: [PATCH 633/727] enhancement/AY-1456_double_click_at_instance_switch_to_publish_tab --- .../ayon_core/tools/publisher/widgets/overview_widget.py | 9 +++------ client/ayon_core/tools/publisher/window.py | 3 +++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/overview_widget.py b/client/ayon_core/tools/publisher/widgets/overview_widget.py index 82e4153681..cedf52ae01 100644 --- a/client/ayon_core/tools/publisher/widgets/overview_widget.py +++ b/client/ayon_core/tools/publisher/widgets/overview_widget.py @@ -18,6 +18,7 @@ class OverviewWidget(QtWidgets.QFrame): instance_context_changed = QtCore.Signal() create_requested = QtCore.Signal() convert_requested = QtCore.Signal() + publish_tab_requested = QtCore.Signal() anim_end_value = 200 anim_duration = 200 @@ -114,13 +115,13 @@ class OverviewWidget(QtWidgets.QFrame): self._on_product_change ) product_list_view.double_clicked.connect( - self._on_double_clicked + self.publish_tab_requested ) product_view_cards.selection_changed.connect( self._on_product_change ) product_view_cards.double_clicked.connect( - self._on_double_clicked + self.publish_tab_requested ) # Active instances changed product_list_view.active_changed.connect( @@ -299,10 +300,6 @@ class OverviewWidget(QtWidgets.QFrame): instances, context_selected, convertor_identifiers ) - def _on_double_clicked(self): - from ayon_core.tools.utils import host_tools - host_tools.show_publisher(tab="publish") - def _on_active_changed(self): if self._refreshing_instances: return diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 123864ff6c..1b13ced317 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -258,6 +258,9 @@ class PublisherWindow(QtWidgets.QDialog): overview_widget.convert_requested.connect( self._on_convert_requested ) + overview_widget.publish_tab_requested.connect( + self._go_to_publish_tab + ) save_btn.clicked.connect(self._on_save_clicked) reset_btn.clicked.connect(self._on_reset_clicked) From b8f0d590f16cf0d7a6e4add5daef27cfb5140a1e Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 11 Mar 2024 10:30:42 +1300 Subject: [PATCH 634/727] right clicking tree view doen't switch tab --- .../tools/publisher/widgets/card_view_widgets.py | 3 ++- .../tools/publisher/widgets/list_view_widgets.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index d1d062c18f..28ed237fe6 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -581,7 +581,8 @@ class InstanceCardView(AbstractInstanceView): ) def mouseDoubleClickEvent(self, event): - self.double_clicked.emit() + if event.button() == QtCore.Qt.LeftButton: + self.double_clicked.emit() def sizeHint(self): """Modify sizeHint based on visibility of scroll bars.""" diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 7c4c07baea..eb5f41be4a 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -379,7 +379,7 @@ class InstanceTreeView(QtWidgets.QTreeView): "double click" as 2x "single click". """ if event.button() != QtCore.Qt.LeftButton: - return + return False pressed_group_index = None pos_index = self.indexAt(event.pos()) @@ -388,13 +388,17 @@ class InstanceTreeView(QtWidgets.QTreeView): self._pressed_group_index = pressed_group_index + return True + def mousePressEvent(self, event): - self._mouse_press(event) - super(InstanceTreeView, self).mousePressEvent(event) + handled = self._mouse_press(event) + if handled: + super(InstanceTreeView, self).mousePressEvent(event) def mouseDoubleClickEvent(self, event): - self._mouse_press(event) - super(InstanceTreeView, self).mouseDoubleClickEvent(event) + handled = self._mouse_press(event) + if handled: + super(InstanceTreeView, self).mouseDoubleClickEvent(event) def _mouse_release(self, event, pressed_index): if event.button() != QtCore.Qt.LeftButton: From 7c028a752cd012e9914c1d3a29829869708c032c Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 11 Mar 2024 10:52:27 +1300 Subject: [PATCH 635/727] clicking in empty area in tree view should switch tab --- client/ayon_core/tools/publisher/widgets/list_view_widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index eb5f41be4a..f142faff83 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -317,6 +317,7 @@ class InstanceListGroupWidget(QtWidgets.QFrame): class InstanceTreeView(QtWidgets.QTreeView): """View showing instances and their groups.""" toggle_requested = QtCore.Signal(int) + double_clicked = QtCore.Signal() def __init__(self, *args, **kwargs): super(InstanceTreeView, self).__init__(*args, **kwargs) @@ -396,6 +397,7 @@ class InstanceTreeView(QtWidgets.QTreeView): super(InstanceTreeView, self).mousePressEvent(event) def mouseDoubleClickEvent(self, event): + self.double_clicked.emit() handled = self._mouse_press(event) if handled: super(InstanceTreeView, self).mouseDoubleClickEvent(event) @@ -461,7 +463,7 @@ class InstanceListView(AbstractInstanceView): instance_view.collapsed.connect(self._on_collapse) instance_view.expanded.connect(self._on_expand) instance_view.toggle_requested.connect(self._on_toggle_request) - instance_view.doubleClicked.connect(self.double_clicked) + instance_view.double_clicked.connect(self.double_clicked) self._group_items = {} self._group_widgets = {} From 55256d59a3ad315974ce3a6aa9f70d00e26561af Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 11 Mar 2024 10:56:15 +1300 Subject: [PATCH 636/727] clicking in empty area in tree view should switch tab --- client/ayon_core/tools/publisher/widgets/list_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index f142faff83..6a7335bd74 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -397,9 +397,9 @@ class InstanceTreeView(QtWidgets.QTreeView): super(InstanceTreeView, self).mousePressEvent(event) def mouseDoubleClickEvent(self, event): - self.double_clicked.emit() handled = self._mouse_press(event) if handled: + self.double_clicked.emit() super(InstanceTreeView, self).mouseDoubleClickEvent(event) def _mouse_release(self, event, pressed_index): From e2849b83e0fbb3d90edf637b3a3161172facecf8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 10:58:24 +0200 Subject: [PATCH 637/727] card widgets can tell if they should trigger double click --- .../publisher/widgets/card_view_widgets.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 28ed237fe6..4e34f9b58c 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -52,6 +52,7 @@ class SelectionTypes: class BaseGroupWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str, str) removed_selected = QtCore.Signal() + double_clicked = QtCore.Signal() def __init__(self, group_name, parent): super(BaseGroupWidget, self).__init__(parent) @@ -192,6 +193,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget): else: widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) + widget.double_clicked(self.double_clicked) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 @@ -254,6 +256,7 @@ class InstanceGroupWidget(BaseGroupWidget): ) widget.selected.connect(self._on_widget_selection) widget.active_changed.connect(self._on_active_changed) + widget.double_clicked.connect(self.double_clicked) self._widgets_by_id[instance.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 @@ -271,6 +274,7 @@ class CardWidget(BaseClickableFrame): # Group identifier of card # - this must be set because if send when mouse is released with card id _group_identifier = None + double_clicked = QtCore.Signal() def __init__(self, parent): super(CardWidget, self).__init__(parent) @@ -279,6 +283,11 @@ class CardWidget(BaseClickableFrame): self._selected = False self._id = None + def mouseDoubleClickEvent(self, event): + super(CardWidget, self).mouseDoubleClickEvent(event) + if self._is_valid_double_click(event): + self.double_clicked.emit() + @property def id(self): """Id of card.""" @@ -312,6 +321,9 @@ class CardWidget(BaseClickableFrame): self.selected.emit(self._id, self._group_identifier, selection_type) + def _is_valid_double_click(self, event): + return True + class ContextCardWidget(CardWidget): """Card for global context. @@ -527,6 +539,15 @@ class InstanceCardWidget(CardWidget): def _on_expend_clicked(self): self._set_expanded() + def _is_valid_double_click(self, event): + widget = self.childAt(event.pos()) + if ( + widget is self._active_checkbox + or widget is self._expand_btn + ): + return False + return True + class InstanceCardView(AbstractInstanceView): """Publish access to card view. @@ -580,10 +601,6 @@ class InstanceCardView(AbstractInstanceView): self.sizePolicy().verticalPolicy() ) - def mouseDoubleClickEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self.double_clicked.emit() - def sizeHint(self): """Modify sizeHint based on visibility of scroll bars.""" # Calculate width hint by content widget and vertical scroll bar @@ -721,6 +738,7 @@ class InstanceCardView(AbstractInstanceView): ) group_widget.active_changed.connect(self._on_active_changed) group_widget.selected.connect(self._on_widget_selection) + group_widget.double_clicked.connect(self.double_clicked) self._content_layout.insertWidget(widget_idx, group_widget) self._widgets_by_group[group_name] = group_widget @@ -761,6 +779,7 @@ class InstanceCardView(AbstractInstanceView): widget = ContextCardWidget(self._content_widget) widget.selected.connect(self._on_widget_selection) + widget.double_clicked.connect(self.double_clicked) self._context_widget = widget @@ -784,6 +803,7 @@ class InstanceCardView(AbstractInstanceView): CONVERTOR_ITEM_GROUP, self._content_widget ) group_widget.selected.connect(self._on_widget_selection) + group_widget.double_clicked.connect(self.double_clicked) self._content_layout.insertWidget(1, group_widget) self._convertor_items_group = group_widget From 8b17cf5485c52bf72c5b624ee4a1c9788f83fe92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 10:58:48 +0200 Subject: [PATCH 638/727] list view widgets can tell if should trigger double click --- .../publisher/widgets/list_view_widgets.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index 6a7335bd74..aa8f26998a 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -110,6 +110,7 @@ class InstanceListItemWidget(QtWidgets.QWidget): This is required to be able use custom checkbox on custom place. """ active_changed = QtCore.Signal(str, bool) + double_clicked = QtCore.Signal() def __init__(self, instance, parent): super(InstanceListItemWidget, self).__init__(parent) @@ -149,6 +150,12 @@ class InstanceListItemWidget(QtWidgets.QWidget): self._set_valid_property(instance.has_valid_context) + def mouseDoubleClickEvent(self, event): + widget = self.childAt(event.pos()) + super(InstanceListItemWidget, self).mouseDoubleClickEvent(event) + if widget is not self._active_checkbox: + self.double_clicked.emit() + def _set_valid_property(self, valid): if self._has_valid_context == valid: return @@ -209,6 +216,8 @@ class InstanceListItemWidget(QtWidgets.QWidget): class ListContextWidget(QtWidgets.QFrame): """Context (or global attributes) widget.""" + double_clicked = QtCore.Signal() + def __init__(self, parent): super(ListContextWidget, self).__init__(parent) @@ -225,6 +234,10 @@ class ListContextWidget(QtWidgets.QFrame): self.label_widget = label_widget + def mouseDoubleClickEvent(self, event): + super(ListContextWidget, self).mouseDoubleClickEvent(event) + self.double_clicked.emit() + class InstanceListGroupWidget(QtWidgets.QFrame): """Widget representing group of instances. @@ -392,15 +405,12 @@ class InstanceTreeView(QtWidgets.QTreeView): return True def mousePressEvent(self, event): - handled = self._mouse_press(event) - if handled: - super(InstanceTreeView, self).mousePressEvent(event) + self._mouse_press(event) + super(InstanceTreeView, self).mousePressEvent(event) def mouseDoubleClickEvent(self, event): - handled = self._mouse_press(event) - if handled: - self.double_clicked.emit() - super(InstanceTreeView, self).mouseDoubleClickEvent(event) + self._mouse_press(event) + super(InstanceTreeView, self).mouseDoubleClickEvent(event) def _mouse_release(self, event, pressed_index): if event.button() != QtCore.Qt.LeftButton: @@ -697,6 +707,7 @@ class InstanceListView(AbstractInstanceView): self._active_toggle_enabled ) widget.active_changed.connect(self._on_active_changed) + widget.double_clicked.connect(self.double_clicked) self._instance_view.setIndexWidget(proxy_index, widget) self._widgets_by_id[instance.id] = widget @@ -727,6 +738,7 @@ class InstanceListView(AbstractInstanceView): ) proxy_index = self._proxy_model.mapFromSource(index) widget = ListContextWidget(self._instance_view) + widget.double_clicked.connect(self.double_clicked) self._instance_view.setIndexWidget(proxy_index, widget) self._context_widget = widget From 168f8cdcc20435105bcf2f73bbca0ea22b08b298 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 10:59:05 +0200 Subject: [PATCH 639/727] revert output of '_mouse_press' --- client/ayon_core/tools/publisher/widgets/list_view_widgets.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py index aa8f26998a..71be0ab1a4 100644 --- a/client/ayon_core/tools/publisher/widgets/list_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/list_view_widgets.py @@ -393,7 +393,7 @@ class InstanceTreeView(QtWidgets.QTreeView): "double click" as 2x "single click". """ if event.button() != QtCore.Qt.LeftButton: - return False + return pressed_group_index = None pos_index = self.indexAt(event.pos()) @@ -402,8 +402,6 @@ class InstanceTreeView(QtWidgets.QTreeView): self._pressed_group_index = pressed_group_index - return True - def mousePressEvent(self, event): self._mouse_press(event) super(InstanceTreeView, self).mousePressEvent(event) From a19350eea9925cb5c8ba6b3af08bbef8bf953fe5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 12:10:50 +0200 Subject: [PATCH 640/727] implemented helper function to determine if current process is ayon launcher --- client/ayon_core/lib/__init__.py | 2 ++ client/ayon_core/lib/ayon_info.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index e436396c6c..e25d3479ee 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -139,6 +139,7 @@ from .path_tools import ( ) from .ayon_info import ( + is_in_ayon_launcher_process, is_running_from_build, is_using_ayon_console, is_staging_enabled, @@ -248,6 +249,7 @@ __all__ = [ "Logger", + "is_in_ayon_launcher_process", "is_running_from_build", "is_using_ayon_console", "is_staging_enabled", diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index fc09a7c90c..c4333fab95 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -1,4 +1,5 @@ import os +import sys import json import datetime import platform @@ -25,6 +26,18 @@ def get_ayon_launcher_version(): return content["__version__"] +def is_in_ayon_launcher_process(): + """Determine if current process is running from AYON launcher. + + Returns: + bool: True if running from AYON launcher. + + """ + ayon_executable_path = os.path.normpath(os.environ["AYON_EXECUTABLE"]) + executable_path = os.path.normpath(sys.executable) + return ayon_executable_path == executable_path + + def is_running_from_build(): """Determine if current process is running from build or code. From fa4569402395e88543796db5903157ea917a9cd5 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 9 May 2024 12:04:27 +0100 Subject: [PATCH 641/727] Use transform cache to handle camera updates --- .../blender/plugins/load/load_camera_abc.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py index 6178578081..a49bb40d9a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py @@ -43,7 +43,10 @@ class AbcCameraLoader(plugin.AssetLoader): def _process(self, libpath, asset_group, group_name): plugin.deselect_all() - bpy.ops.wm.alembic_import(filepath=libpath) + # Force the creation of the transform cache even if the camera + # doesn't have an animation. We use the cache to update the camera. + bpy.ops.wm.alembic_import( + filepath=libpath, always_add_cache_reader=True) objects = lib.get_selection() @@ -178,12 +181,33 @@ class AbcCameraLoader(plugin.AssetLoader): self.log.info("Library already loaded, not updating...") return - mat = asset_group.matrix_basis.copy() + for obj in asset_group.children: + found = False + for constraint in obj.constraints: + if constraint.type == "TRANSFORM_CACHE": + constraint.cache_file.filepath = libpath.as_posix() + found = True + break + if not found: + # This is to keep compatibility with cameras loaded with + # the old loader + # Create a new constraint for the cache file + constraint = obj.constraints.new("TRANSFORM_CACHE") + bpy.ops.cachefile.open(filepath=libpath.as_posix()) + constraint.cache_file = bpy.data.cache_files[-1] + constraint.cache_file.scale = 1.0 - self._remove(asset_group) - self._process(str(libpath), asset_group, object_name) + # This is a workaround to set the object path. Blender doesn't + # load the list of object paths until the object is evaluated. + # This is a hack to force the object to be evaluated. + # The modifier doesn't need to be removed because camera + # objects don't have modifiers. + obj.modifiers.new( + name='MeshSequenceCache', type='MESH_SEQUENCE_CACHE') + bpy.context.evaluated_depsgraph_get() - asset_group.matrix_basis = mat + constraint.object_path = ( + constraint.cache_file.object_paths[0].path) metadata["libpath"] = str(libpath) metadata["representation"] = repre_entity["id"] From 145268e94fe4da3f449ff4e46ca3a4f1d41a2a69 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 14:58:17 +0200 Subject: [PATCH 642/727] skip the plugin logic if all keys are set --- .../publish/collect_frame_data_from_asset_entity.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index 4d203649c7..76ecc1cd8b 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -26,6 +26,13 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): ): if key not in instance.data: missing_keys.append(key) + + # Skip the logic if all keys are already collected. + # NOTE: In editorial is not 'folderEntity' filled, so it would crash + # even if we don't need it. + if not missing_keys: + return + keys_set = [] folder_attributes = instance.data["folderEntity"]["attrib"] for key in missing_keys: From c9ad59525506674ad68a9ec16e8ef0214a9eb62e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 15:05:31 +0200 Subject: [PATCH 643/727] formatting changes --- .../collect_frame_data_from_asset_entity.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py index 76ecc1cd8b..2e564a2e4e 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py @@ -10,9 +10,13 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.491 label = "Collect Missing Frame Data From Folder" - families = ["plate", "pointcache", - "vdbcache", "online", - "render"] + families = [ + "plate", + "pointcache", + "vdbcache", + "online", + "render", + ] hosts = ["traypublisher"] def process(self, instance): @@ -22,7 +26,7 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): "frameStart", "frameEnd", "handleStart", - "handleEnd" + "handleEnd", ): if key not in instance.data: missing_keys.append(key) @@ -39,6 +43,9 @@ class CollectFrameDataFromAssetEntity(pyblish.api.InstancePlugin): if key in folder_attributes: instance.data[key] = folder_attributes[key] keys_set.append(key) + if keys_set: - self.log.debug(f"Frame range data {keys_set} " - "has been collected from folder entity.") + self.log.debug( + f"Frame range data {keys_set} " + "has been collected from folder entity." + ) From f343664a3836fb4dfcc0c753335baa80521ae72c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 15:07:19 +0200 Subject: [PATCH 644/727] rename the file --- ...m_asset_entity.py => collect_frame_data_from_folder_entity.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/ayon_core/hosts/traypublisher/plugins/publish/{collect_frame_data_from_asset_entity.py => collect_frame_data_from_folder_entity.py} (100%) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py similarity index 100% rename from client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_asset_entity.py rename to client/ayon_core/hosts/traypublisher/plugins/publish/collect_frame_data_from_folder_entity.py From 0bba27e4dd97b28be9d47c5aaa08f526b97c928c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 15:16:52 +0200 Subject: [PATCH 645/727] removed unused imports --- client/ayon_core/hosts/max/api/lib.py | 3 --- .../hosts/maya/plugins/create/create_animation_pointcache.py | 1 - client/ayon_core/hosts/traypublisher/csv_publish.py | 2 -- 3 files changed, 6 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index d9a3af3336..0e3abe25ec 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -6,12 +6,9 @@ import json from typing import Any, Dict, Union import six -import ayon_api from ayon_core.pipeline import ( get_current_project_name, - get_current_folder_path, - get_current_task_name, colorspace ) from ayon_core.settings import get_project_settings 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 08d50a1ab8..069762e4ae 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 @@ -6,7 +6,6 @@ from ayon_core.lib import ( BoolDef, NumberDef, ) -from ayon_core.pipeline import CreatedInstance def _get_animation_attr_defs(cls): diff --git a/client/ayon_core/hosts/traypublisher/csv_publish.py b/client/ayon_core/hosts/traypublisher/csv_publish.py index b43792a357..2762172936 100644 --- a/client/ayon_core/hosts/traypublisher/csv_publish.py +++ b/client/ayon_core/hosts/traypublisher/csv_publish.py @@ -1,5 +1,3 @@ -import os - import pyblish.api import pyblish.util From 270921f63eb9a4032cf0f1d04f1056b71eab0071 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 15:18:10 +0200 Subject: [PATCH 646/727] use preferred order in condition --- .../modules/deadline/plugins/publish/validate_deadline_pools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py index 5094b3deaf..2fb511bf51 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -72,7 +72,7 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, auth=auth, log=self.log) # some DL return "none" as a pool name - if not "none" in pools: + if "none" not in pools: pools.append("none") self.log.info("Available pools: {}".format(pools)) self.pools_per_url[deadline_url] = pools From 2e6ee298a7aa87c869af9d5f18a57173b2933aac Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 15:18:25 +0200 Subject: [PATCH 647/727] removed unnecessary check for 'deadline' key --- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 06dd62e18b..0f505dce78 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -467,8 +467,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, # Inject deadline url to instances to query DL for job id for overrides for inst in instances: - if not "deadline" in inst: - inst["deadline"] = {} inst["deadline"] = instance.data["deadline"] # publish job file From b7662645b5c980c09c599ce4d88ad03a77d0d00f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 May 2024 16:15:30 +0200 Subject: [PATCH 648/727] Refactor indentation for better readability Adjusted the indentation in a function to improve code clarity and readability. --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 705d1570b0..3503d0c534 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -873,7 +873,7 @@ def _get_global_config_data( product_entities_by_name = { product_entity["name"]: product_entity for product_entity in ayon_api.get_products( - project_name, + project_name, folder_ids={folder_id}, product_name_regex=product_name, fields={"id", "name"} From 9b2564cfd7049cbaff6670451c9cd6762023bb7f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 9 May 2024 16:22:21 +0200 Subject: [PATCH 649/727] fixing settings type name key --- server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/main.py b/server/settings/main.py index c9c86bdd0d..97bd376f47 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -58,7 +58,7 @@ def _ocio_config_profile_types(): return [ {"value": "builtin_path", "label": "AYON built-in OCIO config"}, {"value": "custom_path", "label": "Path to OCIO config"}, - {"value": "product", "label": "Published product"}, + {"value": "product_name", "label": "Published product"}, ] From f70bdc5795a698e8316b064a57720ddb8585ccb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 16:59:50 +0200 Subject: [PATCH 650/727] fix product name in base value --- server/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/__init__.py b/server/__init__.py index f6f89f2049..31a6e8dfca 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -39,7 +39,7 @@ class CoreAddon(BaseServerAddon): ) base_value = { "type": "builtin_path", - "product": "", + "product_name": "", "host_names": [], "task_names": [], "task_types": [], From bcd1e864c0b2507080521141c61adbc70c5b4fc8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 17:00:04 +0200 Subject: [PATCH 651/727] use correct builtin path --- server/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/__init__.py b/server/__init__.py index 31a6e8dfca..50eda35c89 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -46,10 +46,13 @@ class CoreAddon(BaseServerAddon): "custom_path": "", "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio" } - if first_filepath not in ( + if first_filepath in ( "{BUILTIN_OCIO_ROOT}/aces_1.2/config.oci", "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", ): + base_value["type"] = "builtin_path" + base_value["builtin_path"] = first_filepath + else: base_value["type"] = "custom_path" base_value["custom_path"] = first_filepath From 925ff8b86f11e595be591b7458b28694608efb91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 17:04:09 +0200 Subject: [PATCH 652/727] fix value check --- server/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/__init__.py b/server/__init__.py index 50eda35c89..82473927b6 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -47,7 +47,7 @@ class CoreAddon(BaseServerAddon): "builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio" } if first_filepath in ( - "{BUILTIN_OCIO_ROOT}/aces_1.2/config.oci", + "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", ): base_value["type"] = "builtin_path" From 9570d92411dcd8efa97a93da61e34141a829f049 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 May 2024 18:10:58 +0200 Subject: [PATCH 653/727] change layout of profiles --- server/settings/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/main.py b/server/settings/main.py index 97bd376f47..d1cee32afa 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -77,6 +77,7 @@ def _ocio_built_in_paths(): class CoreImageIOConfigProfilesModel(BaseSettingsModel): + _layout = "expanded" host_names: list[str] = SettingsField( default_factory=list, title="Host names" From b7be1952e8533a6f794c7604ad4de939060a7495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 9 May 2024 20:24:54 +0200 Subject: [PATCH 654/727] Update client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py Co-authored-by: Roy Nieterau --- .../traypublisher/plugins/create/create_editorial_package.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py index 6a581b59d1..19ca032a0f 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py @@ -24,7 +24,6 @@ class EditorialPackageCreator(TrayPublishCreator): # Position batch creator after simple creators order = 120 - def get_icon(self): return "fa.folder" From 4d737790de9d598f7a5b88e7e20b6ca6e738b444 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 9 May 2024 21:04:54 +0100 Subject: [PATCH 655/727] Working version --- client/ayon_core/hosts/nuke/api/plugin.py | 9 +++++++-- .../nuke/plugins/publish/extract_review_intermediates.py | 9 +++++++-- server_addon/nuke/package.py | 2 +- server_addon/nuke/server/settings/publish_plugins.py | 3 +++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/client/ayon_core/hosts/nuke/api/plugin.py index fb56dec833..ec256ea303 100644 --- a/client/ayon_core/hosts/nuke/api/plugin.py +++ b/client/ayon_core/hosts/nuke/api/plugin.py @@ -834,7 +834,7 @@ class ExporterReviewMov(ExporterReview): self.log.info("Nodes exported...") return path - def generate_mov(self, farm=False, **kwargs): + def generate_mov(self, farm=False, delete=True, **kwargs): # colorspace data colorspace = None # get colorspace settings @@ -987,8 +987,13 @@ class ExporterReviewMov(ExporterReview): self.render(write_node.name()) # ---------- generate representation data + tags = ["review", "need_thumbnail"] + + if delete: + tags.append("delete") + self.get_representation_data( - tags=["review", "need_thumbnail", "delete"] + add_tags, + tags=tags + add_tags, custom_tags=add_custom_tags, range=True, colorspace=colorspace diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py b/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py index 8d7a3ec311..82c7b6e4c5 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/extract_review_intermediates.py @@ -136,11 +136,16 @@ class ExtractReviewIntermediates(publish.Extractor): self, instance, o_name, o_data["extension"], multiple_presets) + o_data["add_custom_tags"].append("intermediate") + delete = not o_data.get("publish", False) + if instance.data.get("farm"): if "review" in instance.data["families"]: instance.data["families"].remove("review") - data = exporter.generate_mov(farm=True, **o_data) + data = exporter.generate_mov( + farm=True, delete=delete, **o_data + ) self.log.debug( "_ data: {}".format(data)) @@ -154,7 +159,7 @@ class ExtractReviewIntermediates(publish.Extractor): "bakeWriteNodeName": data.get("bakeWriteNodeName") }) else: - data = exporter.generate_mov(**o_data) + data = exporter.generate_mov(delete=delete, **o_data) # add representation generated by exporter generated_repres.extend(data["representations"]) diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py index bf03c4e7e7..e522b9fb5d 100644 --- a/server_addon/nuke/package.py +++ b/server_addon/nuke/package.py @@ -1,3 +1,3 @@ name = "nuke" title = "Nuke" -version = "0.1.11" +version = "0.1.12" diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index d5b05d8715..e67f7be24f 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -125,6 +125,7 @@ class ReformatNodesConfigModel(BaseSettingsModel): class IntermediateOutputModel(BaseSettingsModel): name: str = SettingsField(title="Output name") + publish: bool = SettingsField(title="Publish") filter: BakingStreamFilterModel = SettingsField( title="Filter", default_factory=BakingStreamFilterModel) read_raw: bool = SettingsField( @@ -346,6 +347,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "outputs": [ { "name": "baking", + "publish": False, "filter": { "task_types": [], "product_types": [], @@ -401,6 +403,7 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "outputs": [ { "name": "baking", + "publish": False, "filter": { "task_types": [], "product_types": [], From 3bd7c7dddfd3a6e419d34640de4d248fc664396e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 May 2024 11:02:55 +0200 Subject: [PATCH 656/727] Fix after effects launch logic variable for `AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH` -> `AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH` --- client/ayon_core/hosts/aftereffects/api/launch_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/aftereffects/api/launch_logic.py b/client/ayon_core/hosts/aftereffects/api/launch_logic.py index 5a23f2cb35..da6887668a 100644 --- a/client/ayon_core/hosts/aftereffects/api/launch_logic.py +++ b/client/ayon_core/hosts/aftereffects/api/launch_logic.py @@ -60,7 +60,7 @@ def main(*subprocess_args): ) ) - elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True): + elif os.environ.get("AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH", True): save = False if os.getenv("WORKFILES_SAVE_AS"): save = True From 39da0bc7a3e23d7a89ed3ed40c1aee094540f2fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 12:33:52 +0200 Subject: [PATCH 657/727] define server version with ayon attributes --- package.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.py b/package.py index 79450d029f..0f2e855161 100644 --- a/package.py +++ b/package.py @@ -5,7 +5,5 @@ version = "0.3.1-dev.1" client_dir = "ayon_core" plugin_for = ["ayon_server"] -requires = [ - "~ayon_server-1.0.3+<2.0.0", -] +ayon_server_version = ">=1.0.3<2.0.0" From 43bf0b135c4923ce14319f79ca4b39e3be27c7fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 12:34:06 +0200 Subject: [PATCH 658/727] define minimum launcher version --- package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/package.py b/package.py index 0f2e855161..459b0034bd 100644 --- a/package.py +++ b/package.py @@ -7,3 +7,4 @@ client_dir = "ayon_core" plugin_for = ["ayon_server"] ayon_server_version = ">=1.0.3<2.0.0" +ayon_launcher_version = ">=1.0.2" From c4b07146adeea379fbe1d6ff99ae2f7f2c408384 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 12:34:15 +0200 Subject: [PATCH 659/727] add remaining attributes --- package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.py b/package.py index 459b0034bd..9e644fa310 100644 --- a/package.py +++ b/package.py @@ -8,3 +8,5 @@ plugin_for = ["ayon_server"] ayon_server_version = ">=1.0.3<2.0.0" ayon_launcher_version = ">=1.0.2" +ayon_required_addons = {} +ayon_compatible_addons = {} From 68cfa1a4a1ee7705f43e7999756d0a14dbbbc22b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 13:54:30 +0200 Subject: [PATCH 660/727] fix server version compatibility --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index 9e644fa310..fa3eaba9bd 100644 --- a/package.py +++ b/package.py @@ -6,7 +6,7 @@ client_dir = "ayon_core" plugin_for = ["ayon_server"] -ayon_server_version = ">=1.0.3<2.0.0" +ayon_server_version = ">=1.0.3,<2.0.0" ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} ayon_compatible_addons = {} From 2a675f51a6db20322b3e1d5e8f537723bc33154d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:38:16 +0200 Subject: [PATCH 661/727] AY-4801-simplified Settings Got rid of profiles, didn't make much sense. Git rid of multiple output definitions, didn't make sense with single otio file. --- .../server/settings/publish_plugins.py | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py index 9869f54620..2afe20c865 100644 --- a/server_addon/traypublisher/server/settings/publish_plugins.py +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -41,9 +41,7 @@ class ExtractEditorialPckgFFmpegModel(BaseSettingsModel): class ExtractEditorialPckgOutputDefModel(BaseSettingsModel): - """Set extension and ffmpeg arguments. See `ExtractReview` for example.""" _layout = "expanded" - name: str = SettingsField("", title="Name") ext: str = SettingsField("", title="Output extension") ffmpeg_args: ExtractEditorialPckgFFmpegModel = SettingsField( @@ -52,40 +50,13 @@ class ExtractEditorialPckgOutputDefModel(BaseSettingsModel): ) -class ExtractEditorialPckgProfileModel(BaseSettingsModel): - product_types: list[str] = SettingsField( - default_factory=list, - title="Product types" - ) - task_types: list[str] = SettingsField( - default_factory=list, - title="Task types", - enum_resolver=task_types_enum - ) - task_names: list[str] = SettingsField( - default_factory=list, - title="Task names" - ) - product_names: list[str] = SettingsField( - default_factory=list, - title="Product names" - ) - outputs: list[ExtractEditorialPckgOutputDefModel] = SettingsField( - default_factory=list, - title="Output Definitions", - ) - - @validator("outputs") - def validate_unique_outputs(cls, value): - ensure_unique_names(value) - return value - - class ExtractEditorialPckgConversionModel(BaseSettingsModel): - """Conversion of input movie files into expected format.""" - enabled: bool = SettingsField(True) - profiles: list[ExtractEditorialPckgProfileModel] = SettingsField( - default_factory=list, title="Profiles" + """Set output definition if resource files should be converted.""" + conversion_enabled: bool = SettingsField(True, + title="Conversion enabled") + output: ExtractEditorialPckgOutputDefModel = SettingsField( + default_factory=ExtractEditorialPckgOutputDefModel, + title="Output Definitions", ) @@ -128,8 +99,7 @@ DEFAULT_PUBLISH_PLUGINS = { "active": True }, "ExtractEditorialPckgConversion": { - "enabled": True, - "optional": True, + "optional": False, "active": True } } From a1d310fad04e210ac1cf60c86473346c5a3061e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:39:07 +0200 Subject: [PATCH 662/727] AY-4801-exposed state of conversion from Settings in creator Settings have toggle to on/off conversion, this is exposed for Artist to decide ad-hoc. --- .../create/create_editorial_package.py | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py index 6a581b59d1..72a156dfb7 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py @@ -4,7 +4,12 @@ from ayon_core.pipeline import ( CreatedInstance, ) -from ayon_core.lib.attribute_definitions import FileDef +from ayon_core.lib.attribute_definitions import ( + FileDef, + BoolDef, + TextDef, + HiddenDef +) from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator @@ -24,6 +29,16 @@ class EditorialPackageCreator(TrayPublishCreator): # Position batch creator after simple creators order = 120 + conversion_enabled = False + + def apply_settings(self, project_settings): + self.conversion_enabled = ( + project_settings["traypublisher"] + ["publish"] + ["ExtractEditorialPckgConversion"] + ["conversion_enabled"] + ) + print(project_settings) def get_icon(self): return "fa.folder" @@ -34,8 +49,9 @@ class EditorialPackageCreator(TrayPublishCreator): return instance_data["creator_attributes"] = { - "path": (Path(folder_path["directory"]) / - Path(folder_path["filenames"][0])).as_posix() + "folder_path": (Path(folder_path["directory"]) / + Path(folder_path["filenames"][0])).as_posix(), + "conversion_enabled": pre_create_data["conversion_enabled"] } # Create new instance @@ -53,7 +69,23 @@ class EditorialPackageCreator(TrayPublishCreator): extensions=[], allow_sequences=False, label="Folder path" - ) + ), + BoolDef("conversion_enabled", + tooltip="Convert to output defined in Settings.", + default=self.conversion_enabled, + label="Convert resources"), + ] + + def get_instance_attr_defs(self): + return [ + TextDef( + "folder_path", + label="Folder path", + disabled=True + ), + BoolDef("conversion_enabled", + tooltip="Convert to output defined in Settings.", + label="Convert resources"), ] def get_detail_description(self): From b55ed2e7869a508f33621b656cc03e23e8bd66d2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:39:42 +0200 Subject: [PATCH 663/727] AY-4801-updated variable name Old 'path' clashed in output of instance attributes in Publisher. --- .../traypublisher/plugins/publish/collect_editorial_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py index 101f58b6d1..cb1277546c 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/collect_editorial_package.py @@ -27,7 +27,7 @@ class CollectEditorialPackage(pyblish.api.InstancePlugin): families = ["editorial_pckg"] def process(self, instance): - folder_path = instance.data["creator_attributes"].get("path") + folder_path = instance.data["creator_attributes"]["folder_path"] if not folder_path or not os.path.exists(folder_path): self.log.info(( "Instance doesn't contain collected existing folder path." From 663ace6c8f23ae8faf61408c761aed6457d4c100 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:40:44 +0200 Subject: [PATCH 664/727] AY-4801-conversion controlled by instance attribute --- .../plugins/publish/extract_editorial_pckg.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index 02f953d579..6b6f0bfe1d 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -45,14 +45,15 @@ class ExtractEditorialPckgConversion(publish.Extractor): publish_resource_folder) project_settings = instance.context.data["project_settings"] - profiles = (project_settings["traypublisher"] - ["publish"] - ["ExtractEditorialPckgConversion"] - .get("profiles")) - output_def = None - if profiles: - output_def = self._get_output_definition(instance, profiles) - if output_def: + output_def = (project_settings["traypublisher"] + ["publish"] + ["ExtractEditorialPckgConversion"] + ["output"]) + + conversion_enabled = (instance.data["creator_attributes"] + ["conversion_enabled"]) + + if conversion_enabled and output_def["ext"]: transfers = self._convert_resources(output_def, transfers) if not "transfers" in instance.data: From 0a45c5f8fff5e1bae29cec10033cff3263cfc638 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:41:10 +0200 Subject: [PATCH 665/727] AY-4801-removed profile filtering --- .../plugins/publish/extract_editorial_pckg.py | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index 6b6f0bfe1d..e2eb4cb916 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -6,7 +6,7 @@ import opentimelineio import pyblish.api -from ayon_core.lib import filter_profiles, get_ffmpeg_tool_args, run_subprocess +from ayon_core.lib import get_ffmpeg_tool_args, run_subprocess from ayon_core.pipeline import publish @@ -81,26 +81,6 @@ class ExtractEditorialPckgConversion(publish.Extractor): os.makedirs(publish_resource_folder, exist_ok=True) return publish_resource_folder - def _get_output_definition(self, instance, profiles): - """Return appropriate profile by context information.""" - product_type = instance.data["productType"] - product_name = instance.data["productName"] - task_entity = instance.data["taskEntity"] or {} - task_name = task_entity.get("name") - task_type = task_entity.get("taskType") - filtering_criteria = { - "product_types": product_type, - "product_names": product_name, - "task_names": task_name, - "task_types": task_type, - } - profile = filter_profiles( - profiles, - filtering_criteria, - logger=self.log - ) - return profile - def _get_resource_path_mapping(self, instance, transfers): """Returns dict of {source_mov_path: rootless_published_path}.""" replace_paths = {} From f8503aa5dc7b62e0d5fdd9d982dc55b1204b604a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:41:37 +0200 Subject: [PATCH 666/727] AY-4801-removed multiple output definitions --- .../plugins/publish/extract_editorial_pckg.py | 107 +++++++++--------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index e2eb4cb916..488b8e5a75 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -147,69 +147,66 @@ class ExtractEditorialPckgConversion(publish.Extractor): def _convert_resources(self, output_def, transfers): """Converts all resource files to configured format.""" - outputs = output_def["outputs"] - if not outputs: - self.log.warning("No output configured in " - "ayon+settings://traypublisher/publish/ExtractEditorialPckgConversion/profiles/0/outputs") # noqa + out_extension = output_def["ext"] + if not out_extension: + self.log.warning("No output extension configured in " + "ayon+settings://traypublisher/publish/ExtractEditorialPckgConversion") # noqa return transfers final_transfers = [] - # most likely only single output is expected - for output in outputs: - out_extension = output["ext"] - out_def_ffmpeg_args = output["ffmpeg_args"] - ffmpeg_input_args = [ - value.strip() - for value in out_def_ffmpeg_args["input"] - if value.strip() - ] - ffmpeg_video_filters = [ - value.strip() - for value in out_def_ffmpeg_args["video_filters"] - if value.strip() - ] - ffmpeg_audio_filters = [ - value.strip() - for value in out_def_ffmpeg_args["audio_filters"] - if value.strip() - ] - ffmpeg_output_args = [ - value.strip() - for value in out_def_ffmpeg_args["output"] - if value.strip() - ] - ffmpeg_input_args = self._split_ffmpeg_args(ffmpeg_input_args) + out_def_ffmpeg_args = output_def["ffmpeg_args"] + ffmpeg_input_args = [ + value.strip() + for value in out_def_ffmpeg_args["input"] + if value.strip() + ] + ffmpeg_video_filters = [ + value.strip() + for value in out_def_ffmpeg_args["video_filters"] + if value.strip() + ] + ffmpeg_audio_filters = [ + value.strip() + for value in out_def_ffmpeg_args["audio_filters"] + if value.strip() + ] + ffmpeg_output_args = [ + value.strip() + for value in out_def_ffmpeg_args["output"] + if value.strip() + ] + ffmpeg_input_args = self._split_ffmpeg_args(ffmpeg_input_args) - generic_args = [ - subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg")) - ] - generic_args.extend(ffmpeg_input_args) - if ffmpeg_video_filters: - generic_args.append("-filter:v") - generic_args.append( - "\"{}\"".format(",".join(ffmpeg_video_filters))) + generic_args = [ + subprocess.list2cmdline(get_ffmpeg_tool_args("ffmpeg")) + ] + generic_args.extend(ffmpeg_input_args) + if ffmpeg_video_filters: + generic_args.append("-filter:v") + generic_args.append( + "\"{}\"".format(",".join(ffmpeg_video_filters))) - if ffmpeg_audio_filters: - generic_args.append("-filter:a") - generic_args.append( - "\"{}\"".format(",".join(ffmpeg_audio_filters))) + if ffmpeg_audio_filters: + generic_args.append("-filter:a") + generic_args.append( + "\"{}\"".format(",".join(ffmpeg_audio_filters))) - for source, destination in transfers: - base_name = os.path.basename(destination) - file_name, ext = os.path.splitext(base_name) - dest_path = os.path.join(os.path.dirname(destination), - f"{file_name}.{out_extension}") - final_transfers.append((source, dest_path)) + for source, destination in transfers: + base_name = os.path.basename(destination) + file_name, ext = os.path.splitext(base_name) + dest_path = os.path.join(os.path.dirname(destination), + f"{file_name}.{out_extension}") + final_transfers.append((source, dest_path)) - all_args = copy.deepcopy(generic_args) - all_args.append(f"-i {source}") - all_args.extend(ffmpeg_output_args) # order matters - all_args.append(f"{dest_path}") - subprcs_cmd = " ".join(all_args) + all_args = copy.deepcopy(generic_args) + all_args.append(f"-i {source}") + all_args.extend(ffmpeg_output_args) # order matters + all_args.append(f"{dest_path}") + subprcs_cmd = " ".join(all_args) - # run subprocess - self.log.debug("Executing: {}".format(subprcs_cmd)) - run_subprocess(subprcs_cmd, shell=True, logger=self.log) + # run subprocess + self.log.debug("Executing: {}".format(subprcs_cmd)) + run_subprocess(subprcs_cmd, shell=True, logger=self.log) return final_transfers def _split_ffmpeg_args(self, in_args): From ac2453db1e1cb37dd29cbd76cf9f9d61d0ca6751 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 14:42:13 +0200 Subject: [PATCH 667/727] bump version to '0.3.1' --- client/ayon_core/version.py | 2 +- package.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index a60de0493a..952df2a2c0 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.3.1-dev.1" +__version__ = "0.3.1" diff --git a/package.py b/package.py index fa3eaba9bd..4398bd9920 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "0.3.1-dev.1" +version = "0.3.1" client_dir = "ayon_core" From 0e4e845407d9f0c425b392d3f454dd5866b819b5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 10 May 2024 14:43:02 +0200 Subject: [PATCH 668/727] bump version to '0.3.2-dev.1' --- client/ayon_core/version.py | 2 +- package.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 952df2a2c0..275e1b1dd6 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.3.1" +__version__ = "0.3.2-dev.1" diff --git a/package.py b/package.py index 4398bd9920..b7b8d2dae6 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "0.3.1" +version = "0.3.2-dev.1" client_dir = "ayon_core" From ba0918964f3766465104d278489d49bad7585233 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 14:41:57 +0200 Subject: [PATCH 669/727] AY-4801-updated validation message --- .../plugins/publish/extract_editorial_pckg.py | 8 +++++--- .../plugins/publish/validate_editorial_package.py | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index 488b8e5a75..d25a7146a0 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -111,9 +111,11 @@ class ExtractEditorialPckgConversion(publish.Extractor): if not target_url: continue file_name = os.path.basename(target_url) - replace_value = replace_paths.get(file_name) - if replace_value: - clip.media_reference.target_url = replace_value + replace_path = replace_paths.get(file_name) + if replace_path: + clip.media_reference.target_url = replace_path + if clip.name == file_name: + clip.name = os.path.basename(replace_path) return otio_data diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py index 869dc73811..ce545610ea 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py @@ -47,8 +47,9 @@ class ValidateEditorialPackage(pyblish.api.InstancePlugin): missing_files.add(target_basename) if missing_files: - raise PublishValidationError("Otio file contains missing files " - f"'{missing_files}'.") + raise PublishValidationError( + "Otio file contains missing files `{missing_files}`.\n\n" + f"Please add them to `{folder_path}` and republish.") instance.data["editorial_pckg"]["otio_data"] = otio_data From 4cfc90bbb3b7bf97315b993028edc201280fabe4 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 10 May 2024 16:29:56 +0300 Subject: [PATCH 670/727] Add OPMenu Stencil --- .../hosts/houdini/startup/OPmenu.xml | 528 ++++++++++++++++++ 1 file changed, 528 insertions(+) create mode 100644 client/ayon_core/hosts/houdini/startup/OPmenu.xml diff --git a/client/ayon_core/hosts/houdini/startup/OPmenu.xml b/client/ayon_core/hosts/houdini/startup/OPmenu.xml new file mode 100644 index 0000000000..bdb559a084 --- /dev/null +++ b/client/ayon_core/hosts/houdini/startup/OPmenu.xml @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 2c12a540c184b7d7323bdf5306d3dbae9031a787 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 10 May 2024 16:36:18 +0300 Subject: [PATCH 671/727] update OPMenu: add 'Create New (AYON)...' script item --- client/ayon_core/hosts/houdini/startup/OPmenu.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/client/ayon_core/hosts/houdini/startup/OPmenu.xml b/client/ayon_core/hosts/houdini/startup/OPmenu.xml index bdb559a084..0d58cf53fe 100644 --- a/client/ayon_core/hosts/houdini/startup/OPmenu.xml +++ b/client/ayon_core/hosts/houdini/startup/OPmenu.xml @@ -432,6 +432,21 @@ examples.load_token(kwargs['selectedtoken'], kwargs['node'], shift=kwargs['shift + + + + + + + + From 171ecf2be41c9d6e13e862b7479713d5f3ce221e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 15:53:50 +0200 Subject: [PATCH 672/727] AY-4801-bump up version of OpenTimelineIO --- client/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pyproject.toml b/client/pyproject.toml index 1a0ad7e5f2..5e811321f8 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -16,7 +16,7 @@ aiohttp_json_rpc = "*" # TVPaint server aiohttp-middlewares = "^2.0.0" wsrpc_aiohttp = "^3.1.1" # websocket server Click = "^8" -OpenTimelineIO = "0.14.1" +OpenTimelineIO = "0.16.0" opencolorio = "2.2.1" Pillow = "9.5.0" pynput = "^1.7.2" # Timers manager - TODO remove From c976261786de427ba2cff49afee44d5a6e28c99e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 16:04:02 +0200 Subject: [PATCH 673/727] AY-4801-added default setting to .mp4 conversion --- .../server/settings/publish_plugins.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py index 2afe20c865..dc659f6110 100644 --- a/server_addon/traypublisher/server/settings/publish_plugins.py +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -100,6 +100,21 @@ DEFAULT_PUBLISH_PLUGINS = { }, "ExtractEditorialPckgConversion": { "optional": False, - "active": True + "conversion_enabled": True, + "output": { + "ext": "", + "ffmpeg_args": { + "video_filters": [], + "audio_filters": [], + "input": [ + "-apply_trc gamma22" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + } + } } } From e19986a792972962be07d060249cfe3c56764ca3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 10 May 2024 16:26:10 +0200 Subject: [PATCH 674/727] AY-4801-bump up version of traypublisher package --- server_addon/traypublisher/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/traypublisher/package.py b/server_addon/traypublisher/package.py index 4ca8ae9fd3..c138a2296d 100644 --- a/server_addon/traypublisher/package.py +++ b/server_addon/traypublisher/package.py @@ -1,3 +1,3 @@ name = "traypublisher" title = "TrayPublisher" -version = "0.1.4" +version = "0.1.5" From 922db60bd031f5a071c5de84b595e9a7a6d5bab2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 May 2024 17:18:36 +0200 Subject: [PATCH 675/727] Add quotes to file paths in ExtractEditorialPckgConversion, improve error message handling in ValidateEditorialPackage. --- .../traypublisher/plugins/publish/extract_editorial_pckg.py | 5 ++--- .../plugins/publish/validate_editorial_package.py | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index d25a7146a0..a4d8d2c6fb 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -201,9 +201,9 @@ class ExtractEditorialPckgConversion(publish.Extractor): final_transfers.append((source, dest_path)) all_args = copy.deepcopy(generic_args) - all_args.append(f"-i {source}") + all_args.append(f"-i \"{source}\"") all_args.extend(ffmpeg_output_args) # order matters - all_args.append(f"{dest_path}") + all_args.append(f"\"{dest_path}\"") subprcs_cmd = " ".join(all_args) # run subprocess @@ -232,4 +232,3 @@ class ExtractEditorialPckgConversion(publish.Extractor): if arg and arg not in splitted_args: splitted_args.append(arg) return splitted_args - diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py index ce545610ea..89594ce441 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py @@ -48,7 +48,7 @@ class ValidateEditorialPackage(pyblish.api.InstancePlugin): if missing_files: raise PublishValidationError( - "Otio file contains missing files `{missing_files}`.\n\n" + f"Otio file contains missing files `{missing_files}`.\n\n" f"Please add them to `{folder_path}` and republish.") instance.data["editorial_pckg"]["otio_data"] = otio_data @@ -67,5 +67,3 @@ class ValidateEditorialPackage(pyblish.api.InstancePlugin): target_urls.append(target_url) return target_urls - - From 86b2f3b5b52431f6cb11b91e771552c717fa6df8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 10 May 2024 18:45:15 +0300 Subject: [PATCH 676/727] remove redundant elements --- .../hosts/houdini/startup/OPmenu.xml | 520 +----------------- 1 file changed, 3 insertions(+), 517 deletions(-) diff --git a/client/ayon_core/hosts/houdini/startup/OPmenu.xml b/client/ayon_core/hosts/houdini/startup/OPmenu.xml index 0d58cf53fe..0a7b265fa1 100644 --- a/client/ayon_core/hosts/houdini/startup/OPmenu.xml +++ b/client/ayon_core/hosts/houdini/startup/OPmenu.xml @@ -1,438 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + opmenu.unsynchronize + opmenu.vhda_create @@ -447,97 +24,6 @@ create_interactive("io.openpype.creators.houdini.hda", **kwargs) ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 8dc0043d0308b3e823bd055ae7a54217d95462d6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 11 May 2024 00:00:52 +0800 Subject: [PATCH 677/727] add joint into accepted_controllers & make sure the bake animation doesn't get resample when it is exported --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 1 + .../hosts/maya/plugins/publish/validate_animated_reference.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py index ee66ed2fb7..36dc1b1544 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -36,6 +36,7 @@ class ExtractFBXAnimation(publish.Extractor): out_members = instance.data.get("animated_skeleton", []) # Export instance.data["constraints"] = True + instance.data["bakeResampleAnimation"] = False instance.data["skeletonDefinitions"] = True instance.data["referencedAssetsContent"] = True fbx_exporter.set_options_from_instance(instance) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py index 2ba2bff6fc..c9dcc662af 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py @@ -16,7 +16,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, hosts = ["maya"] families = ["animation.fbx"] label = "Animated Reference Rig" - accepted_controllers = ["transform", "locator"] + accepted_controllers = ["transform", "locator", "joint"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] optional = False From 2d4bc1e7ca9546692a242a40e273d1e88fe6fcdf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 11 May 2024 00:23:53 +0800 Subject: [PATCH 678/727] add inputchildren as options --- client/ayon_core/hosts/maya/api/fbx.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 3f1395cb40..437d64abbf 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -102,6 +102,7 @@ class FBXExtractor: "constraints": False, "lights": True, "embeddedTextures": False, + "includeChildren": True, "inputConnections": True, "upAxis": "y", "triangulate": False, From 828fe3ad266324fa64d20ec2cd17c7a9a4803a6f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 May 2024 19:14:34 +0200 Subject: [PATCH 679/727] Fix call to `create_context_node` --- .../ayon_core/hosts/houdini/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py b/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py index a958509e25..40a607e81a 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_workfile.py @@ -95,7 +95,7 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator): # write workfile information to context container. op_ctx = hou.node(CONTEXT_CONTAINER) if not op_ctx: - op_ctx = self.create_context_node() + op_ctx = self.host.create_context_node() workfile_data = {"workfile": current_instance.data_to_store()} imprint(op_ctx, workfile_data) From f820050f7eb7ac95d3c4f07174a5a409e47d2d3a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 May 2024 18:39:33 +0800 Subject: [PATCH 680/727] add inputChildren into the dict from options function --- client/ayon_core/hosts/maya/api/fbx.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 437d64abbf..fd1bf2c901 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -59,6 +59,7 @@ class FBXExtractor: "constraints": bool, "lights": bool, "embeddedTextures": bool, + "includeChildren": bool, "inputConnections": bool, "upAxis": str, # x, y or z, "triangulate": bool, From 4a70e5bb0084285062527937c3b2d3858d36e506 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 May 2024 18:56:56 +0800 Subject: [PATCH 681/727] make sure the constraint is false when exporting --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py index 36dc1b1544..21a8abd46f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -35,8 +35,7 @@ class ExtractFBXAnimation(publish.Extractor): fbx_exporter = fbx.FBXExtractor(log=self.log) out_members = instance.data.get("animated_skeleton", []) # Export - instance.data["constraints"] = True - instance.data["bakeResampleAnimation"] = False + instance.data["constraints"] = False instance.data["skeletonDefinitions"] = True instance.data["referencedAssetsContent"] = True fbx_exporter.set_options_from_instance(instance) From 8cf1d53e2b66eccc6040ebbc48f4f99c9e4a19d0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 May 2024 22:42:37 +0800 Subject: [PATCH 682/727] add TODO --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py index 21a8abd46f..77b5b79b5f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -35,7 +35,8 @@ class ExtractFBXAnimation(publish.Extractor): fbx_exporter = fbx.FBXExtractor(log=self.log) out_members = instance.data.get("animated_skeleton", []) # Export - instance.data["constraints"] = False + # TODO: need to set up the options for users to set up + # the flags they intended to export instance.data["skeletonDefinitions"] = True instance.data["referencedAssetsContent"] = True fbx_exporter.set_options_from_instance(instance) From fb2714005999c262d5e023afd17c76ae9c296dca Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 13 May 2024 23:39:49 +0800 Subject: [PATCH 683/727] remove joint --- .../hosts/maya/plugins/publish/validate_animated_reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py index c9dcc662af..2ba2bff6fc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py @@ -16,7 +16,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, hosts = ["maya"] families = ["animation.fbx"] label = "Animated Reference Rig" - accepted_controllers = ["transform", "locator", "joint"] + accepted_controllers = ["transform", "locator"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] optional = False From aa6b254326f052b05f95fee20e91fad784a54c1a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 May 2024 11:14:06 +0200 Subject: [PATCH 684/727] do not try to fix own overrides --- server/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/__init__.py b/server/__init__.py index 82473927b6..79f505ccd5 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -26,10 +26,14 @@ class CoreAddon(BaseServerAddon): def _convert_imagio_configs_0_3_1(self, overrides): """Imageio config settings did change to profiles since 0.3.1. .""" imageio_overrides = overrides.get("imageio") or {} - if "ocio_config" not in imageio_overrides: + if ( + "ocio_config" not in imageio_overrides + or "filepath" not in imageio_overrides["ocio_config"] + ): return ocio_config = imageio_overrides.pop("ocio_config") + filepath = ocio_config["filepath"] if not filepath: return From 37cd670f40a89206023adc0da2e6b11aff0ed273 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 May 2024 11:40:47 +0200 Subject: [PATCH 685/727] removed deprecated function 'get_template_data_from_session' --- client/ayon_core/pipeline/context_tools.py | 30 ---------------------- 1 file changed, 30 deletions(-) diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index 33567d7280..c32d04c44c 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -459,36 +459,6 @@ def is_representation_from_latest(representation): ) -def get_template_data_from_session(session=None, settings=None): - """Template data for template fill from session keys. - - Args: - session (Union[Dict[str, str], None]): The Session to use. If not - provided use the currently active global Session. - settings (Optional[Dict[str, Any]]): Prepared studio or project - settings. - - Returns: - Dict[str, Any]: All available data from session. - """ - - if session is not None: - project_name = session["AYON_PROJECT_NAME"] - folder_path = session["AYON_FOLDER_PATH"] - task_name = session["AYON_TASK_NAME"] - host_name = session["AYON_HOST_NAME"] - else: - context = get_current_context() - project_name = context["project_name"] - folder_path = context["folder_path"] - task_name = context["task_name"] - host_name = get_current_host_name() - - return get_template_data_with_names( - project_name, folder_path, task_name, host_name, settings - ) - - def get_current_context_template_data(settings=None): """Prepare template data for current context. From 217cd06f9a7c94b4bc0e12c54e2da5c48e73855d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 May 2024 12:31:50 +0200 Subject: [PATCH 686/727] fix ruff comments --- .../traypublisher/plugins/create/create_editorial_package.py | 1 - .../traypublisher/plugins/publish/extract_editorial_pckg.py | 2 -- .../plugins/publish/validate_editorial_package.py | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py index 830cfa5564..82b109be28 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial_package.py @@ -8,7 +8,6 @@ from ayon_core.lib.attribute_definitions import ( FileDef, BoolDef, TextDef, - HiddenDef ) from ayon_core.hosts.traypublisher.api.plugin import TrayPublishCreator diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py index a4d8d2c6fb..6dd4e84704 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/extract_editorial_pckg.py @@ -56,8 +56,6 @@ class ExtractEditorialPckgConversion(publish.Extractor): if conversion_enabled and output_def["ext"]: transfers = self._convert_resources(output_def, transfers) - if not "transfers" in instance.data: - instance.data["transfers"] = [] instance.data["transfers"] = transfers source_to_rootless = self._get_resource_path_mapping(instance, diff --git a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py index 89594ce441..c63c4a6a73 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py +++ b/client/ayon_core/hosts/traypublisher/plugins/publish/validate_editorial_package.py @@ -22,8 +22,7 @@ class ValidateEditorialPackage(pyblish.api.InstancePlugin): def process(self, instance): editorial_pckg_data = instance.data.get("editorial_pckg") if not editorial_pckg_data: - raise PublishValidationError( - f"Editorial package not collected") + raise PublishValidationError("Editorial package not collected") folder_path = editorial_pckg_data["folder_path"] From 4dc893dfd571365337594ac2e764e69c230e352c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 May 2024 12:32:21 +0200 Subject: [PATCH 687/727] Nuke: Refactor metadata imprinting - Simplified code by removing 'author' attribute from metadata imprinting in various plugins. --- client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py | 4 ++-- client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py | 4 ++-- client/ayon_core/hosts/nuke/plugins/load/load_clip.py | 4 +--- client/ayon_core/hosts/nuke/plugins/load/load_effects.py | 2 -- client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py | 2 -- client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py | 2 -- client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py | 2 -- client/ayon_core/hosts/nuke/plugins/load/load_image.py | 3 +-- client/ayon_core/hosts/nuke/plugins/load/load_model.py | 4 ++-- .../ayon_core/hosts/nuke/plugins/load/load_script_precomp.py | 2 -- 10 files changed, 8 insertions(+), 21 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py index 7d823919dc..50af8a4eb9 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py @@ -62,7 +62,7 @@ class LoadBackdropNodes(load.LoaderPlugin): } # add attributes from the version to imprint to metadata knob - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # getting file path @@ -206,7 +206,7 @@ class LoadBackdropNodes(load.LoaderPlugin): "colorspaceInput": colorspace, } - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # adding nodes to node graph diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py index 14c54c3adc..3c7d4f3bb2 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py @@ -48,7 +48,7 @@ class AlembicCameraLoader(load.LoaderPlugin): "frameEnd": last, "version": version_entity["version"], } - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # getting file path @@ -123,7 +123,7 @@ class AlembicCameraLoader(load.LoaderPlugin): } # add attributes from the version to imprint to metadata knob - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # getting file path diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index df8f2ab018..22203a5978 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -197,7 +197,6 @@ class LoadClip(plugin.NukeLoader): "frameStart", "frameEnd", "source", - "author", "fps", "handleStart", "handleEnd", @@ -347,8 +346,7 @@ class LoadClip(plugin.NukeLoader): "source": version_attributes.get("source"), "handleStart": str(self.handle_start), "handleEnd": str(self.handle_end), - "fps": str(version_attributes.get("fps")), - "author": version_attributes.get("author") + "fps": str(version_attributes.get("fps")) } last_version_entity = ayon_api.get_last_version_by_product_id( diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py index a87c81295a..be7420fcf0 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py @@ -69,7 +69,6 @@ class LoadEffects(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] @@ -189,7 +188,6 @@ class LoadEffects(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps", ]: data_imprint[k] = version_attributes[k] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py index 8fa1347598..9bb430b37b 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py @@ -69,7 +69,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] @@ -192,7 +191,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py index 95f85bacfc..57d00795ae 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py @@ -71,7 +71,6 @@ class LoadGizmo(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] @@ -139,7 +138,6 @@ class LoadGizmo(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py index 3112e27811..ed2b1ec458 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -73,7 +73,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] @@ -145,7 +144,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_image.py b/client/ayon_core/hosts/nuke/plugins/load/load_image.py index d825b621fc..b5fccd8a0d 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_image.py @@ -133,7 +133,7 @@ class LoadImage(load.LoaderPlugin): "version": version_entity["version"], "colorspace": colorspace, } - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes.get(k, str(None)) r["tile_color"].setValue(int("0x4ecd25ff", 16)) @@ -207,7 +207,6 @@ class LoadImage(load.LoaderPlugin): "colorspace": version_attributes.get("colorSpace"), "source": version_attributes.get("source"), "fps": str(version_attributes.get("fps")), - "author": version_attributes.get("author") } # change color of node diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_model.py b/client/ayon_core/hosts/nuke/plugins/load/load_model.py index 0326e0a4fc..40862cd1e0 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_model.py @@ -47,7 +47,7 @@ class AlembicModelLoader(load.LoaderPlugin): "version": version_entity["version"] } # add attributes from the version to imprint to metadata knob - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # getting file path @@ -130,7 +130,7 @@ class AlembicModelLoader(load.LoaderPlugin): } # add additional metadata from the version to imprint to Avalon knob - for k in ["source", "author", "fps"]: + for k in ["source", "fps"]: data_imprint[k] = version_attributes[k] # getting file path diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py index 3e554f9d3b..d6699be164 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py @@ -55,7 +55,6 @@ class LinkAsGroup(load.LoaderPlugin): "handleStart", "handleEnd", "source", - "author", "fps" ]: data_imprint[k] = version_attributes[k] @@ -131,7 +130,6 @@ class LinkAsGroup(load.LoaderPlugin): "colorspace": version_attributes.get("colorSpace"), "source": version_attributes.get("source"), "fps": version_attributes.get("fps"), - "author": version_attributes.get("author") } # Update the imprinted representation From 7f4385c9a23020b044a7a34a3de4aec6f3491543 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 May 2024 12:33:44 +0200 Subject: [PATCH 688/727] remove unused imports --- server_addon/traypublisher/server/settings/publish_plugins.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py index dc659f6110..99a0bbf107 100644 --- a/server_addon/traypublisher/server/settings/publish_plugins.py +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -1,10 +1,6 @@ -from pydantic import validator - from ayon_server.settings import ( BaseSettingsModel, SettingsField, - task_types_enum, - ensure_unique_names ) From 53b25cb77e65a48ca10149d2eff743e6cf13fd3c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 14 May 2024 13:28:41 +0200 Subject: [PATCH 689/727] fix import --- client/ayon_core/modules/royalrender/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/royalrender/api.py b/client/ayon_core/modules/royalrender/api.py index a69f88c43c..ef715811c5 100644 --- a/client/ayon_core/modules/royalrender/api.py +++ b/client/ayon_core/modules/royalrender/api.py @@ -7,7 +7,7 @@ from ayon_core.lib import Logger, run_subprocess, AYONSettingsRegistry from ayon_core.lib.vendor_bin_utils import find_tool_in_custom_paths from .rr_job import SubmitFile -from .rr_job import RRjob, SubmitterParameter # noqa F401 +from .rr_job import RRJob, SubmitterParameter # noqa F401 class Api: From c5b0a1e0cebf5c4c6cbf8768d41ead634ba07a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 14 May 2024 14:19:00 +0200 Subject: [PATCH 690/727] :bug: fix merge --- client/ayon_core/plugins/publish/integrate.py | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 33dc3024d1..865b566e6e 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -108,75 +108,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder -<<<<<<< enhancement/AY-4138_remove-explicit-products-type-list -- Incoming Change -======= - families = ["workfile", - "pointcache", - "pointcloud", - "proxyAbc", - "camera", - "animation", - "model", - "maxScene", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "ass", - "assProxy", - "vdbcache", - "scene", - "vrayproxy", - "vrayscene_layer", - "render", - "prerender", - "imagesequence", - "review", - "rendersetup", - "rig", - "plate", - "look", - "ociolook", - "audio", - "yetiRig", - "yeticache", - "nukenodes", - "gizmo", - "source", - "matchmove", - "image", - "assembly", - "fbx", - "gltf", - "textures", - "action", - "harmony.template", - "harmony.palette", - "editorial", - "background", - "camerarig", - "redshiftproxy", - "effect", - "xgen", - "hda", - "usd", - "staticMesh", - "skeletalMesh", - "mvLook", - "mvUsd", - "mvUsdComposition", - "mvUsdOverride", - "online", - "uasset", - "blendScene", - "yeticacheUE", - "tycache", - "csv_ingest_file", - "editorial_pckg", - "render.local.hou", - ] ->>>>>>> develop -- Current Change default_template_name = "publish" # Representation context keys that should always be written to From 1b05428eb73087b222890509e0a12c0e59a90654 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 10:17:30 +0200 Subject: [PATCH 691/727] expand default settings for readability --- server/settings/main.py | 42 ++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index d1cee32afa..40e16e7e91 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -268,26 +268,50 @@ DEFAULT_VALUES = { }, "studio_name": "", "studio_code": "", - "environments": '{\n"STUDIO_SW": {\n "darwin": "/mnt/REPO_SW",\n "linux": "/mnt/REPO_SW",\n "windows": "P:/REPO_SW"\n }\n}', + "environments": json.dumps( + { + "STUDIO_SW": { + "darwin": "/mnt/REPO_SW", + "linux": "/mnt/REPO_SW", + "windows": "P:/REPO_SW" + } + }, + indent=4 + ), "tools": DEFAULT_TOOLS_VALUES, - "version_start_category": {"profiles": []}, + "version_start_category": { + "profiles": [] + }, "publish": DEFAULT_PUBLISH_VALUES, "project_folder_structure": json.dumps( { "__project_root__": { "prod": {}, "resources": { - "footage": {"plates": {}, "offline": {}}, + "footage": { + "plates": {}, + "offline": {} + }, "audio": {}, - "art_dept": {}, + "art_dept": {} }, "editorial": {}, - "assets": {"characters": {}, "locations": {}}, - "shots": {}, + "assets": { + "characters": {}, + "locations": {} + }, + "shots": {} } }, - indent=4, + indent=4 ), - "project_plugins": {"windows": [], "darwin": [], "linux": []}, - "project_environments": "{}", + "project_plugins": { + "windows": [], + "darwin": [], + "linux": [] + }, + "project_environments": json.dumps( + {}, + indent=4 + ) } From c6a396de4f8850a1acf56125a82a8248a6666fc4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 May 2024 17:15:07 +0200 Subject: [PATCH 692/727] AY-1110 - updated variable to be more descriptive Bump up version --- server_addon/deadline/package.py | 2 +- server_addon/deadline/server/settings/main.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py index 25ba1c1166..e26734c813 100644 --- a/server_addon/deadline/package.py +++ b/server_addon/deadline/package.py @@ -1,3 +1,3 @@ name = "deadline" title = "Deadline" -version = "0.1.11" +version = "0.1.12" diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 5d42b9b1ef..47ad72a86f 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -38,10 +38,9 @@ class ServerItemSubmodel(BaseSettingsModel): name: str = SettingsField(title="Name") value: str = SettingsField(title="Url") require_authentication: bool = SettingsField( - False, - title="Require authentication") - ssl: bool = SettingsField(False, - title="SSL") + False, title="Require authentication") + not_verify_ssl: bool = SettingsField( + False, title="Don't verify SSL") class DeadlineSettings(BaseSettingsModel): @@ -78,7 +77,7 @@ DEFAULT_VALUES = { "name": "default", "value": "http://127.0.0.1:8082", "require_authentication": False, - "ssl": False + "not_verify_ssl": False } ], "deadline_server": "default", From e9697884e98ce02dc603fdae81155e114370e1a3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 May 2024 17:18:13 +0200 Subject: [PATCH 693/727] AY-1110 - use field from Setttings instead of OPENPYPE_DONT_VERIFY_SSL --- .../deadline/abstract_submit_deadline.py | 35 +++++++------------ .../publish/collect_user_credentials.py | 3 ++ .../publish/submit_blender_deadline.py | 5 +-- .../publish/submit_celaction_deadline.py | 6 ++-- .../plugins/publish/submit_fusion_deadline.py | 3 +- .../plugins/publish/submit_max_deadline.py | 16 ++++++--- .../plugins/publish/submit_maya_deadline.py | 18 +++++++--- .../plugins/publish/submit_nuke_deadline.py | 8 +++-- .../publish/submit_publish_cache_job.py | 5 +-- .../plugins/publish/submit_publish_job.py | 5 +-- 10 files changed, 62 insertions(+), 42 deletions(-) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index 00e51100bc..564966b6a0 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -29,15 +29,11 @@ from ayon_core.pipeline.publish.lib import ( JSONDecodeError = getattr(json.decoder, "JSONDecodeError", ValueError) -# TODO both 'requests_post' and 'requests_get' should not set 'verify' based -# on environment variable. This should be done in a more controlled way, -# e.g. each deadline url could have checkbox to enabled/disable -# ssl verification. def requests_post(*args, **kwargs): """Wrap request post method. - Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline server is + Disabling SSL certificate validation if ``verify`` kwarg is set to False. + This is useful when Deadline server is running with self-signed certificates and its certificate is not added to trusted certificates on client machines. @@ -46,10 +42,6 @@ def requests_post(*args, **kwargs): of defense SSL is providing, and it is not recommended. """ - if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", - True) else True # noqa - auth = kwargs.get("auth") if auth: kwargs["auth"] = tuple(auth) # explicit cast to tuple @@ -61,8 +53,8 @@ def requests_post(*args, **kwargs): def requests_get(*args, **kwargs): """Wrap request get method. - Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline server is + Disabling SSL certificate validation if ``verify`` kwarg is set to False. + This is useful when Deadline server is running with self-signed certificates and its certificate is not added to trusted certificates on client machines. @@ -71,9 +63,6 @@ def requests_get(*args, **kwargs): of defense SSL is providing, and it is not recommended. """ - if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", - True) else True # noqa auth = kwargs.get("auth") if auth: kwargs["auth"] = tuple(auth) @@ -466,7 +455,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.aux_files = self.get_aux_files() auth = instance.data["deadline"]["auth"] - job_id = self.process_submission(auth) + verify = instance.data["deadline"]["verify"] + job_id = self.process_submission(auth, verify) self.log.info("Submitted job to Deadline: {}.".format(job_id)) # TODO: Find a way that's more generic and not render type specific @@ -479,10 +469,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, job_info=render_job_info, plugin_info=render_plugin_info ) - render_job_id = self.submit(payload, auth) + render_job_id = self.submit(payload, auth, verify) self.log.info("Render job id: %s", render_job_id) - def process_submission(self, auth=None): + def process_submission(self, auth=None, verify=True): """Process data for submission. This takes Deadline JobInfo, PluginInfo, AuxFile, creates payload @@ -493,7 +483,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ payload = self.assemble_payload() - return self.submit(payload, auth) + return self.submit(payload, auth, verify) @abstractmethod def get_job_info(self): @@ -583,7 +573,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, "AuxFiles": aux_files or self.aux_files } - def submit(self, payload, auth): + def submit(self, payload, auth, verify): """Submit payload to Deadline API end-point. This takes payload in the form of JSON file and POST it to @@ -592,6 +582,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, Args: payload (dict): dict to become json in deadline submission. auth (tuple): (username, password) + verify (bool): verify SSL certificate if present Returns: str: resulting Deadline job id. @@ -601,8 +592,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ url = "{}/api/jobs".format(self._deadline_url) - response = requests_post(url, json=payload, - auth=auth) + response = requests_post( + url, json=payload, auth=auth, verify=verify) if not response.ok: self.log.error("Submission failed!") self.log.error(response.status_code) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 5d03523c89..99d75ecb9e 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -76,6 +76,9 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): ) instance.data["deadline"]["auth"] = None + instance.data["deadline"]["verify"] = ( + not deadline_info["not_verify_ssl"]) + if not deadline_info["require_authentication"]: return # TODO import 'get_addon_site_settings' when available diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py index f5805beb5c..311dbcedd5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -174,8 +174,9 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance.data["toBeRenderedOn"] = "deadline" payload = self.assemble_payload() - return self.submit(payload, - auth=instance.data["deadline"]["auth"]) + auth = instance.data["deadline"]["auth"] + verify = instance.data["deadline"]["verify"] + return self.submit(payload, auth=auth, verify=verify) def from_published_scene(self): """ diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index 2220442dac..a17bf0c3ef 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -193,9 +193,11 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): self.expected_files(instance, render_path) self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - + auth = instance.data["deadline"]["auth"] + verify = instance.data["deadline"]["verify"] response = requests_post(self.deadline_url, json=payload, - auth=instance.data["deadline"]["require_authentication"]) + auth=auth, + verify=verify) if not response.ok: self.log.error( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index e9b93a47cd..6c70119628 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -242,7 +242,8 @@ class FusionSubmitDeadline( # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) auth = instance.data["deadline"]["auth"] - response = requests_post(url, json=payload, auth=auth) + verify = instance.data["deadline"]["verify"] + response = requests_post(url, json=payload, auth=auth, verify=verify) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py index e9f6c382c5..ababb01285 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py @@ -181,19 +181,27 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Submitting 3dsMax render..") project_settings = instance.context.data["project_settings"] + auth = instance.data["deadline"]["auth"] + verify = instance.data["deadline"]["verify"] if instance.data.get("multiCamera"): self.log.debug("Submitting jobs for multiple cameras..") payload = self._use_published_name_for_multiples( payload_data, project_settings) job_infos, plugin_infos = payload for job_info, plugin_info in zip(job_infos, plugin_infos): - self.submit(self.assemble_payload(job_info, plugin_info), - instance.data["deadline"]["auth"]) + self.submit( + self.assemble_payload(job_info, plugin_info), + auth=auth, + verify=verify + ) else: payload = self._use_published_name(payload_data, project_settings) job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info), - instance.data["deadline"]["auth"]) + self.submit( + self.assemble_payload(job_info, plugin_info), + auth=auth, + verify=verify + ) def _use_published_name(self, data, project_settings): # Not all hosts can import these modules. diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py index 250dc8b7ea..f1bc1cb2be 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -292,7 +292,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return plugin_payload - def process_submission(self, auth=None): + def process_submission(self, auth=None, verify=True): from maya import cmds instance = self._instance @@ -332,8 +332,10 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if "vrayscene" in instance.data["families"]: self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) + export_job = self.submit(vray_export_payload, - instance.data["deadline"]["auth"]) + auth=auth, + verify=verify) payload = self._get_vray_render_payload(payload_data) @@ -353,7 +355,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Submit main render job job_info, plugin_info = payload self.submit(self.assemble_payload(job_info, plugin_info), - instance.data["deadline"]["auth"]) + auth=auth, + verify=verify) def _tile_render(self, payload): """Submit as tile render per frame with dependent assembly jobs.""" @@ -557,13 +560,18 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Submit assembly jobs assembly_job_ids = [] num_assemblies = len(assembly_payloads) + auth = instance.data["deadline"]["auth"] + verify = instance.data["deadline"]["verify"] for i, payload in enumerate(assembly_payloads): self.log.debug( "submitting assembly job {} of {}".format(i + 1, num_assemblies) ) - assembly_job_id = self.submit(payload, - instance.data["deadline"]["auth"]) + assembly_job_id = self.submit( + payload, + auth=auth, + verify=verify + ) assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index ef744ae1e1..db35c2ae67 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -424,8 +424,12 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) auth = instance.data["deadline"]["auth"] - response = requests_post(self.deadline_url, json=payload, timeout=10, - auth=auth) + verify = instance.data["deadline"]["verify"] + response = requests_post(self.deadline_url, + json=payload, + timeout=10, + auth=auth, + verify=verify) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index ce15eda9a0..103f1355da 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -210,8 +210,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, url = "{}/api/jobs".format(self.deadline_url) auth = instance.data["deadline"]["auth"] - response = requests_post(url, json=payload, timeout=10, - auth=auth) + verify = instance.data["deadline"]["verify"] + response = requests_post( + url, json=payload, timeout=10, auth=auth, verify=verify) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 0f505dce78..64313c5c4d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -304,8 +304,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, url = "{}/api/jobs".format(self.deadline_url) auth = instance.data["deadline"]["auth"] - response = requests_post(url, json=payload, timeout=10, - auth=auth) + verify = instance.data["deadline"]["verify"] + response = requests_post( + url, json=payload, timeout=10, auth=auth, verify=verify) if not response.ok: raise Exception(response.text) From 61fcc3ddecc2121d4947344215cce3dbff8caff5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 May 2024 17:47:37 +0200 Subject: [PATCH 694/727] AY-1110 - use get_addon_site_settings from ayon_api Method was not available previously --- .../deadline/plugins/publish/collect_user_credentials.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 99d75ecb9e..30e3703b58 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -12,7 +12,7 @@ Provides: """ import pyblish.api -from ayon_api import get_server_api_connection +from ayon_api import get_addon_site_settings from ayon_core.modules.deadline.deadline_module import DeadlineModule from ayon_core.modules.deadline import __version__ @@ -81,9 +81,8 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): if not deadline_info["require_authentication"]: return - # TODO import 'get_addon_site_settings' when available - # in public 'ayon_api' - local_settings = get_server_api_connection().get_addon_site_settings( + + local_settings = get_addon_site_settings( DeadlineModule.name, __version__) local_settings = local_settings["local_settings"] for server_info in local_settings: From b300db793a9af3d93e093df074dbf07685440f82 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:28:19 +0200 Subject: [PATCH 695/727] move deprecated functions at the end of file --- client/ayon_core/pipeline/colorspace.py | 215 ++++++++++++------------ 1 file changed, 106 insertions(+), 109 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 3503d0c534..c081b58752 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -195,17 +195,6 @@ def get_colorspace_name_from_filepath( return colorspace_name -# TODO: remove this in future - backward compatibility -@deprecated("get_imageio_file_rules_colorspace_from_filepath") -def get_imageio_colorspace_from_filepath(*args, **kwargs): - return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) - -# TODO: remove this in future - backward compatibility -@deprecated("get_imageio_file_rules_colorspace_from_filepath") -def get_colorspace_from_filepath(*args, **kwargs): - return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) - - def get_imageio_file_rules_colorspace_from_filepath( filepath, host_name, @@ -394,21 +383,6 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): return True -# TODO: remove this in future - backward compatibility -@deprecated("_get_wrapped_with_subprocess") -def get_data_subprocess(config_path, data_type): - """[Deprecated] Get data via subprocess - - Wrapper for Python 2 hosts. - - Args: - config_path (str): path leading to config.ocio file - """ - return _get_wrapped_with_subprocess( - "config", data_type, in_path=config_path, - ) - - def _get_wrapped_with_subprocess(command_group, command, **kwargs): """Get data via subprocess @@ -673,24 +647,6 @@ def get_colorspaces_enumerator_items( return labeled_colorspaces -# TODO: remove this in future - backward compatibility -@deprecated("_get_wrapped_with_subprocess") -def get_colorspace_data_subprocess(config_path): - """[Deprecated] Get colorspace data via subprocess - - Wrapper for Python 2 hosts. - - Args: - config_path (str): path leading to config.ocio file - - Returns: - dict: colorspace and family in couple - """ - return _get_wrapped_with_subprocess( - "config", "get_colorspace", in_path=config_path - ) - - def get_ocio_config_views(config_path): """Get all viewer data @@ -716,71 +672,6 @@ def get_ocio_config_views(config_path): return _get_views_data(config_path) -# TODO: remove this in future - backward compatibility -@deprecated("_get_wrapped_with_subprocess") -def get_views_data_subprocess(config_path): - """[Deprecated] Get viewers data via subprocess - - Wrapper for Python 2 hosts. - - Args: - config_path (str): path leading to config.ocio file - - Returns: - dict: `display/viewer` and viewer data - """ - return _get_wrapped_with_subprocess( - "config", "get_views", in_path=config_path - ) - - -@deprecated("get_imageio_config_preset") -def get_imageio_config( - project_name, - host_name, - project_settings=None, - anatomy_data=None, - anatomy=None, - env=None -): - """Returns config data from settings - - Config path is formatted in `path` key - and original settings input is saved into `template` key. - - Deprecated: - Deprecated since '0.3.1' . Use `get_imageio_config_preset` instead. - - Args: - project_name (str): project name - host_name (str): host name - project_settings (Optional[dict]): Project settings. - anatomy_data (Optional[dict]): anatomy formatting data. - anatomy (Optional[Anatomy]): Anatomy object. - env (Optional[dict]): Environment variables. - - Returns: - dict: config path data or empty dict - - """ - if not anatomy_data: - from .context_tools import get_current_context_template_data - anatomy_data = get_current_context_template_data() - - task_name = anatomy_data.get("task", {}).get("name") - folder_path = anatomy_data.get("folder", {}).get("path") - return get_imageio_config_preset( - project_name, - folder_path, - task_name, - host_name, - anatomy=anatomy, - project_settings=project_settings, - template_data=anatomy_data, - env=env, - ) - - def _get_global_config_data( project_name, host_name, @@ -1437,3 +1328,109 @@ def get_current_context_imageio_config_preset( template_data=template_data, env=env, ) + + +# --- Deprecated functions --- +@deprecated("get_imageio_file_rules_colorspace_from_filepath") +def get_imageio_colorspace_from_filepath(*args, **kwargs): + return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) + + +@deprecated("get_imageio_file_rules_colorspace_from_filepath") +def get_colorspace_from_filepath(*args, **kwargs): + return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) + + +@deprecated("_get_wrapped_with_subprocess") +def get_colorspace_data_subprocess(config_path): + """[Deprecated] Get colorspace data via subprocess + + Wrapper for Python 2 hosts. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: colorspace and family in couple + """ + return _get_wrapped_with_subprocess( + "config", "get_colorspace", in_path=config_path + ) + + +@deprecated("_get_wrapped_with_subprocess") +def get_views_data_subprocess(config_path): + """[Deprecated] Get viewers data via subprocess + + Wrapper for Python 2 hosts. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: `display/viewer` and viewer data + """ + return _get_wrapped_with_subprocess( + "config", "get_views", in_path=config_path + ) + + +@deprecated("_get_wrapped_with_subprocess") +def get_data_subprocess(config_path, data_type): + """[Deprecated] Get data via subprocess + + Wrapper for Python 2 hosts. + + Args: + config_path (str): path leading to config.ocio file + """ + return _get_wrapped_with_subprocess( + "config", data_type, in_path=config_path, + ) + + +@deprecated("get_imageio_config_preset") +def get_imageio_config( + project_name, + host_name, + project_settings=None, + anatomy_data=None, + anatomy=None, + env=None +): + """Returns config data from settings + + Config path is formatted in `path` key + and original settings input is saved into `template` key. + + Deprecated: + Deprecated since '0.3.1' . Use `get_imageio_config_preset` instead. + + Args: + project_name (str): project name + host_name (str): host name + project_settings (Optional[dict]): Project settings. + anatomy_data (Optional[dict]): anatomy formatting data. + anatomy (Optional[Anatomy]): Anatomy object. + env (Optional[dict]): Environment variables. + + Returns: + dict: config path data or empty dict + + """ + if not anatomy_data: + from .context_tools import get_current_context_template_data + anatomy_data = get_current_context_template_data() + + task_name = anatomy_data.get("task", {}).get("name") + folder_path = anatomy_data.get("folder", {}).get("path") + return get_imageio_config_preset( + project_name, + folder_path, + task_name, + host_name, + anatomy=anatomy, + project_settings=project_settings, + template_data=anatomy_data, + env=env, + ) From 4d398e21371ff8a840d14f35887a4107bbcf46f1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:32:04 +0200 Subject: [PATCH 696/727] added functions that are using 'PyOpenColorIO' --- client/ayon_core/pipeline/colorspace.py | 170 ++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index c081b58752..562e698f75 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -1294,6 +1294,176 @@ def _get_display_view_colorspace_subprocess(config_path, display, view): return json.load(f) +# --- Implementation of logic using 'PyOpenColorIO' --- +def _get_ocio_config(config_path): + """Helper function to create OCIO config object. + + Args: + config_path (str): Path to config. + + Returns: + PyOpenColorIO.Config: OCIO config for the confing path. + + """ + import PyOpenColorIO + + config_path = os.path.abspath(config_path) + + if not os.path.isfile(config_path): + raise IOError("Input path should be `config.ocio` file") + + return PyOpenColorIO.Config.CreateFromFile(config_path) + + +def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): + """Return found colorspace data found in v2 file rules. + + Args: + config_path (str): path string leading to config.ocio + filepath (str): path string leading to v2 file rules + + Raises: + IOError: Input config does not exist. + + Returns: + dict: aggregated available colorspaces + + """ + config = _get_ocio_config(config_path) + + # TODO: use `parseColorSpaceFromString` instead if ocio v1 + return config.getColorSpaceFromFilepath(str(filepath)) + + +def _get_config_version_data(config_path): + """Return major and minor version info. + + Args: + config_path (str): path string leading to config.ocio + + Raises: + IOError: Input config does not exist. + + Returns: + dict: minor and major keys with values + + """ + config = _get_ocio_config(config_path) + + return { + "major": config.getMajorVersion(), + "minor": config.getMinorVersion() + } + + +def _get_display_view_colorspace_name(config_path, display, view): + """Returns the colorspace attribute of the (display, view) pair. + + Args: + config_path (str): path string leading to config.ocio + display (str): display name e.g. "ACES" + view (str): view name e.g. "sRGB" + + Raises: + IOError: Input config does not exist. + + Returns: + str: view color space name e.g. "Output - sRGB" + + """ + config = _get_ocio_config(config_path) + return config.getDisplayViewColorSpaceName(display, view) + + +def _get_ocio_config_colorspaces(config_path): + """Return all found colorspace data. + + Args: + config_path (str): path string leading to config.ocio + + Raises: + IOError: Input config does not exist. + + Returns: + dict: aggregated available colorspaces + + """ + config = _get_ocio_config(config_path) + + colorspace_data = { + "roles": {}, + "colorspaces": { + color.getName(): { + "family": color.getFamily(), + "categories": list(color.getCategories()), + "aliases": list(color.getAliases()), + "equalitygroup": color.getEqualityGroup(), + } + for color in config.getColorSpaces() + }, + "displays_views": { + f"{view} ({display})": { + "display": display, + "view": view + + } + for display in config.getDisplays() + for view in config.getViews(display) + }, + "looks": {} + } + + # add looks + looks = config.getLooks() + if looks: + colorspace_data["looks"] = { + look.getName(): {"process_space": look.getProcessSpace()} + for look in looks + } + + # add roles + roles = config.getRoles() + if roles: + colorspace_data["roles"] = { + role: {"colorspace": colorspace} + for (role, colorspace) in roles + } + + return colorspace_data + + +def _get_ocio_config_views(config_path): + """Return all found viewer data. + + Args: + config_path (str): path string leading to config.ocio + + Raises: + IOError: Input config does not exist. + + Returns: + dict: aggregated available viewers + + """ + config = _get_ocio_config(config_path) + + output = {} + for display in config.getDisplays(): + for view in config.getViews(display): + colorspace = config.getDisplayViewColorSpaceName(display, view) + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa + if colorspace == "": + colorspace = display + + output[f"{display}/{view}"] = { + "display": display, + "view": view, + "colorspace": colorspace + } + + return output + + # --- Current context functions --- def get_current_context_imageio_config_preset( anatomy=None, From b0340f4f3b94e1dae75305410dda2c33c70ad03a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:35:42 +0200 Subject: [PATCH 697/727] renamed 'compatibility_check' to 'has_compatible_ocio_package' --- client/ayon_core/pipeline/colorspace.py | 58 ++++++++++++++++--------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 562e698f75..a503c37101 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -107,6 +107,27 @@ def _make_temp_json_file(): os.remove(temporary_json_filepath) +def has_compatible_ocio_package(): + """Current process has available compatible 'PyOpenColorIO'. + + Returns: + bool: True if compatible package is available. + + """ + if CachedData.has_compatible_ocio_package is not None: + return CachedData.has_compatible_ocio_package + + try: + import PyOpenColorIO # noqa: F401 + # TODO validate 'PyOpenColorIO' version + CachedData.has_compatible_ocio_package = True + except ImportError: + CachedData.has_compatible_ocio_package = False + + # compatible + return CachedData.has_compatible_ocio_package + + def get_ocio_config_script_path(): """Get path to ocio wrapper script @@ -261,7 +282,7 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath): Returns: Union[str, None]: matching colorspace name """ - if not compatibility_check(): + if not has_compatible_ocio_package(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess result_data = _get_wrapped_with_subprocess( @@ -418,28 +439,12 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): return json.load(f_) -# TODO: this should be part of ocio_wrapper.py -def compatibility_check(): - """Making sure PyOpenColorIO is importable""" - if CachedData.has_compatible_ocio_package is not None: - return CachedData.has_compatible_ocio_package - - try: - import PyOpenColorIO # noqa: F401 - CachedData.has_compatible_ocio_package = True - except ImportError: - CachedData.has_compatible_ocio_package = False - - # compatible - return CachedData.has_compatible_ocio_package - - # TODO: this should be part of ocio_wrapper.py def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" if not CachedData.config_version_data.get(config_path): - if compatibility_check(): + if has_compatible_ocio_package(): # TODO: refactor this so it is not imported but part of this file from ayon_core.scripts.ocio_wrapper import _get_version_data @@ -479,7 +484,7 @@ def get_ocio_config_colorspaces(config_path): dict: colorspace and family in couple """ if not CachedData.ocio_config_colorspaces.get(config_path): - if not compatibility_check(): + if not has_compatible_ocio_package(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess config_colorspaces = _get_wrapped_with_subprocess( @@ -659,7 +664,7 @@ def get_ocio_config_views(config_path): Returns: dict: `display/viewer` and viewer data """ - if not compatibility_check(): + if not has_compatible_ocio_package(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess return _get_wrapped_with_subprocess( @@ -1250,7 +1255,7 @@ def get_display_view_colorspace_name(config_path, display, view): str: View color space name. e.g. "Output - sRGB" """ - if not compatibility_check(): + if not has_compatible_ocio_package(): # python environment is not compatible with PyOpenColorIO # needs to be run in subprocess return _get_display_view_colorspace_subprocess( @@ -1501,6 +1506,17 @@ def get_current_context_imageio_config_preset( # --- Deprecated functions --- +@deprecated("has_compatible_ocio_package") +def compatibility_check(): + """Making sure PyOpenColorIO is importable + + Deprecated: + Deprecated since '0.3.2'. Use `has_compatible_ocio_package` instead. + """ + + return has_compatible_ocio_package() + + @deprecated("get_imageio_file_rules_colorspace_from_filepath") def get_imageio_colorspace_from_filepath(*args, **kwargs): return get_imageio_file_rules_colorspace_from_filepath(*args, **kwargs) From c668ab49397b564dab51a9f901ef9e76a4974d87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:38:41 +0200 Subject: [PATCH 698/727] modified ocio wrapper to use new functions --- client/ayon_core/scripts/ocio_wrapper.py | 480 +++++++---------------- 1 file changed, 142 insertions(+), 338 deletions(-) diff --git a/client/ayon_core/scripts/ocio_wrapper.py b/client/ayon_core/scripts/ocio_wrapper.py index 0a78e33c1f..9cbab32956 100644 --- a/client/ayon_core/scripts/ocio_wrapper.py +++ b/client/ayon_core/scripts/ocio_wrapper.py @@ -1,28 +1,31 @@ """OpenColorIO Wrapper. -Only to be interpreted by Python 3. It is run in subprocess in case -Python 2 hosts needs to use it. Or it is used as module for Python 3 -processing. - -Providing functionality: -- get_colorspace - console command - python 2 - - returning all available color spaces - found in input config path. -- _get_colorspace_data - python 3 - module function - - returning all available colorspaces - found in input config path. -- get_views - console command - python 2 - - returning all available viewers - found in input config path. -- _get_views_data - python 3 - module function - - returning all available viewers - found in input config path. +Receive OpenColorIO information and store it in JSON format for processed +that don't have access to OpenColorIO or their version of OpenColorIO is +not compatible. """ -import click import json from pathlib import Path -import PyOpenColorIO as ocio + +import click + +from ayon_core.pipeline.colorspace import ( + has_compatible_ocio_package, + get_display_view_colorspace_name, + get_config_file_rules_colorspace_from_filepath, + get_config_version_data, + get_ocio_config_views, + get_ocio_config_colorspaces, +) + + +def _save_output_to_json_file(output, output_path): + json_path = Path(output_path) + with open(json_path, "w") as stream: + json.dump(output, stream) + + print(f"Data are saved to '{json_path}'") @click.group() @@ -51,383 +54,184 @@ def colorspace(): @config.command( - name="get_colorspace", - help=( - "return all colorspaces from config file " - "--path input arg is required" - ) -) -@click.option("--in_path", required=True, - help="path where to read ocio config file", - type=click.Path(exists=True)) -@click.option("--out_path", required=True, - help="path where to write output json file", - type=click.Path()) -def get_colorspace(in_path, out_path): + name="get_ocio_config_colorspaces", + help="return all colorspaces from config file") +@click.option( + "--config_path", + required=True, + help="OCIO config path to read ocio config file.", + type=click.Path(exists=True)) +@click.option( + "--output_path", + required=True, + help="path where to write output json file", + type=click.Path()) +def _get_ocio_config_colorspaces(config_path, output_path): """Aggregate all colorspace to file. - Python 2 wrapped console command - Args: - in_path (str): config file path string - out_path (str): temp json file path string + config_path (str): config file path string + output_path (str): temp json file path string Example of use: > pyton.exe ./ocio_wrapper.py config get_colorspace - --in_path= --out_path= + --config_path --output_path """ - json_path = Path(out_path) - - out_data = _get_colorspace_data(in_path) - - with open(json_path, "w") as f_: - json.dump(out_data, f_) - - print(f"Colorspace data are saved to '{json_path}'") - - -def _get_colorspace_data(config_path): - """Return all found colorspace data. - - Args: - config_path (str): path string leading to config.ocio - - Raises: - IOError: Input config does not exist. - - Returns: - dict: aggregated available colorspaces - """ - config_path = Path(config_path) - - if not config_path.is_file(): - raise IOError( - f"Input path `{config_path}` should be `config.ocio` file") - - config = ocio.Config().CreateFromFile(str(config_path)) - - colorspace_data = { - "roles": {}, - "colorspaces": { - color.getName(): { - "family": color.getFamily(), - "categories": list(color.getCategories()), - "aliases": list(color.getAliases()), - "equalitygroup": color.getEqualityGroup(), - } - for color in config.getColorSpaces() - }, - "displays_views": { - f"{view} ({display})": { - "display": display, - "view": view - - } - for display in config.getDisplays() - for view in config.getViews(display) - }, - "looks": {} - } - - # add looks - looks = config.getLooks() - if looks: - colorspace_data["looks"] = { - look.getName(): {"process_space": look.getProcessSpace()} - for look in looks - } - - # add roles - roles = config.getRoles() - if roles: - colorspace_data["roles"] = { - role: {"colorspace": colorspace} - for (role, colorspace) in roles - } - - return colorspace_data + _save_output_to_json_file( + get_ocio_config_colorspaces(config_path), + output_path + ) @config.command( - name="get_views", - help=( - "return all viewers from config file " - "--path input arg is required" - ) -) -@click.option("--in_path", required=True, - help="path where to read ocio config file", - type=click.Path(exists=True)) -@click.option("--out_path", required=True, - help="path where to write output json file", - type=click.Path()) -def get_views(in_path, out_path): + name="get_ocio_config_views", + help="All viewers from config file") +@click.option( + "--config_path", + required=True, + help="OCIO config path to read ocio config file.", + type=click.Path(exists=True)) +@click.option( + "--output_path", + required=True, + help="path where to write output json file", + type=click.Path()) +def _get_ocio_config_views(config_path, output_path): """Aggregate all viewers to file. - Python 2 wrapped console command - Args: - in_path (str): config file path string - out_path (str): temp json file path string + config_path (str): config file path string + output_path (str): temp json file path string Example of use: > pyton.exe ./ocio_wrapper.py config get_views \ - --in_path= --out_path= + --config_path --output """ - json_path = Path(out_path) - - out_data = _get_views_data(in_path) - - with open(json_path, "w") as f_: - json.dump(out_data, f_) - - print(f"Viewer data are saved to '{json_path}'") - - -def _get_views_data(config_path): - """Return all found viewer data. - - Args: - config_path (str): path string leading to config.ocio - - Raises: - IOError: Input config does not exist. - - Returns: - dict: aggregated available viewers - """ - config_path = Path(config_path) - - if not config_path.is_file(): - raise IOError("Input path should be `config.ocio` file") - - config = ocio.Config().CreateFromFile(str(config_path)) - - data_ = {} - for display in config.getDisplays(): - for view in config.getViews(display): - colorspace = config.getDisplayViewColorSpaceName(display, view) - # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa - if colorspace == "": - colorspace = display - - data_[f"{display}/{view}"] = { - "display": display, - "view": view, - "colorspace": colorspace - } - - return data_ + _save_output_to_json_file( + get_ocio_config_views(config_path), + output_path + ) @config.command( - name="get_version", - help=( - "return major and minor version from config file " - "--config_path input arg is required" - "--out_path input arg is required" - ) -) -@click.option("--config_path", required=True, - help="path where to read ocio config file", - type=click.Path(exists=True)) -@click.option("--out_path", required=True, - help="path where to write output json file", - type=click.Path()) -def get_version(config_path, out_path): + name="get_config_version_data", + help="Get major and minor version from config file") +@click.option( + "--config_path", + required=True, + help="OCIO config path to read ocio config file.", + type=click.Path(exists=True)) +@click.option( + "--output_path", + required=True, + help="path where to write output json file", + type=click.Path()) +def _get_config_version_data(config_path, output_path): """Get version of config. - Python 2 wrapped console command - Args: config_path (str): ocio config file path string - out_path (str): temp json file path string + output_path (str): temp json file path string Example of use: > pyton.exe ./ocio_wrapper.py config get_version \ - --config_path= --out_path= + --config_path --output_path """ - json_path = Path(out_path) - - out_data = _get_version_data(config_path) - - with open(json_path, "w") as f_: - json.dump(out_data, f_) - - print(f"Config version data are saved to '{json_path}'") - - -def _get_version_data(config_path): - """Return major and minor version info. - - Args: - config_path (str): path string leading to config.ocio - - Raises: - IOError: Input config does not exist. - - Returns: - dict: minor and major keys with values - """ - config_path = Path(config_path) - - if not config_path.is_file(): - raise IOError("Input path should be `config.ocio` file") - - config = ocio.Config().CreateFromFile(str(config_path)) - - return { - "major": config.getMajorVersion(), - "minor": config.getMinorVersion() - } + _save_output_to_json_file( + get_config_version_data(config_path), + output_path + ) @colorspace.command( name="get_config_file_rules_colorspace_from_filepath", - help=( - "return colorspace from filepath " - "--config_path - ocio config file path (input arg is required) " - "--filepath - any file path (input arg is required) " - "--out_path - temp json file path (input arg is required)" - ) -) -@click.option("--config_path", required=True, - help="path where to read ocio config file", - type=click.Path(exists=True)) -@click.option("--filepath", required=True, - help="path to file to get colorspace from", - type=click.Path()) -@click.option("--out_path", required=True, - help="path where to write output json file", - type=click.Path()) -def get_config_file_rules_colorspace_from_filepath( - config_path, filepath, out_path + help="Colorspace file rules from filepath") +@click.option( + "--config_path", + required=True, + help="OCIO config path to read ocio config file.", + type=click.Path(exists=True)) +@click.option( + "--filepath", + equired=True, + help="Path to file to get colorspace from.", + type=click.Path()) +@click.option( + "--output_path", + required=True, + help="Path where to write output json file.", + type=click.Path()) +def _get_config_file_rules_colorspace_from_filepath( + config_path, filepath, output_path ): """Get colorspace from file path wrapper. - Python 2 wrapped console command - Args: config_path (str): config file path string filepath (str): path string leading to file - out_path (str): temp json file path string + output_path (str): temp json file path string Example of use: - > pyton.exe ./ocio_wrapper.py \ + > python.exe ./ocio_wrapper.py \ colorspace get_config_file_rules_colorspace_from_filepath \ - --config_path= --filepath= --out_path= + --config_path --filepath --output_path """ - json_path = Path(out_path) - - colorspace = _get_config_file_rules_colorspace_from_filepath( - config_path, filepath) - - with open(json_path, "w") as f_: - json.dump(colorspace, f_) - - print(f"Colorspace name is saved to '{json_path}'") - - -def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): - """Return found colorspace data found in v2 file rules. - - Args: - config_path (str): path string leading to config.ocio - filepath (str): path string leading to v2 file rules - - Raises: - IOError: Input config does not exist. - - Returns: - dict: aggregated available colorspaces - """ - config_path = Path(config_path) - - if not config_path.is_file(): - raise IOError( - f"Input path `{config_path}` should be `config.ocio` file") - - config = ocio.Config().CreateFromFile(str(config_path)) - - # TODO: use `parseColorSpaceFromString` instead if ocio v1 - colorspace = config.getColorSpaceFromFilepath(str(filepath)) - - return colorspace - - -def _get_display_view_colorspace_name(config_path, display, view): - """Returns the colorspace attribute of the (display, view) pair. - - Args: - config_path (str): path string leading to config.ocio - display (str): display name e.g. "ACES" - view (str): view name e.g. "sRGB" - - - Raises: - IOError: Input config does not exist. - - Returns: - view color space name (str) e.g. "Output - sRGB" - """ - - config_path = Path(config_path) - - if not config_path.is_file(): - raise IOError("Input path should be `config.ocio` file") - - config = ocio.Config.CreateFromFile(str(config_path)) - colorspace = config.getDisplayViewColorSpaceName(display, view) - - return colorspace + _save_output_to_json_file( + get_config_file_rules_colorspace_from_filepath(config_path, filepath), + output_path + ) @config.command( name="get_display_view_colorspace_name", help=( - "return default view colorspace name " - "for the given display and view " - "--path input arg is required" - ) -) -@click.option("--in_path", required=True, - help="path where to read ocio config file", - type=click.Path(exists=True)) -@click.option("--out_path", required=True, - help="path where to write output json file", - type=click.Path()) -@click.option("--display", required=True, - help="display name", - type=click.STRING) -@click.option("--view", required=True, - help="view name", - type=click.STRING) -def get_display_view_colorspace_name(in_path, out_path, - display, view): + "Default view colorspace name for the given display and view" + )) +@click.option( + "--config_path", + required=True, + help="path where to read ocio config file", + type=click.Path(exists=True)) +@click.option( + "--display", + required=True, + help="Display name", + type=click.STRING) +@click.option( + "--view", + required=True, + help="view name", + type=click.STRING) +@click.option( + "--output_path", + required=True, + help="path where to write output json file", + type=click.Path()) +def _get_display_view_colorspace_name( + config_path, display, view, output_path +): """Aggregate view colorspace name to file. Wrapper command for processes without access to OpenColorIO Args: - in_path (str): config file path string - out_path (str): temp json file path string + config_path (str): config file path string + output_path (str): temp json file path string display (str): display name e.g. "ACES" view (str): view name e.g. "sRGB" Example of use: > pyton.exe ./ocio_wrapper.py config \ - get_display_view_colorspace_name --in_path= \ - --out_path= --display= --view= + get_display_view_colorspace_name --config_path \ + --output_path --display --view """ + _save_output_to_json_file( + get_display_view_colorspace_name(config_path, display, view), + output_path + ) - out_data = _get_display_view_colorspace_name(in_path, - display, - view) - with open(out_path, "w") as f: - json.dump(out_data, f) - - print(f"Display view colorspace saved to '{out_path}'") - -if __name__ == '__main__': +if __name__ == "__main__": + if not has_compatible_ocio_package(): + raise RuntimeError("OpenColorIO is not available.") main() From b23faf324903e5c5b4ae2deaa76087c3358b405d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:40:50 +0200 Subject: [PATCH 699/727] modified existing functions to use functions from colorspace.py --- client/ayon_core/pipeline/colorspace.py | 286 +++++++++++------------- 1 file changed, 132 insertions(+), 154 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index a503c37101..c347638937 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -18,11 +18,10 @@ from ayon_core.lib import ( run_ayon_launcher_process, Logger, ) +from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS from ayon_core.pipeline import Anatomy from ayon_core.pipeline.template_data import get_template_data from ayon_core.pipeline.load import get_representation_path_with_anatomy -from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS - log = Logger.get_logger(__name__) @@ -281,26 +280,50 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath): Returns: Union[str, None]: matching colorspace name + """ - if not has_compatible_ocio_package(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess + if has_compatible_ocio_package(): + result_data = _get_config_file_rules_colorspace_from_filepath( + config_path, filepath + ) + else: result_data = _get_wrapped_with_subprocess( - "colorspace", "get_config_file_rules_colorspace_from_filepath", + "colorspace", + "get_config_file_rules_colorspace_from_filepath", config_path=config_path, filepath=filepath ) - if result_data: - return result_data[0] - - # TODO: refactor this so it is not imported but part of this file - from ayon_core.scripts.ocio_wrapper import _get_config_file_rules_colorspace_from_filepath # noqa: E501 - - result_data = _get_config_file_rules_colorspace_from_filepath( - config_path, filepath) if result_data: return result_data[0] + return None + + +def get_config_version_data(config_path): + """Return major and minor version info. + + Args: + config_path (str): path string leading to config.ocio + + Raises: + IOError: Input config does not exist. + + Returns: + dict: minor and major keys with values + + """ + if config_path not in CachedData.config_version_data: + if has_compatible_ocio_package(): + version_data = _get_config_version_data(config_path) + else: + version_data = _get_wrapped_with_subprocess( + "config", + "get_config_version_data", + config_path=config_path + ) + CachedData.config_version_data[config_path] = version_data + + return deepcopy(CachedData.config_version_data[config_path]) def parse_colorspace_from_filepath( @@ -394,6 +417,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): Returns: bool: True if exists + """ colorspaces = get_ocio_config_colorspaces(config_path)["colorspaces"] if colorspace_name not in colorspaces: @@ -405,9 +429,7 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): def _get_wrapped_with_subprocess(command_group, command, **kwargs): - """Get data via subprocess - - Wrapper for Python 2 hosts. + """Get data via subprocess. Args: command_group (str): command group name @@ -420,14 +442,16 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): with _make_temp_json_file() as tmp_json_path: # Prepare subprocess arguments args = [ - "run", get_ocio_config_script_path(), - command_group, command + "run", + get_ocio_config_script_path(), + command_group, + command ] - for key_, value_ in kwargs.items(): - args.extend(("--{}".format(key_), value_)) + for key, value in kwargs.items(): + args.extend(("--{}".format(key), value)) - args.append("--out_path") + args.append("--output_path") args.append(tmp_json_path) log.info("Executing: {}".format(" ".join(args))) @@ -435,39 +459,23 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): run_ayon_launcher_process(*args, logger=log) # return all colorspaces - with open(tmp_json_path, "r") as f_: - return json.load(f_) + with open(tmp_json_path, "r") as stream: + return json.load(stream) -# TODO: this should be part of ocio_wrapper.py def compatibility_check_config_version(config_path, major=1, minor=None): """Making sure PyOpenColorIO config version is compatible""" - if not CachedData.config_version_data.get(config_path): - if has_compatible_ocio_package(): - # TODO: refactor this so it is not imported but part of this file - from ayon_core.scripts.ocio_wrapper import _get_version_data - - CachedData.config_version_data[config_path] = \ - _get_version_data(config_path) - - else: - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - CachedData.config_version_data[config_path] = \ - _get_wrapped_with_subprocess( - "config", "get_version", config_path=config_path - ) + version_data = get_config_version_data(config_path) # check major version - if CachedData.config_version_data[config_path]["major"] != major: + if version_data["major"] != major: return False # check minor version - if minor and CachedData.config_version_data[config_path]["minor"] != minor: + if minor is not None and version_data["minor"] != minor: return False - # compatible return True @@ -482,22 +490,20 @@ def get_ocio_config_colorspaces(config_path): Returns: dict: colorspace and family in couple - """ - if not CachedData.ocio_config_colorspaces.get(config_path): - if not has_compatible_ocio_package(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - config_colorspaces = _get_wrapped_with_subprocess( - "config", "get_colorspace", in_path=config_path - ) - else: - # TODO: refactor this so it is not imported but part of this file - from ayon_core.scripts.ocio_wrapper import _get_colorspace_data - config_colorspaces = _get_colorspace_data(config_path) + """ + if config_path not in CachedData.ocio_config_colorspaces: + if has_compatible_ocio_package(): + config_colorspaces = _get_ocio_config_colorspaces(config_path) + else: + config_colorspaces = _get_wrapped_with_subprocess( + "config", + "get_ocio_config_colorspaces", + config_path=config_path + ) CachedData.ocio_config_colorspaces[config_path] = config_colorspaces - return CachedData.ocio_config_colorspaces[config_path] + return deepcopy(CachedData.ocio_config_colorspaces[config_path]) def convert_colorspace_enumerator_item( @@ -571,16 +577,18 @@ def get_colorspaces_enumerator_items( Families can be used for building menu and submenus in gui. Args: - config_items (dict[str,dict]): colorspace data coming from - `get_ocio_config_colorspaces` function - include_aliases (bool): include aliases in result - include_looks (bool): include looks in result - include_roles (bool): include roles in result + config_items (dict[str,dict]): Colorspace data coming from + `get_ocio_config_colorspaces` function. + include_aliases (Optional[bool]): Include aliases in result. + include_looks (Optional[bool]): Include looks in result. + include_roles (Optional[bool]): Include roles in result. + include_display_views (Optional[bool]): Include display views + in result. Returns: - list[tuple[str,str]]: colorspace and family in couple + list[tuple[str, str]]: Colorspace and family in couples. + """ - labeled_colorspaces = [] aliases = set() colorspaces = set() looks = set() @@ -590,64 +598,74 @@ def get_colorspaces_enumerator_items( if items_type == "colorspaces": for color_name, color_data in colorspace_items.items(): if color_data.get("aliases"): - aliases.update([ + aliases.update({ ( "aliases::{}".format(alias_name), "[alias] {} ({})".format(alias_name, color_name) ) for alias_name in color_data["aliases"] - ]) + }) colorspaces.add(( "{}::{}".format(items_type, color_name), "[colorspace] {}".format(color_name) )) elif items_type == "looks": - looks.update([ + looks.update({ ( "{}::{}".format(items_type, name), "[look] {} ({})".format(name, role_data["process_space"]) ) for name, role_data in colorspace_items.items() - ]) + }) elif items_type == "displays_views": - display_views.update([ + display_views.update({ ( "{}::{}".format(items_type, name), "[view (display)] {}".format(name) ) for name, _ in colorspace_items.items() - ]) + }) elif items_type == "roles": - roles.update([ + roles.update({ ( "{}::{}".format(items_type, name), "[role] {} ({})".format(name, role_data["colorspace"]) ) for name, role_data in colorspace_items.items() - ]) + }) - if roles and include_roles: - roles = sorted(roles, key=lambda x: x[0]) - labeled_colorspaces.extend(roles) + def _sort_key_getter(item): + """Use colorspace for sorting.""" + return item[0] - # add colorspaces as second so it is not first in menu - colorspaces = sorted(colorspaces, key=lambda x: x[0]) - labeled_colorspaces.extend(colorspaces) + labeled_colorspaces = [] + if include_roles: + labeled_colorspaces.extend( + sorted(roles, key=_sort_key_getter) + ) - if aliases and include_aliases: - aliases = sorted(aliases, key=lambda x: x[0]) - labeled_colorspaces.extend(aliases) + # Add colorspaces after roles, so it is not first in menu + labeled_colorspaces.extend( + sorted(colorspaces, key=_sort_key_getter) + ) - if looks and include_looks: - looks = sorted(looks, key=lambda x: x[0]) - labeled_colorspaces.extend(looks) + if include_aliases: + labeled_colorspaces.extend( + sorted(aliases, key=_sort_key_getter) + ) - if display_views and include_display_views: - display_views = sorted(display_views, key=lambda x: x[0]) - labeled_colorspaces.extend(display_views) + if include_looks: + labeled_colorspaces.extend( + sorted(looks, key=_sort_key_getter) + ) + + if include_display_views: + labeled_colorspaces.extend( + sorted(display_views, key=_sort_key_getter) + ) return labeled_colorspaces @@ -663,18 +681,16 @@ def get_ocio_config_views(config_path): Returns: dict: `display/viewer` and viewer data + """ - if not has_compatible_ocio_package(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - return _get_wrapped_with_subprocess( - "config", "get_views", in_path=config_path - ) + if has_compatible_ocio_package(): + return _get_ocio_config_views(config_path) - # TODO: refactor this so it is not imported but part of this file - from ayon_core.scripts.ocio_wrapper import _get_views_data - - return _get_views_data(config_path) + return _get_wrapped_with_subprocess( + "config", + "get_ocio_config_views", + config_path=config_path + ) def _get_global_config_data( @@ -1255,48 +1271,17 @@ def get_display_view_colorspace_name(config_path, display, view): str: View color space name. e.g. "Output - sRGB" """ - if not has_compatible_ocio_package(): - # python environment is not compatible with PyOpenColorIO - # needs to be run in subprocess - return _get_display_view_colorspace_subprocess( + if has_compatible_ocio_package(): + return _get_display_view_colorspace_name( config_path, display, view ) - - from ayon_core.scripts.ocio_wrapper import _get_display_view_colorspace_name # noqa - - return _get_display_view_colorspace_name(config_path, display, view) - - -def _get_display_view_colorspace_subprocess(config_path, display, view): - """Returns the colorspace attribute of the (display, view) pair - via subprocess. - - Args: - config_path (str): path string leading to config.ocio - display (str): display name e.g. "ACES" - view (str): view name e.g. "sRGB" - - Returns: - view color space name (str) e.g. "Output - sRGB" - - """ - with _make_temp_json_file() as tmp_json_path: - # Prepare subprocess arguments - args = [ - "run", get_ocio_config_script_path(), - "config", "get_display_view_colorspace_name", - "--in_path", config_path, - "--out_path", tmp_json_path, - "--display", display, - "--view", view - ] - log.debug("Executing: {}".format(" ".join(args))) - - run_ayon_launcher_process(*args, logger=log) - - # return default view colorspace name - with open(tmp_json_path, "r") as f: - return json.load(f) + return _get_wrapped_with_subprocess( + "config", + "get_display_view_colorspace_name", + config_path=config_path, + display=display, + view=view + ) # --- Implementation of logic using 'PyOpenColorIO' --- @@ -1531,7 +1516,8 @@ def get_colorspace_from_filepath(*args, **kwargs): def get_colorspace_data_subprocess(config_path): """[Deprecated] Get colorspace data via subprocess - Wrapper for Python 2 hosts. + Deprecated: + Deprecated since OpenPype. Use `_get_wrapped_with_subprocess` instead. Args: config_path (str): path leading to config.ocio file @@ -1540,7 +1526,9 @@ def get_colorspace_data_subprocess(config_path): dict: colorspace and family in couple """ return _get_wrapped_with_subprocess( - "config", "get_colorspace", in_path=config_path + "config", + "get_ocio_config_colorspaces", + config_path=config_path ) @@ -1548,30 +1536,20 @@ def get_colorspace_data_subprocess(config_path): def get_views_data_subprocess(config_path): """[Deprecated] Get viewers data via subprocess - Wrapper for Python 2 hosts. + Deprecated: + Deprecated since OpenPype. Use `_get_wrapped_with_subprocess` instead. Args: config_path (str): path leading to config.ocio file Returns: dict: `display/viewer` and viewer data + """ return _get_wrapped_with_subprocess( - "config", "get_views", in_path=config_path - ) - - -@deprecated("_get_wrapped_with_subprocess") -def get_data_subprocess(config_path, data_type): - """[Deprecated] Get data via subprocess - - Wrapper for Python 2 hosts. - - Args: - config_path (str): path leading to config.ocio file - """ - return _get_wrapped_with_subprocess( - "config", data_type, in_path=config_path, + "config", + "get_ocio_config_views", + config_path=config_path ) From 25fe55aa316f8f5d16bd6c773d0aa3c1a3d07935 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:42:21 +0200 Subject: [PATCH 700/727] validate 'PyOpenColorIO' version --- client/ayon_core/pipeline/colorspace.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index c347638937..c29bdd762d 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -116,13 +116,21 @@ def has_compatible_ocio_package(): if CachedData.has_compatible_ocio_package is not None: return CachedData.has_compatible_ocio_package + is_compatible = False try: - import PyOpenColorIO # noqa: F401 - # TODO validate 'PyOpenColorIO' version - CachedData.has_compatible_ocio_package = True - except ImportError: - CachedData.has_compatible_ocio_package = False + import PyOpenColorIO + # Check if PyOpenColorIO is compatible + # - version 2.0.0 or higher is required + # NOTE version 1 does not have '__version__' attribute + if hasattr(PyOpenColorIO, "__version__"): + version_parts = PyOpenColorIO.__version__.split(".") + major = int(version_parts[0]) + is_compatible = (major, ) >= (2, ) + except ImportError: + pass + + CachedData.has_compatible_ocio_package = is_compatible # compatible return CachedData.has_compatible_ocio_package From 8806463c7789fdcc2ce222576062c759c56c8b19 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:42:30 +0200 Subject: [PATCH 701/727] use raw DeprecationWarning --- client/ayon_core/pipeline/colorspace.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index c29bdd762d..b568e2cdf1 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -36,10 +36,6 @@ class CachedData: } -class DeprecatedWarning(DeprecationWarning): - pass - - def deprecated(new_destination): """Mark functions as deprecated. @@ -64,13 +60,13 @@ def deprecated(new_destination): @functools.wraps(decorated_func) def wrapper(*args, **kwargs): - warnings.simplefilter("always", DeprecatedWarning) + warnings.simplefilter("always", DeprecationWarning) warnings.warn( ( "Call to deprecated function '{}'" "\nFunction was moved or removed.{}" ).format(decorated_func.__name__, warning_message), - category=DeprecatedWarning, + category=DeprecationWarning, stacklevel=4 ) return decorated_func(*args, **kwargs) From 3b93392eb75663351e04de49d2c55cf163cd56cc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:47:23 +0200 Subject: [PATCH 702/727] fix typo --- client/ayon_core/scripts/ocio_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/scripts/ocio_wrapper.py b/client/ayon_core/scripts/ocio_wrapper.py index 9cbab32956..897e910fa5 100644 --- a/client/ayon_core/scripts/ocio_wrapper.py +++ b/client/ayon_core/scripts/ocio_wrapper.py @@ -153,7 +153,7 @@ def _get_config_version_data(config_path, output_path): type=click.Path(exists=True)) @click.option( "--filepath", - equired=True, + required=True, help="Path to file to get colorspace from.", type=click.Path()) @click.option( From ddbec4ea71ae9e57c9c2a1d9d82abbe01933896f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 15 May 2024 18:57:04 +0200 Subject: [PATCH 703/727] added docstring to sorter --- client/ayon_core/pipeline/colorspace.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index b568e2cdf1..d9785b61fb 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -642,7 +642,15 @@ def get_colorspaces_enumerator_items( }) def _sort_key_getter(item): - """Use colorspace for sorting.""" + """Use colorspace for sorting. + + Args: + item (tuple[str, str]): Item with colorspace and label. + + Returns: + str: Colorspace. + + """ return item[0] labeled_colorspaces = [] From d353dd145a4c82a72498de87f9dfb32f2cffac09 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 16 May 2024 09:40:53 +0200 Subject: [PATCH 704/727] removed unnecessary command group --- client/ayon_core/pipeline/colorspace.py | 11 +-------- client/ayon_core/scripts/ocio_wrapper.py | 30 ++++-------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index d9785b61fb..239c187959 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -292,7 +292,6 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath): ) else: result_data = _get_wrapped_with_subprocess( - "colorspace", "get_config_file_rules_colorspace_from_filepath", config_path=config_path, filepath=filepath @@ -321,7 +320,6 @@ def get_config_version_data(config_path): version_data = _get_config_version_data(config_path) else: version_data = _get_wrapped_with_subprocess( - "config", "get_config_version_data", config_path=config_path ) @@ -432,11 +430,10 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): return True -def _get_wrapped_with_subprocess(command_group, command, **kwargs): +def _get_wrapped_with_subprocess(command, **kwargs): """Get data via subprocess. Args: - command_group (str): command group name command (str): command name **kwargs: command arguments @@ -448,7 +445,6 @@ def _get_wrapped_with_subprocess(command_group, command, **kwargs): args = [ "run", get_ocio_config_script_path(), - command_group, command ] @@ -501,7 +497,6 @@ def get_ocio_config_colorspaces(config_path): config_colorspaces = _get_ocio_config_colorspaces(config_path) else: config_colorspaces = _get_wrapped_with_subprocess( - "config", "get_ocio_config_colorspaces", config_path=config_path ) @@ -699,7 +694,6 @@ def get_ocio_config_views(config_path): return _get_ocio_config_views(config_path) return _get_wrapped_with_subprocess( - "config", "get_ocio_config_views", config_path=config_path ) @@ -1288,7 +1282,6 @@ def get_display_view_colorspace_name(config_path, display, view): config_path, display, view ) return _get_wrapped_with_subprocess( - "config", "get_display_view_colorspace_name", config_path=config_path, display=display, @@ -1538,7 +1531,6 @@ def get_colorspace_data_subprocess(config_path): dict: colorspace and family in couple """ return _get_wrapped_with_subprocess( - "config", "get_ocio_config_colorspaces", config_path=config_path ) @@ -1559,7 +1551,6 @@ def get_views_data_subprocess(config_path): """ return _get_wrapped_with_subprocess( - "config", "get_ocio_config_views", config_path=config_path ) diff --git a/client/ayon_core/scripts/ocio_wrapper.py b/client/ayon_core/scripts/ocio_wrapper.py index 897e910fa5..0414fc59ce 100644 --- a/client/ayon_core/scripts/ocio_wrapper.py +++ b/client/ayon_core/scripts/ocio_wrapper.py @@ -33,27 +33,7 @@ def main(): pass # noqa: WPS100 -@main.group() -def config(): - """Config related commands group - - Example of use: - > pyton.exe ./ocio_wrapper.py config *args - """ - pass # noqa: WPS100 - - -@main.group() -def colorspace(): - """Colorspace related commands group - - Example of use: - > pyton.exe ./ocio_wrapper.py config *args - """ - pass # noqa: WPS100 - - -@config.command( +@main.command( name="get_ocio_config_colorspaces", help="return all colorspaces from config file") @click.option( @@ -83,7 +63,7 @@ def _get_ocio_config_colorspaces(config_path, output_path): ) -@config.command( +@main.command( name="get_ocio_config_views", help="All viewers from config file") @click.option( @@ -113,7 +93,7 @@ def _get_ocio_config_views(config_path, output_path): ) -@config.command( +@main.command( name="get_config_version_data", help="Get major and minor version from config file") @click.option( @@ -143,7 +123,7 @@ def _get_config_version_data(config_path, output_path): ) -@colorspace.command( +@main.command( name="get_config_file_rules_colorspace_from_filepath", help="Colorspace file rules from filepath") @click.option( @@ -182,7 +162,7 @@ def _get_config_file_rules_colorspace_from_filepath( ) -@config.command( +@main.command( name="get_display_view_colorspace_name", help=( "Default view colorspace name for the given display and view" From dc0f1769d5a73ebea1c9e7546d412f744370c65a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 May 2024 12:31:13 +0200 Subject: [PATCH 705/727] Revert "AY-1110 - use get_addon_site_settings from ayon_api" This reverts commit 61fcc3ddecc2121d4947344215cce3dbff8caff5. --- .../deadline/plugins/publish/collect_user_credentials.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py index 30e3703b58..99d75ecb9e 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -12,7 +12,7 @@ Provides: """ import pyblish.api -from ayon_api import get_addon_site_settings +from ayon_api import get_server_api_connection from ayon_core.modules.deadline.deadline_module import DeadlineModule from ayon_core.modules.deadline import __version__ @@ -81,8 +81,9 @@ class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): if not deadline_info["require_authentication"]: return - - local_settings = get_addon_site_settings( + # TODO import 'get_addon_site_settings' when available + # in public 'ayon_api' + local_settings = get_server_api_connection().get_addon_site_settings( DeadlineModule.name, __version__) local_settings = local_settings["local_settings"] for server_info in local_settings: From 340c07317f32bbfa94b865189cb554a36bb17465 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 19:38:26 +0800 Subject: [PATCH 706/727] make validator animated reference being optional --- .../hosts/maya/plugins/publish/validate_animated_reference.py | 2 +- server_addon/maya/server/settings/publishers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py index 2ba2bff6fc..4e8261d42e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py @@ -18,7 +18,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, label = "Animated Reference Rig" accepted_controllers = ["transform", "locator"] actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - optional = False + optional = True def process(self, instance): if not self.is_active(instance.data): diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 20523b2ca9..3e8dc704b7 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1448,8 +1448,8 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ValidateAnimatedReferenceRig": { - "enabled": True, - "optional": False, + "enabled": False, + "optional": True, "active": True }, "ValidateAnimationContent": { From ce194b32febe009e2eec969c2fba77fb8238a319 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 20:25:03 +0800 Subject: [PATCH 707/727] remove the validator --- .../publish/validate_animated_reference.py | 71 ------------------- .../maya/server/settings/publishers.py | 10 +-- 2 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py b/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py deleted file mode 100644 index 4e8261d42e..0000000000 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_animated_reference.py +++ /dev/null @@ -1,71 +0,0 @@ -import pyblish.api -import ayon_core.hosts.maya.api.action -from ayon_core.pipeline.publish import ( - PublishValidationError, - ValidateContentsOrder, - OptionalPyblishPluginMixin -) -from maya import cmds - - -class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin, - OptionalPyblishPluginMixin): - """Validate all nodes in skeletonAnim_SET are referenced""" - - order = ValidateContentsOrder - hosts = ["maya"] - families = ["animation.fbx"] - label = "Animated Reference Rig" - accepted_controllers = ["transform", "locator"] - actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - optional = True - - def process(self, instance): - if not self.is_active(instance.data): - return - animated_sets = instance.data.get("animated_skeleton", []) - if not animated_sets: - self.log.debug( - "No nodes found in skeletonAnim_SET. " - "Skipping validation of animated reference rig..." - ) - return - - for animated_reference in animated_sets: - is_referenced = cmds.referenceQuery( - animated_reference, isNodeReferenced=True) - if not bool(is_referenced): - raise PublishValidationError( - "All the content in skeletonAnim_SET" - " should be referenced nodes" - ) - invalid_controls = self.validate_controls(animated_sets) - if invalid_controls: - raise PublishValidationError( - "All the content in skeletonAnim_SET" - " should be transforms" - ) - - @classmethod - def validate_controls(self, set_members): - """Check if the controller set contains only accepted node types. - - Checks if all its set members are within the hierarchy of the root - Checks if the node types of the set members valid - - Args: - set_members: list of nodes of the skeleton_anim_set - hierarchy: list of nodes which reside under the root node - - Returns: - errors (list) - """ - - # Validate control types - invalid = [] - set_members = cmds.ls(set_members, long=True) - for node in set_members: - if cmds.nodeType(node) not in self.accepted_controllers: - invalid.append(node) - - return invalid diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3e8dc704b7..3ff57bab13 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -921,10 +921,7 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Animated Reference Rig", ) - ValidateAnimationContent: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Animation Content", - ) + ValidateOutRelatedNodeIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Out Set Related Node Ids", @@ -1447,11 +1444,6 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": True }, - "ValidateAnimatedReferenceRig": { - "enabled": False, - "optional": True, - "active": True - }, "ValidateAnimationContent": { "enabled": True, "optional": False, From cd0bf07939a557744a2bc94d5f67479282b5099c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 May 2024 21:11:32 +0800 Subject: [PATCH 708/727] upversion --- server_addon/maya/package.py | 2 +- server_addon/maya/server/settings/publishers.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py index fe3e3039f5..5ab2fa217c 100644 --- a/server_addon/maya/package.py +++ b/server_addon/maya/package.py @@ -1,3 +1,3 @@ name = "maya" title = "Maya" -version = "0.1.18" +version = "0.1.19" diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 3ff57bab13..01ac6f4acd 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -917,11 +917,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Rig Controllers", ) - ValidateAnimatedReferenceRig: BasicValidateModel = SettingsField( + ValidateAnimationContent: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, - title="Validate Animated Reference Rig", + title="Validate Animation Content", ) - ValidateOutRelatedNodeIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Out Set Related Node Ids", From 2536f86c221099a70cd7bbf7a226593674c24c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 16 May 2024 22:54:02 +0200 Subject: [PATCH 709/727] :recycle: fix UE name on linux and darwin --- client/ayon_core/hosts/unreal/lib.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/unreal/lib.py b/client/ayon_core/hosts/unreal/lib.py index 37122b2096..185853a0aa 100644 --- a/client/ayon_core/hosts/unreal/lib.py +++ b/client/ayon_core/hosts/unreal/lib.py @@ -80,17 +80,21 @@ def get_engine_versions(env=None): def get_editor_exe_path(engine_path: Path, engine_version: str) -> Path: """Get UE Editor executable path.""" ue_path = engine_path / "Engine/Binaries" + + ue_name = "UnrealEditor" + + # handle older versions of Unreal Engine + if engine_version.split(".")[0] == "4": + ue_name = "UE4Editor" + if platform.system().lower() == "windows": - if engine_version.split(".")[0] == "4": - ue_path /= "Win64/UE4Editor.exe" - elif engine_version.split(".")[0] == "5": - ue_path /= "Win64/UnrealEditor.exe" + ue_path /= f"Win64/{ue_name}.exe" elif platform.system().lower() == "linux": - ue_path /= "Linux/UE4Editor" + ue_path /= f"Linux/{ue_name}" elif platform.system().lower() == "darwin": - ue_path /= "Mac/UE4Editor" + ue_path /= f"Mac/{ue_name}" return ue_path From cc7769dd21fc2d8312220d5f61f07b7c6eef5038 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 11:13:11 +0200 Subject: [PATCH 710/727] ValidateContainers have profile based settings --- .../plugins/publish/validate_containers.py | 40 +++++++++++++-- server/settings/publish_plugins.py | 49 +++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_containers.py b/client/ayon_core/plugins/publish/validate_containers.py index bd21ec9693..21736f0659 100644 --- a/client/ayon_core/plugins/publish/validate_containers.py +++ b/client/ayon_core/plugins/publish/validate_containers.py @@ -1,6 +1,11 @@ import pyblish.api + +from ayon_core.lib import filter_profiles +from ayon_core.host import ILoadHost from ayon_core.pipeline.load import any_outdated_containers from ayon_core.pipeline import ( + get_current_host_name, + registered_host, PublishXmlValidationError, OptionalPyblishPluginMixin ) @@ -18,17 +23,44 @@ class ShowInventory(pyblish.api.Action): host_tools.show_scene_inventory() -class ValidateContainers(OptionalPyblishPluginMixin, - pyblish.api.ContextPlugin): - +class ValidateContainers( + OptionalPyblishPluginMixin, + pyblish.api.ContextPlugin +): """Containers are must be updated to latest version on publish.""" label = "Validate Outdated Containers" order = pyblish.api.ValidatorOrder - hosts = ["maya", "houdini", "nuke", "harmony", "photoshop", "aftereffects"] + optional = True actions = [ShowInventory] + @classmethod + def apply_settings(cls, settings): + # Disable plugin if host does not inherit from 'ILoadHost' + # - not a host that can load containers + host = registered_host() + if not isinstance(host, ILoadHost): + cls.enabled = False + return + + # Disable if no profile is found for the current host + profile = filter_profiles( + settings["core"]["publish"]["ValidateContainers"]["profiles"], + {"host_names": get_current_host_name()} + ) + if not profile: + cls.enabled = False + return + + # Apply settings from profile + for attr_name in { + "enabled", + "optional", + "active", + }: + setattr(cls, attr_name, profile[attr_name]) + def process(self, context): if not self.is_active(context.data): return diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index e61bf6986b..f487438109 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -59,6 +59,32 @@ class CollectFramesFixDefModel(BaseSettingsModel): ) +class ValidateContainersProfile(BaseSettingsModel): + _layout = "expanded" + # Filtering + host_names: list[str] = SettingsField( + default_factory=list, + title="Host names" + ) + # Profile values + enabled: bool = SettingsField(True) + optional: bool = SettingsField(True) + active: bool = SettingsField(True) + + +class ValidateContainersModel(BaseSettingsModel): + """Validate if Publishing intent was selected. + + It is possible to disable validation for specific publishing context + with profiles. + """ + + _isGroup = True + profiles: list[ValidateContainersProfile] = SettingsField( + default_factory=list + ) + + class ValidateIntentProfile(BaseSettingsModel): _layout = "expanded" hosts: list[str] = SettingsField(default_factory=list, title="Host names") @@ -770,6 +796,10 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=ValidateBaseModel, title="Validate Version" ) + ValidateContainers: ValidateContainersModel = SettingsField( + default_factory=ValidateContainersModel, + title="Validate Containers" + ) ValidateIntent: ValidateIntentModel = SettingsField( default_factory=ValidateIntentModel, title="Validate Intent" @@ -855,6 +885,25 @@ DEFAULT_PUBLISH_VALUES = { "optional": False, "active": True }, + "ValidateContainers": { + "profiles": [ + { + # Default host names are based on original + # filter of ValidateContainer pyblish plugin + "host_names": [ + "maya", + "houdini", + "nuke", + "harmony", + "photoshop", + "aftereffects" + ], + "enabled": True, + "optional": True, + "active": True + } + ] + }, "ValidateIntent": { "enabled": False, "profiles": [] From 0101527af2864458d3c0728ab3ae508dd7a7bafb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 11:14:28 +0200 Subject: [PATCH 711/727] add titles --- server/settings/publish_plugins.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index f487438109..f40cc1fefe 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -67,9 +67,9 @@ class ValidateContainersProfile(BaseSettingsModel): title="Host names" ) # Profile values - enabled: bool = SettingsField(True) - optional: bool = SettingsField(True) - active: bool = SettingsField(True) + enabled: bool = SettingsField(True, title="Enabled") + optional: bool = SettingsField(True, title="Optional") + active: bool = SettingsField(True, title="Active") class ValidateContainersModel(BaseSettingsModel): From d2a0719de3ba30a74f846a67082c7a02e0f1855b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 11:28:02 +0200 Subject: [PATCH 712/727] remove 'ValidateContainers' settings from other hosts --- server_addon/aftereffects/package.py | 2 +- .../server/settings/publish_plugins.py | 15 --------------- server_addon/harmony/package.py | 2 +- server_addon/harmony/server/settings/main.py | 5 ----- .../harmony/server/settings/publish_plugins.py | 13 ------------- server_addon/maya/package.py | 2 +- .../maya/server/settings/publishers.py | 9 --------- server_addon/nuke/package.py | 2 +- .../nuke/server/settings/publish_plugins.py | 9 --------- server_addon/photoshop/package.py | 2 +- .../server/settings/publish_plugins.py | 18 ------------------ 11 files changed, 5 insertions(+), 74 deletions(-) diff --git a/server_addon/aftereffects/package.py b/server_addon/aftereffects/package.py index a680b37602..7a2f9bc7af 100644 --- a/server_addon/aftereffects/package.py +++ b/server_addon/aftereffects/package.py @@ -1,3 +1,3 @@ name = "aftereffects" title = "AfterEffects" -version = "0.1.3" +version = "0.1.4" diff --git a/server_addon/aftereffects/server/settings/publish_plugins.py b/server_addon/aftereffects/server/settings/publish_plugins.py index 61d67f26d3..a9f30c6686 100644 --- a/server_addon/aftereffects/server/settings/publish_plugins.py +++ b/server_addon/aftereffects/server/settings/publish_plugins.py @@ -22,12 +22,6 @@ class ValidateSceneSettingsModel(BaseSettingsModel): ) -class ValidateContainersModel(BaseSettingsModel): - enabled: bool = SettingsField(True, title="Enabled") - optional: bool = SettingsField(True, title="Optional") - active: bool = SettingsField(True, title="Active") - - class AfterEffectsPublishPlugins(BaseSettingsModel): CollectReview: CollectReviewPluginModel = SettingsField( default_factory=CollectReviewPluginModel, @@ -37,10 +31,6 @@ class AfterEffectsPublishPlugins(BaseSettingsModel): default_factory=ValidateSceneSettingsModel, title="Validate Scene Settings", ) - ValidateContainers: ValidateContainersModel = SettingsField( - default_factory=ValidateContainersModel, - title="Validate Containers", - ) AE_PUBLISH_PLUGINS_DEFAULTS = { @@ -58,9 +48,4 @@ AE_PUBLISH_PLUGINS_DEFAULTS = { ".*" ] }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True, - } } diff --git a/server_addon/harmony/package.py b/server_addon/harmony/package.py index 83e88e7d57..00824cedef 100644 --- a/server_addon/harmony/package.py +++ b/server_addon/harmony/package.py @@ -1,3 +1,3 @@ name = "harmony" title = "Harmony" -version = "0.1.2" +version = "0.1.3" diff --git a/server_addon/harmony/server/settings/main.py b/server_addon/harmony/server/settings/main.py index 9c780b63c2..8a72c966d8 100644 --- a/server_addon/harmony/server/settings/main.py +++ b/server_addon/harmony/server/settings/main.py @@ -45,11 +45,6 @@ DEFAULT_HARMONY_SETTING = { "optional": True, "active": True }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True - }, "ValidateSceneSettings": { "enabled": True, "optional": True, diff --git a/server_addon/harmony/server/settings/publish_plugins.py b/server_addon/harmony/server/settings/publish_plugins.py index c9e7c515e4..2d976389f6 100644 --- a/server_addon/harmony/server/settings/publish_plugins.py +++ b/server_addon/harmony/server/settings/publish_plugins.py @@ -18,14 +18,6 @@ class ValidateAudioPlugin(BaseSettingsModel): active: bool = SettingsField(True, title="Active") -class ValidateContainersPlugin(BaseSettingsModel): - """Check if loaded container is scene are latest versions.""" - _isGroup = True - enabled: bool = True - optional: bool = SettingsField(False, title="Optional") - active: bool = SettingsField(True, title="Active") - - class ValidateSceneSettingsPlugin(BaseSettingsModel): """Validate if FrameStart, FrameEnd and Resolution match shot data in DB. Use regular expressions to limit validations only on particular asset @@ -63,11 +55,6 @@ class HarmonyPublishPlugins(BaseSettingsModel): default_factory=ValidateAudioPlugin, ) - ValidateContainers: ValidateContainersPlugin = SettingsField( - title="Validate Containers", - default_factory=ValidateContainersPlugin, - ) - ValidateSceneSettings: ValidateSceneSettingsPlugin = SettingsField( title="Validate Scene Settings", default_factory=ValidateSceneSettingsPlugin, diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py index 5ab2fa217c..4537c23eaa 100644 --- a/server_addon/maya/package.py +++ b/server_addon/maya/package.py @@ -1,3 +1,3 @@ name = "maya" title = "Maya" -version = "0.1.19" +version = "0.1.20" diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 01ac6f4acd..9c552e17fa 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -634,10 +634,6 @@ class PublishersModel(BaseSettingsModel): title="Validate Instance In Context", section="Validators" ) - ValidateContainers: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Containers" - ) ValidateFrameRange: ValidateFrameRangeModel = SettingsField( default_factory=ValidateFrameRangeModel, title="Validate Frame Range" @@ -1059,11 +1055,6 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": True }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True - }, "ValidateFrameRange": { "enabled": True, "optional": True, diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py index e522b9fb5d..bc166bd14e 100644 --- a/server_addon/nuke/package.py +++ b/server_addon/nuke/package.py @@ -1,3 +1,3 @@ name = "nuke" title = "Nuke" -version = "0.1.12" +version = "0.1.13" diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index e67f7be24f..6c37ecd37a 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -231,10 +231,6 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=OptionalPluginModel, section="Validators" ) - ValidateContainers: OptionalPluginModel = SettingsField( - title="Validate Containers", - default_factory=OptionalPluginModel - ) ValidateKnobs: ValidateKnobsModel = SettingsField( title="Validate Knobs", default_factory=ValidateKnobsModel @@ -300,11 +296,6 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "optional": True, "active": True }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True - }, "ValidateKnobs": { "enabled": False, "knobs": "\n".join([ diff --git a/server_addon/photoshop/package.py b/server_addon/photoshop/package.py index 25615529d1..22043f951c 100644 --- a/server_addon/photoshop/package.py +++ b/server_addon/photoshop/package.py @@ -1,3 +1,3 @@ name = "photoshop" title = "Photoshop" -version = "0.1.2" +version = "0.1.3" diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index d04faaf53a..149b08beb4 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -83,14 +83,6 @@ class CollectVersionPlugin(BaseSettingsModel): enabled: bool = SettingsField(True, title="Enabled") -class ValidateContainersPlugin(BaseSettingsModel): - """Check that workfile contains latest version of loaded items""" # noqa - _isGroup = True - enabled: bool = True - optional: bool = SettingsField(False, title="Optional") - active: bool = SettingsField(True, title="Active") - - class ValidateNamingPlugin(BaseSettingsModel): """Validate naming of products and layers""" # noqa invalid_chars: str = SettingsField( @@ -154,11 +146,6 @@ class PhotoshopPublishPlugins(BaseSettingsModel): default_factory=CollectVersionPlugin, ) - ValidateContainers: ValidateContainersPlugin = SettingsField( - title="Validate Containers", - default_factory=ValidateContainersPlugin, - ) - ValidateNaming: ValidateNamingPlugin = SettingsField( title="Validate naming of products and layers", default_factory=ValidateNamingPlugin, @@ -187,11 +174,6 @@ DEFAULT_PUBLISH_SETTINGS = { "CollectVersion": { "enabled": False }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True - }, "ValidateNaming": { "invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,;]", "replace_char": "_" From bb05691289def08af1d69449cd284f880839097e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 11:48:41 +0200 Subject: [PATCH 713/727] remove houdini settings too --- server_addon/houdini/package.py | 2 +- server_addon/houdini/server/settings/publish.py | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py index 6c81eba439..06b034da38 100644 --- a/server_addon/houdini/package.py +++ b/server_addon/houdini/package.py @@ -1,3 +1,3 @@ name = "houdini" title = "Houdini" -version = "0.2.14" +version = "0.2.15" diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index 9e8e796aff..4a0c022f23 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -77,10 +77,6 @@ class PublishPluginsModel(BaseSettingsModel): default_factory=CollectLocalRenderInstancesModel, title="Collect Local Render Instances." ) - ValidateContainers: BasicValidateModel = SettingsField( - default_factory=BasicValidateModel, - title="Validate Latest Containers.", - section="Validators") ValidateInstanceInContextHoudini: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Instance is in same Context.") @@ -119,11 +115,6 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = { ] } }, - "ValidateContainers": { - "enabled": True, - "optional": True, - "active": True - }, "ValidateInstanceInContextHoudini": { "enabled": True, "optional": True, From d4ca6bf3f644615ca49e3b43c649000f05f35b6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 12:14:47 +0200 Subject: [PATCH 714/727] use kwarg to pass project entity --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 239c187959..099616ff4a 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -920,7 +920,7 @@ def get_imageio_config_preset( project_entity = None if anatomy is None: project_entity = ayon_api.get_project(project_name) - anatomy = Anatomy(project_name, project_entity) + anatomy = Anatomy(project_name, project_entity=project_entity) if env is None: env = dict(os.environ.items()) From 7340ab081d0b7bc197eb205eeaad57571b8e73f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 12:21:08 +0200 Subject: [PATCH 715/727] do not lower task name when it can be None --- client/ayon_core/tools/push_to_project/models/integrate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/tools/push_to_project/models/integrate.py b/client/ayon_core/tools/push_to_project/models/integrate.py index 6e43050c05..5937ffa4da 100644 --- a/client/ayon_core/tools/push_to_project/models/integrate.py +++ b/client/ayon_core/tools/push_to_project/models/integrate.py @@ -723,7 +723,6 @@ class ProjectPushItemProcess: dst_project_name = self._item.dst_project_name dst_folder_id = self._item.dst_folder_id dst_task_name = self._item.dst_task_name - dst_task_name_low = dst_task_name.lower() new_folder_name = self._item.new_folder_name if not dst_folder_id and not new_folder_name: self._status.set_failed( @@ -765,7 +764,7 @@ class ProjectPushItemProcess: dst_project_name, folder_ids=[folder_entity["id"]] ) } - task_info = folder_tasks.get(dst_task_name_low) + task_info = folder_tasks.get(dst_task_name.lower()) if not task_info: self._status.set_failed( f"Could find task with name \"{dst_task_name}\"" From 45611ea27e8e3f8106df6dc45075a02e2a1145fd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 12:35:42 +0200 Subject: [PATCH 716/727] call update on viewport instead on view --- client/ayon_core/tools/launcher/ui/actions_widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/launcher/ui/actions_widget.py b/client/ayon_core/tools/launcher/ui/actions_widget.py index a225827418..d03aceb009 100644 --- a/client/ayon_core/tools/launcher/ui/actions_widget.py +++ b/client/ayon_core/tools/launcher/ui/actions_widget.py @@ -359,7 +359,8 @@ class ActionsWidget(QtWidgets.QWidget): def _on_model_refresh(self): self._proxy_model.sort(0) # Force repaint all items - self._view.update() + viewport = self._view.viewport() + viewport.update() def _on_animation(self): time_now = time.time() From 9dcfd06ed62599f4544e6c86af07e712da972b83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 14:53:29 +0200 Subject: [PATCH 717/727] change "profiles" key to "plugin_state_profiles" --- .../ayon_core/plugins/publish/validate_containers.py | 10 ++++++++-- server/settings/publish_plugins.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_containers.py b/client/ayon_core/plugins/publish/validate_containers.py index 21736f0659..3fd5083b4e 100644 --- a/client/ayon_core/plugins/publish/validate_containers.py +++ b/client/ayon_core/plugins/publish/validate_containers.py @@ -45,9 +45,15 @@ class ValidateContainers( return # Disable if no profile is found for the current host + profiles = ( + settings + ["core"] + ["publish"] + ["ValidateContainers"] + ["plugin_state_profiles"] + ) profile = filter_profiles( - settings["core"]["publish"]["ValidateContainers"]["profiles"], - {"host_names": get_current_host_name()} + profiles, {"host_names": get_current_host_name()} ) if not profile: cls.enabled = False diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index f40cc1fefe..0c19655035 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -80,7 +80,7 @@ class ValidateContainersModel(BaseSettingsModel): """ _isGroup = True - profiles: list[ValidateContainersProfile] = SettingsField( + plugin_state_profiles: list[ValidateContainersProfile] = SettingsField( default_factory=list ) @@ -886,7 +886,7 @@ DEFAULT_PUBLISH_VALUES = { "active": True }, "ValidateContainers": { - "profiles": [ + "plugin_state_profiles": [ { # Default host names are based on original # filter of ValidateContainer pyblish plugin From daf35ecd0f7c7bcde0a19c121e6bd1d302f9a0a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 14:53:37 +0200 Subject: [PATCH 718/727] add a title to profiles --- server/settings/publish_plugins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 0c19655035..8cf660354a 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -81,7 +81,8 @@ class ValidateContainersModel(BaseSettingsModel): _isGroup = True plugin_state_profiles: list[ValidateContainersProfile] = SettingsField( - default_factory=list + default_factory=list, + title="Plugin enable state profiles", ) From 38fd82623357d3001e96d0bc076c2dba0ff663b5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 20 May 2024 15:34:12 +0200 Subject: [PATCH 719/727] rename 'ValidateContainers' to 'ValidateOutdatedContainers' --- .../ayon_core/plugins/publish/validate_containers.py | 4 ++-- server/settings/publish_plugins.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/plugins/publish/validate_containers.py b/client/ayon_core/plugins/publish/validate_containers.py index 3fd5083b4e..520e7a7ce9 100644 --- a/client/ayon_core/plugins/publish/validate_containers.py +++ b/client/ayon_core/plugins/publish/validate_containers.py @@ -23,7 +23,7 @@ class ShowInventory(pyblish.api.Action): host_tools.show_scene_inventory() -class ValidateContainers( +class ValidateOutdatedContainers( OptionalPyblishPluginMixin, pyblish.api.ContextPlugin ): @@ -49,7 +49,7 @@ class ValidateContainers( settings ["core"] ["publish"] - ["ValidateContainers"] + ["ValidateOutdatedContainers"] ["plugin_state_profiles"] ) profile = filter_profiles( diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 8cf660354a..61e73ce912 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -59,7 +59,7 @@ class CollectFramesFixDefModel(BaseSettingsModel): ) -class ValidateContainersProfile(BaseSettingsModel): +class ValidateOutdatedContainersProfile(BaseSettingsModel): _layout = "expanded" # Filtering host_names: list[str] = SettingsField( @@ -72,7 +72,7 @@ class ValidateContainersProfile(BaseSettingsModel): active: bool = SettingsField(True, title="Active") -class ValidateContainersModel(BaseSettingsModel): +class ValidateOutdatedContainersModel(BaseSettingsModel): """Validate if Publishing intent was selected. It is possible to disable validation for specific publishing context @@ -80,7 +80,7 @@ class ValidateContainersModel(BaseSettingsModel): """ _isGroup = True - plugin_state_profiles: list[ValidateContainersProfile] = SettingsField( + plugin_state_profiles: list[ValidateOutdatedContainersProfile] = SettingsField( default_factory=list, title="Plugin enable state profiles", ) @@ -797,8 +797,8 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=ValidateBaseModel, title="Validate Version" ) - ValidateContainers: ValidateContainersModel = SettingsField( - default_factory=ValidateContainersModel, + ValidateOutdatedContainers: ValidateOutdatedContainersModel = SettingsField( + default_factory=ValidateOutdatedContainersModel, title="Validate Containers" ) ValidateIntent: ValidateIntentModel = SettingsField( @@ -886,7 +886,7 @@ DEFAULT_PUBLISH_VALUES = { "optional": False, "active": True }, - "ValidateContainers": { + "ValidateOutdatedContainers": { "plugin_state_profiles": [ { # Default host names are based on original From caf3682e87bc3f4356f5ed8a9d54d0c040282588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Tue, 21 May 2024 22:22:33 +0200 Subject: [PATCH 720/727] Make sure actions in Launcher are sorted so they don't keep changing order randomly --- client/ayon_core/tools/launcher/models/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/launcher/models/actions.py b/client/ayon_core/tools/launcher/models/actions.py index 32df600c87..c27f0cd757 100644 --- a/client/ayon_core/tools/launcher/models/actions.py +++ b/client/ayon_core/tools/launcher/models/actions.py @@ -332,7 +332,7 @@ class ActionsModel: selection = self._prepare_selection(project_name, folder_id, task_id) output = [] action_items = self._get_action_items(project_name) - for identifier, action in self._get_action_objects().items(): + for identifier, action in sorted(self._get_action_objects().items()): if not action.is_compatible(selection): continue From 6c461eb21c981ebb70c19d40013f63fcefbd1a5f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 22 May 2024 09:54:27 +0200 Subject: [PATCH 721/727] add addon name to version.py --- server_addon/create_ayon_addons.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index f0a36d4740..749077d2a8 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -47,7 +47,7 @@ plugin_for = ["ayon_server"] """ CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*- -"""Package declaring AYON core addon version.""" +"""Package declaring AYON addon '{}' version.""" __version__ = "{}" ''' @@ -183,6 +183,7 @@ def create_addon_zip( def prepare_client_code( + addon_name: str, addon_dir: Path, addon_output_dir: Path, addon_version: str @@ -211,7 +212,9 @@ def prepare_client_code( version_path = subpath / "version.py" if version_path.exists(): with open(version_path, "w") as stream: - stream.write(CLIENT_VERSION_CONTENT.format(addon_version)) + stream.write( + CLIENT_VERSION_CONTENT.format(addon_name, addon_version) + ) zip_filepath = private_dir / "client.zip" with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: @@ -262,7 +265,9 @@ def create_addon_package( server_dir, addon_output_dir / "server", dirs_exist_ok=True ) - prepare_client_code(addon_dir, addon_output_dir, addon_version) + prepare_client_code( + package.name, addon_dir, addon_output_dir, addon_version + ) if create_zip: create_addon_zip( From dd29bd8fa8f88853fb8cf4ea4ec496ec8e2a41cf Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 22 May 2024 15:46:03 +0300 Subject: [PATCH 722/727] remove original render instance --- .../ayon_core/hosts/houdini/plugins/publish/extract_render.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 7b4762a25f..651df15c10 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -72,3 +72,6 @@ class ExtractRender(publish.Extractor): raise RuntimeError("Failed to complete render extraction. " "Missing output files: {}".format( missing_frames)) + + # Remove original render instance + instance.context.remove(instance) From 60a76239bf254406eb19eef3096e3b94a40d0060 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Wed, 22 May 2024 18:32:28 +0300 Subject: [PATCH 723/727] disable integration for the original render instance Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/hosts/houdini/plugins/publish/extract_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 651df15c10..20e9341ebd 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -74,4 +74,4 @@ class ExtractRender(publish.Extractor): missing_frames)) # Remove original render instance - instance.context.remove(instance) + instance.data["integrate"] = False From ab8d5186b9f9c9a9fc885272dc398b579aa8225e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 22 May 2024 18:42:19 +0300 Subject: [PATCH 724/727] update comment about skipping integration --- .../ayon_core/hosts/houdini/plugins/publish/extract_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 20e9341ebd..267280ad9f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -73,5 +73,5 @@ class ExtractRender(publish.Extractor): "Missing output files: {}".format( missing_frames)) - # Remove original render instance + # Skip integrating original render instance instance.data["integrate"] = False From 48167e23c79baf4510d514426139f07f9ec86a9b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 22 May 2024 17:43:31 +0200 Subject: [PATCH 725/727] Implement sorting proxy model --- .../tools/launcher/ui/actions_widget.py | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/actions_widget.py b/client/ayon_core/tools/launcher/ui/actions_widget.py index a225827418..3e4dfaf6e4 100644 --- a/client/ayon_core/tools/launcher/ui/actions_widget.py +++ b/client/ayon_core/tools/launcher/ui/actions_widget.py @@ -290,6 +290,34 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate): painter.drawPixmap(extender_x, extender_y, pix) +class ActionsProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + + def lessThan(self, left, right): + # Sort by action order and then by label + left_value = left.data(ACTION_SORT_ROLE) + right_value = right.data(ACTION_SORT_ROLE) + + # Values are same -> use super sorting + if left_value == right_value: + # Default behavior is using DisplayRole + return super().lessThan(left, right) + + # Validate 'None' values + if right_value is None: + return True + if left_value is None: + return False + # Sort values and handle incompatible types + try: + return left_value < right_value + except TypeError: + return True + + class ActionsWidget(QtWidgets.QWidget): def __init__(self, controller, parent): super(ActionsWidget, self).__init__(parent) @@ -316,10 +344,7 @@ class ActionsWidget(QtWidgets.QWidget): model = ActionsQtModel(controller) - proxy_model = QtCore.QSortFilterProxyModel() - proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - proxy_model.setSortRole(ACTION_SORT_ROLE) - + proxy_model = ActionsProxyModel() proxy_model.setSourceModel(model) view.setModel(proxy_model) From 88cae86ee67a0cedd2e0635dd1fe1f6a2a45f4d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 22 May 2024 17:43:42 +0200 Subject: [PATCH 726/727] reverse sorting in actions model --- client/ayon_core/tools/launcher/models/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/launcher/models/actions.py b/client/ayon_core/tools/launcher/models/actions.py index c27f0cd757..32df600c87 100644 --- a/client/ayon_core/tools/launcher/models/actions.py +++ b/client/ayon_core/tools/launcher/models/actions.py @@ -332,7 +332,7 @@ class ActionsModel: selection = self._prepare_selection(project_name, folder_id, task_id) output = [] action_items = self._get_action_items(project_name) - for identifier, action in sorted(self._get_action_objects().items()): + for identifier, action in self._get_action_objects().items(): if not action.is_compatible(selection): continue From 7f586576415493f0c49bdb497fb70c9921b038ed Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 22 May 2024 18:46:39 +0300 Subject: [PATCH 727/727] move code to a dedicated place --- .../plugins/publish/collect_local_render_instances.py | 6 +++--- .../hosts/houdini/plugins/publish/extract_render.py | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py index 5a446fa0d3..474002e1ee 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_local_render_instances.py @@ -132,6 +132,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin): ] }) - # Remove original render instance - # I can't remove it here as I still need it to trigger the render. - # context.remove(instance) + # Skip integrating original render instance. + # We are not removing it because it's used to trigger the render. + instance.data["integrate"] = False diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py index 267280ad9f..7b4762a25f 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_render.py @@ -72,6 +72,3 @@ class ExtractRender(publish.Extractor): raise RuntimeError("Failed to complete render extraction. " "Missing output files: {}".format( missing_frames)) - - # Skip integrating original render instance - instance.data["integrate"] = False