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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] 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/633] `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/633] `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/633] `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/633] `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/633] 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/633] 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/633] `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/633] 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/633] =?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/633] =?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/633] `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/633] =?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/633] =?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/633] =?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/633] =?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/633] 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/633] 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/633] 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/633] 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/633] `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/633] `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/633] 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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] `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/633] 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/633] 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/633] 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/633] 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/633] `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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] [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/633] 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/633] 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/633] 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/633] [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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] [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/633] 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/633] 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/633] 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/633] [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/633] 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/633] 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/633] 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/633] 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/633] [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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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/633] 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 c60bd1cb2df44735ed1ce719f88449928f8d4e4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:56:29 +0100 Subject: [PATCH 179/633] 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 180/633] 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 181/633] 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 182/633] :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 183/633] 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 184/633] 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 185/633] 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 186/633] 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 187/633] 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 188/633] 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 189/633] 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 190/633] 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 191/633] 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 192/633] 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 193/633] 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 194/633] 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 195/633] 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 196/633] 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 197/633] 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 198/633] 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 199/633] 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 200/633] 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 201/633] 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 202/633] 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 203/633] 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 204/633] 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 205/633] 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 206/633] Make singular --- .../hosts/houdini/plugins/publish/validate_cop_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py index 8d3b248ecd..a6a7044f77 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_cop_output_node.py @@ -26,7 +26,7 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Output node '{}' are incorrect. " + ("Output node '{}' is incorrect. " "See plug-in log for details.").format(invalid), title=self.label ) From 9666352b6e625f8b45e577bfea9bc39f18af7585 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 12:08:35 +0100 Subject: [PATCH 207/633] 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 208/633] 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 3def9fe29e19691ca8f7d1514ca74677c8edd00a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 28 Mar 2024 10:47:41 +0000 Subject: [PATCH 209/633] 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 210/633] 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 211/633] 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 941e80dd952864adcd4ba4386d5883434f5cd338 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 29 Mar 2024 15:25:41 +0200 Subject: [PATCH 212/633] 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 213/633] 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 214/633] 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 215/633] 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 216/633] 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 217/633] 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 218/633] 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 219/633] 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 220/633] 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 221/633] 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 222/633] 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 223/633] 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 224/633] 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 225/633] 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 226/633] 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 227/633] 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 228/633] 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 229/633] 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 230/633] 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 231/633] Settings for ValidateAlembicOptionsDefaults. --- server_addon/maya/server/settings/publishers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 0e12d10c75..9b10125d1c 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -749,6 +749,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Alembic Visible Node", ) + ValidateAlembicOptionsDefaults: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Alembic Options Defaults" + ) ExtractProxyAlembic: ExtractProxyAlembicModel = SettingsField( default_factory=ExtractProxyAlembicModel, title="Extract Proxy Alembic", @@ -1397,6 +1401,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "validate_shapes": True }, + "ValidateAlembicOptionsDefaults": { + "enabled": True, + "optional": True, + "active": True + }, "ExtractPlayblast": DEFAULT_PLAYBLAST_SETTING, "ExtractMayaSceneRaw": { "enabled": True, From bf26eba7797d5f53a5eda6b2836c43276443d7dc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:45:57 +0200 Subject: [PATCH 232/633] 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 233/633] 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 bcb1c2a0ba3b49c43a08507ad52a9fdce07037ef Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 21:42:56 +0200 Subject: [PATCH 234/633] 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 235/633] 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 236/633] 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 ab74098b7bd49494ee7a1cf60e33605b602495a4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 3 Apr 2024 14:07:39 +0200 Subject: [PATCH 237/633] 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 238/633] 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 239/633] 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 7f703585f12d47bc7d6044faf3641fc61cbdab55 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 4 Apr 2024 10:51:51 +0200 Subject: [PATCH 240/633] 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 4e6bd3d3361708fa7cf84ef69e0e5715ed451df2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 4 Apr 2024 12:32:57 +0200 Subject: [PATCH 241/633] 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 242/633] 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 243/633] 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 244/633] 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 245/633] 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 246/633] 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 247/633] 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 248/633] 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 249/633] 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 250/633] 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 251/633] 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 252/633] 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 253/633] 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 254/633] 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 255/633] 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 256/633] 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 257/633] 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 258/633] 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 259/633] 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 260/633] 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 261/633] 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 a6ca1488997950044ebddc69b418f5605b9103ab Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 8 Apr 2024 14:24:06 +0200 Subject: [PATCH 262/633] 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 263/633] 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 c5df561c970bc1cfc0498f8b1adf52392f5fe969 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 20:20:33 +0800 Subject: [PATCH 264/633] 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 265/633] 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 266/633] 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 267/633] 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 268/633] 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 269/633] 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 270/633] 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 271/633] 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 272/633] 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 273/633] 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 274/633] 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 275/633] 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 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 276/633] 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 d16945311574f13fcbf9311b58273737bcb438fb Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 11 Apr 2024 10:16:51 +0100 Subject: [PATCH 277/633] 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 278/633] 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 279/633] 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 280/633] 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 281/633] 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 282/633] 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 283/633] 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 284/633] 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 285/633] 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 286/633] Update client/ayon_core/hosts/maya/api/alembic.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hosts/maya/api/alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/alembic.py b/client/ayon_core/hosts/maya/api/alembic.py index b657262b4d..b67e0e0062 100644 --- a/client/ayon_core/hosts/maya/api/alembic.py +++ b/client/ayon_core/hosts/maya/api/alembic.py @@ -4,7 +4,7 @@ import os from maya import cmds # noqa -from openpype.hosts.maya.api.lib import evaluation +from ayon_core.hosts.maya.api.lib import evaluation log = logging.getLogger(__name__) From a7450e4ab5c72e9b33fa6909371dd23f6f717d45 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 11 Apr 2024 19:44:31 +0100 Subject: [PATCH 287/633] 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 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 288/633] 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 953238490bf943c083dcbd975abb9bc55b617588 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 12 Apr 2024 11:30:19 +0100 Subject: [PATCH 289/633] 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 290/633] 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 291/633] 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 292/633] 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 293/633] 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 294/633] 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 295/633] 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 296/633] 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 297/633] 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 298/633] 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 299/633] 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 300/633] 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 301/633] 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 302/633] 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 303/633] 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 304/633] 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 305/633] 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 306/633] 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 307/633] 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 308/633] 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 309/633] 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 310/633] 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 311/633] 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 312/633] fix melPostJobCallback --- server_addon/maya/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 6a63d88657..e10df66403 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1628,7 +1628,7 @@ DEFAULT_PUBLISH_SETTINGS = { "dontSkipUnwrittenFrames": False, "eulerFilter": False, "melPerFrameCallback": "", - "melPostFrameCallback": "", + "melPostJobCallback": "", "noNormals": False, "overrides": [ "attr", From c9919ec8801a3112b8147ff31e5f7b611b36f942 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Apr 2024 10:38:45 +0100 Subject: [PATCH 313/633] 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 314/633] 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 315/633] 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 316/633] 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 317/633] 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 318/633] 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 319/633] Bugfix: Parent to world only if not already at world - support root level placeholders --- client/ayon_core/hosts/maya/api/workfile_template_builder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index 75386d7e64..ddf19125e3 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -331,7 +331,8 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if scene_parent: cmds.parent(node, scene_parent) else: - cmds.parent(node, world=True) + if cmds.listRelatives(node, parent=True): + cmds.parent(node, world=True) holding_sets = cmds.listSets(object=placeholder.scene_identifier) if not holding_sets: From affb42e2eda5eb227f40f10e75db795fcd11ef84 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 15 Apr 2024 14:58:31 +0100 Subject: [PATCH 320/633] 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 321/633] 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 322/633] 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 6ef55adaf044a2c3ece228688fdae2cafcb7e5ec Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 15 Apr 2024 21:01:19 +0200 Subject: [PATCH 323/633] 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 324/633] 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 325/633] 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 326/633] 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 327/633] 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 328/633] 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 a3f9651de1a8825c0827d731b1467c9f01fb3e2a Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 16 Apr 2024 11:56:19 +0200 Subject: [PATCH 329/633] 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 330/633] 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 331/633] update import --- client/ayon_core/hosts/houdini/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 3da81b2b51..9cac2ab210 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -839,7 +839,7 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): # fall to default review color space if the setting is empty. if not review_color_space: - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa review_color_space = get_default_display_view_colorspace() opengl_node.setParms( From e8e1a1a2dffd197e61101ccc910c9d3f69c01a1f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 14:09:21 +0200 Subject: [PATCH 332/633] 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 ca3f3910232fc4077574c8272b222c9014ecd2fe Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 17:55:59 +0200 Subject: [PATCH 333/633] 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 334/633] 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 335/633] 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 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 336/633] 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 337/633] 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 338/633] 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 339/633] 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 340/633] 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 341/633] 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 d7d91b62a9d91e72dde457ed0503692e37fb0445 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 17 Apr 2024 15:18:00 +0200 Subject: [PATCH 342/633] 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 343/633] 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 03cfed2c7957538a9c72afe4e31c010cb1d606ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 20:33:39 +0200 Subject: [PATCH 344/633] 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 345/633] 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 346/633] 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 347/633] 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 bd42a506cfd8b9c143f18c198e5920459e836124 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 17:14:06 +0800 Subject: [PATCH 348/633] 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 349/633] 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 350/633] 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 351/633] 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 352/633] 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 353/633] 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 354/633] 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 355/633] 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 356/633] 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 60468e4d7410ff5021d771a09b1e2e16494e6380 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 19 Apr 2024 16:09:18 +0200 Subject: [PATCH 357/633] 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 358/633] 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 359/633] 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 360/633] 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 361/633] 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 362/633] 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 363/633] 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 364/633] 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 365/633] 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 366/633] 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 367/633] 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 368/633] 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 369/633] 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 370/633] 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 371/633] 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 372/633] 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 373/633] 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 374/633] 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 375/633] 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 376/633] 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 377/633] 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 378/633] 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 379/633] 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 380/633] 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 381/633] 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 382/633] 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 383/633] 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 384/633] 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 385/633] 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 386/633] 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 387/633] 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 388/633] 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 389/633] 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 390/633] 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 391/633] 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 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 392/633] 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 393/633] 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 394/633] 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 395/633] 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 396/633] 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 397/633] 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 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 398/633] 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 399/633] 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 400/633] 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 401/633] 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 402/633] 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 403/633] 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 404/633] 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 405/633] :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 406/633] :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 407/633] 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 408/633] 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 409/633] 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 410/633] 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 411/633] 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 412/633] 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 413/633] 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 414/633] 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 415/633] 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 416/633] 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 417/633] 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 418/633] 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 419/633] 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 420/633] 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 421/633] 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 422/633] 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 423/633] 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 424/633] 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 425/633] 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 426/633] 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 427/633] 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 428/633] 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 429/633] 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 430/633] 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 431/633] 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 432/633] 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 433/633] 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 434/633] 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 435/633] 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 436/633] 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 437/633] 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 438/633] 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 439/633] 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 440/633] 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 441/633] 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 442/633] 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 443/633] 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 444/633] :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 445/633] :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 446/633] 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 447/633] :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 448/633] 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 449/633] 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 450/633] 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 451/633] _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 452/633] 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 453/633] :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 454/633] 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 455/633] :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 456/633] 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 457/633] 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 458/633] 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 459/633] 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 460/633] 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 461/633] 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 462/633] 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 463/633] 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 464/633] 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 465/633] 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 466/633] 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 467/633] 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 468/633] 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 469/633] 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 470/633] 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 471/633] 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 472/633] 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 473/633] 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 474/633] 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 475/633] 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 476/633] 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 477/633] 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 478/633] :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 479/633] :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 480/633] 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 481/633] 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 482/633] 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 483/633] 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 484/633] :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 485/633] 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 486/633] :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 487/633] :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 488/633] 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 489/633] 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 490/633] 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 491/633] 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 492/633] 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 493/633] 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 494/633] 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 495/633] 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 496/633] 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 497/633] 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 498/633] 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 499/633] 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 500/633] 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 501/633] 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 502/633] 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 503/633] 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 504/633] 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 505/633] :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 506/633] 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 507/633] 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 508/633] 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 509/633] 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 510/633] 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 511/633] 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 512/633] 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 513/633] 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 514/633] '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 515/633] 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 516/633] 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 517/633] 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 518/633] 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 519/633] 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 520/633] 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 521/633] 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 522/633] 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 523/633] 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 524/633] 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 525/633] 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 526/633] '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 527/633] 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 528/633] 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 529/633] 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 530/633] 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 531/633] 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 532/633] 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 533/633] 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 534/633] 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 535/633] 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 536/633] 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 537/633] 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 538/633] 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 539/633] 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 540/633] 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 541/633] 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 542/633] 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 543/633] 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 544/633] 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 545/633] 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 546/633] 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 547/633] 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 548/633] 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 549/633] 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 550/633] 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 551/633] 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 552/633] 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 553/633] 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 554/633] 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 555/633] 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 556/633] 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 557/633] 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 558/633] 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 559/633] 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 560/633] 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 561/633] 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 562/633] 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 563/633] 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 564/633] 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 565/633] 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 566/633] 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 567/633] 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 568/633] 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 569/633] 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 570/633] 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 571/633] 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 572/633] 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 573/633] 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 574/633] 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 575/633] 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 576/633] 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 577/633] 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 578/633] 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 579/633] 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 580/633] 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 581/633] 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 582/633] 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 583/633] 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 584/633] 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 585/633] 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 586/633] 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 587/633] 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 588/633] 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 589/633] 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 590/633] 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 591/633] 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 592/633] 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 593/633] 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 594/633] 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 595/633] 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 596/633] 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 597/633] 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 598/633] 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 599/633] 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 600/633] 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 601/633] 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 602/633] 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 603/633] :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 604/633] 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 605/633] 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 606/633] 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 607/633] 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 608/633] 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 609/633] 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 610/633] 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 611/633] 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 612/633] 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 613/633] 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 614/633] 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 615/633] 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 616/633] 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 617/633] 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 618/633] 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 619/633] 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 620/633] 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 621/633] 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 622/633] :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 623/633] 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 624/633] 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 625/633] 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 626/633] 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 627/633] 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 628/633] 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 629/633] 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 630/633] 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 631/633] 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 632/633] 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 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 633/633] 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(