From 7402662161441640ba10df0d0d47556a3310eae4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2023 23:12:14 +0100 Subject: [PATCH 01/91] Remove `shelf` class and shelf build on maya `userSetup.py` --- openpype/hosts/maya/api/lib.py | 113 ----------------------- openpype/hosts/maya/startup/userSetup.py | 19 ---- 2 files changed, 132 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9b..f7eaf358fe7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2921,119 +2921,6 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def _null(*args): - pass - - -class shelf(): - '''A simple class to build shelves in maya. Since the build method is empty, - it should be extended by the derived class to build the necessary shelf - elements. By default it creates an empty shelf called "customShelf".''' - - ########################################################################### - '''This is an example shelf.''' - # class customShelf(_shelf): - # def build(self): - # self.addButon(label="button1") - # self.addButon("button2") - # self.addButon("popup") - # p = cmds.popupMenu(b=1) - # self.addMenuItem(p, "popupMenuItem1") - # self.addMenuItem(p, "popupMenuItem2") - # sub = self.addSubMenu(p, "subMenuLevel1") - # self.addMenuItem(sub, "subMenuLevel1Item1") - # sub2 = self.addSubMenu(sub, "subMenuLevel2") - # self.addMenuItem(sub2, "subMenuLevel2Item1") - # self.addMenuItem(sub2, "subMenuLevel2Item2") - # self.addMenuItem(sub, "subMenuLevel1Item2") - # self.addMenuItem(p, "popupMenuItem3") - # self.addButon("button3") - # customShelf() - ########################################################################### - - def __init__(self, name="customShelf", iconPath="", preset={}): - self.name = name - - self.iconPath = iconPath - - self.labelBackground = (0, 0, 0, 0) - self.labelColour = (.9, .9, .9) - - self.preset = preset - - self._cleanOldShelf() - cmds.setParent(self.name) - self.build() - - def build(self): - '''This method should be overwritten in derived classes to actually - build the shelf elements. Otherwise, nothing is added to the shelf.''' - for item in self.preset['items']: - if not item.get('command'): - item['command'] = self._null - if item['type'] == 'button': - self.addButon(item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'menuItem': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'subMenu': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - - def addButon(self, label, icon="commandButton.png", - command=_null, doubleCommand=_null): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - cmds.setParent(self.name) - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - cmds.shelfButton(width=37, height=37, image=icon, label=label, - command=command, dcc=doubleCommand, - imageOverlayLabel=label, olb=self.labelBackground, - olc=self.labelColour) - - def addMenuItem(self, parent, label, command=_null, icon=""): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, c=command, i="") - - def addSubMenu(self, parent, label, icon=None): - ''' - Adds a sub menu item with the specified label and icon to - the specified parent popup menu. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1) - - def _cleanOldShelf(self): - ''' - Checks if the shelf exists and empties it if it does - or creates it if it does not. - ''' - if cmds.shelfLayout(self.name, ex=1): - if cmds.shelfLayout(self.name, q=1, ca=1): - for each in cmds.shelfLayout(self.name, q=1, ca=1): - cmds.deleteUI(each) - else: - cmds.shelfLayout(self.name, p="ShelfLayout") - - def _get_render_instances(): """Return all 'render-like' instances. diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index f2899cdb370..417f72a59f7 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -46,24 +46,5 @@ def _log_and_open(): lowestPriority=True ) -# Build a shelf. -shelf_preset = settings['maya'].get('project_shelf') -if shelf_preset: - icon_path = os.path.join( - os.environ['OPENPYPE_PROJECT_SCRIPTS'], - project_name, - "icons") - icon_path = os.path.abspath(icon_path) - - for i in shelf_preset['imports']: - import_string = "from {} import {}".format(project_name, i) - print(import_string) - exec(import_string) - - cmds.evalDeferred( - "mlib.shelf(name=shelf_preset['name'], iconPath=icon_path," - " preset=shelf_preset)" - ) - print("Finished OpenPype usersetup.") From 6c04a6df1a120eead69fb90b192e26017e95f334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 21 Dec 2023 11:53:35 +0100 Subject: [PATCH 02/91] :bug: fix default render product name --- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index a1f45547268..f60d8c664cf 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -67,7 +67,7 @@ def process(self, instance): beauty_product = self.get_render_product_name(default_prefix) render_products.append(beauty_product) files_by_aov = { - "RGB Color": self.generate_expected_files(instance, + "": self.generate_expected_files(instance, beauty_product)} if instance.data.get("RenderElement", True): @@ -75,7 +75,9 @@ def process(self, instance): if render_element: for aov, renderpass in render_element.items(): render_products.append(renderpass) - files_by_aov[aov] = self.generate_expected_files(instance, renderpass) # noqa + files_by_aov[aov] = self.generate_expected_files( + instance, renderpass) + for product in render_products: self.log.debug("Found render product: %s" % product) From 0fc23ce2e7600c335bd05ccc642f742497a36282 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:06:31 +0000 Subject: [PATCH 03/91] Changed logic for creation of output node --- openpype/hosts/blender/api/render_lib.py | 70 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index b437078ad8b..cdccf138055 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -135,71 +135,95 @@ def set_render_passes(settings): return aov_list, custom_passes -def set_node_tree(output_path, name, aov_sep, ext, multilayer): +def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree + comp_layer_type = "CompositorNodeRLayers" + output_type = "CompositorNodeOutputFile" + # Get the Render Layers node rl_node = None for node in tree.nodes: - if node.bl_idname == "CompositorNodeRLayers": + if node.bl_idname == comp_layer_type: rl_node = node break # If there's not a Render Layers node, we create it if not rl_node: - rl_node = tree.nodes.new("CompositorNodeRLayers") + rl_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Image", "Alpha", "Noisy Image"] + exclude_sockets = ["Alpha", "Noisy Image"] passes = [ socket for socket in rl_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - # Remove all output nodes + old_output = None + + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. for node in tree.nodes: - if node.bl_idname == "CompositorNodeOutputFile": - tree.nodes.remove(node) + if node.bl_idname == output_type and "AYON" in node.name: + old_output = node + break # Create a new output node - output = tree.nodes.new("CompositorNodeOutputFile") + output = tree.nodes.new(output_type) image_settings = bpy.context.scene.render.image_settings output.format.file_format = image_settings.file_format + slots = None + # In case of a multilayer exr, we don't need to use the output node, # because the blender render already outputs a multilayer exr. - if ext == "exr" and multilayer: - output.layer_slots.clear() - return [] + multi_exr = ext == "exr" and multilayer + slots = output.layer_slots if multi_exr else output.file_slots + output.base_path = render_product if multi_exr else str(output_path) - output.file_slots.clear() - output.base_path = str(output_path) + slots.clear() aov_file_products = [] # For each active render pass, we add a new socket to the output node # and link it - for render_pass in passes: - filepath = f"{name}{aov_sep}{render_pass.name}.####" + for rpass in passes: + if rpass.name == "Image": + pass_name = "rgba" if multi_exr else "beauty" + else: + pass_name = rpass.name + filepath = f"{name}{aov_sep}{pass_name}.####" - output.file_slots.new(filepath) + slots.new(pass_name if multi_exr else filepath) filename = str(output_path / filepath.lstrip("/")) - aov_file_products.append((render_pass.name, filename)) + aov_file_products.append((rpass.name, filename)) + + if not old_output: + node_input = output.inputs[-1] + tree.links.new(rpass, node_input) - node_input = output.inputs[-1] + for link in tree.links: + if link.to_node == old_output: + socket_name = link.to_socket.name + new_socket = output.inputs.get(socket_name) + if new_socket: + tree.links.new(link.from_socket, new_socket) - tree.links.new(render_pass, node_input) + if old_output: + output.location = old_output.location + tree.nodes.remove(old_output) + output.name = "AYON File Output" - return aov_file_products + return [] if multi_exr else aov_file_products def imprint_render_settings(node, data): @@ -236,9 +260,11 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, ext, multilayer) - bpy.context.scene.render.filepath = render_product + # Clear the render filepath, so that the output is handled only by the + # output node in the compositor. + bpy.context.scene.render.filepath = "" render_settings = { "render_folder": render_folder, From fa5b9777a98cca34f5cd5412500f34825a6ff3d8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:07:00 +0000 Subject: [PATCH 04/91] Create a new version of the workfile instead of overwriting current one --- openpype/hosts/blender/plugins/create/create_render.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 7fb3e5eb006..e728579286e 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -1,8 +1,10 @@ """Create render.""" import bpy +from openpype.lib import version_up from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.render_lib import prepare_rendering +from openpype.hosts.blender.api.workio import save_file class CreateRenderlayer(plugin.BaseCreator): @@ -37,6 +39,7 @@ def create( # settings. Even the validator to check that the file is saved will # detect the file as saved, even if it isn't. The only solution for # now it is to force the file to be saved. - bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) + filepath = version_up(bpy.data.filepath) + save_file(filepath, copy=False) return collection From 04dfe1863f267b210f6a3dc57b8f9890957e8d3c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 20:46:27 +0800 Subject: [PATCH 05/91] Implement the validator for camera attributes in Max host --- openpype/hosts/max/api/action.py | 39 +++++++++++ .../publish/validate_camera_attributes.py | 59 +++++++++++++++++ openpype/pipeline/publish/lib.py | 2 +- .../defaults/project_settings/max.json | 10 +++ .../schemas/schema_max_publish.json | 66 +++++++++++++++++++ .../max/server/settings/publishers.py | 26 +++++++- server_addon/max/server/version.py | 2 +- 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/api/action.py create mode 100644 openpype/hosts/max/plugins/publish/validate_camera_attributes.py diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py new file mode 100644 index 00000000000..506847652bb --- /dev/null +++ b/openpype/hosts/max/api/action.py @@ -0,0 +1,39 @@ +from pymxs import runtime as rt + +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid objects in Blender when a publish plug-in failed.""" + label = "Select Invalid" + on = "failed" + icon = "search" + + 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...") + invalid = list() + for instance in errored_instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning( + "Failed plug-in doesn't have any selectable objects." + ) + + if not invalid: + self.log.info("No invalid nodes found.") + return + invalid_names = [obj.name for obj in invalid] + self.log.info( + "Selecting invalid objects: %s", ", ".join(invalid_names) + ) + + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py new file mode 100644 index 00000000000..e279b414757 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -0,0 +1,59 @@ +import pyblish.api +from pymxs import runtime as rt + +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) +from openpype.hosts.max.api.action import SelectInvalidAction + + +class ValidateCameraAttributes(OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin): + """Validates Camera has no invalid attribute properties + or values.(For 3dsMax Cameras only) + + """ + + order = pyblish.api.ValidatorOrder + families = ['camera'] + hosts = ['max'] + label = 'Camera Attributes' + actions = [SelectInvalidAction] + optional = True + + DEFAULTS = ["fov", "nearrange", "farrange", + "nearclip","farclip"] + CAM_TYPE = ["Freecamera", "Targetcamera", + "Physical"] + + @classmethod + def get_invalid(cls, instance): + invalid = [] + cameras = instance.data["members"] + project_settings = instance.context.data["project_settings"].get("max") + cam_attr_settings = project_settings["publish"]["ValidateCameraAttributes"] + for camera in cameras: + if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: + cls.log.debug( + "Skipping camera created from external plugin..") + continue + for attr in cls.DEFAULTS: + default_value = cam_attr_settings.get(attr) + if rt.getProperty(camera, attr) != default_value: + cls.log.error( + f"Invalid attribute value: {attr} " + f"(should be: {default_value}))") + invalid.append(camera) + + return invalid + + def process(self, instance): + if not self.is_active(instance.data): + self.log.debug("Skipping Validate Camera Attributes...") + return + invalid = self.get_invalid(instance) + + if invalid: + raise PublishValidationError( + f"Invalid camera attributes: {invalid}") diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 4ea2f932f14..87ca3323cbd 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -74,7 +74,7 @@ def get_template_name_profiles( project_settings ["global"] ["publish"] - ["IntegrateAssetNew"] + ["IntegrateHeroVersion"] ["template_name_profiles"] ) if legacy_profiles: diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc6..a0a4fcf83d8 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -56,6 +56,16 @@ "enabled": false, "attributes": {} }, + "ValidateCameraAttributes": { + "enabled": true, + "optional": true, + "active": false, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda988..90a5d2fc18d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -48,6 +48,72 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateCameraAttributes", + "label": "Validate Camera Attributes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "number", + "key": "fov", + "label": "Focal Length", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "nearrange", + "label": "Near Range", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farrange", + "label": "Far Range", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + }, + { + "type": "number", + "key": "nearclip", + "label": "Near Clip", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farclip", + "label": "Far Clip", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + } + ] + }, + { "type": "dict", "collapsible": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99be..340f344a9f9 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -27,6 +27,17 @@ def validate_json(cls, value): return value +class ValidateCameraAttributesModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + fov: float = Field(0.0, title="Focal Length") + nearrange: float = Field(0.0, title="Near Range") + farrange: float = Field(0.0, title="Far Range") + nearclip: float = Field(0.0, title="Near Clip") + farclip: float = Field(0.0, title="Far Clip") + + class FamilyMappingItemModel(BaseSettingsModel): product_types: list[str] = Field( default_factory=list, @@ -63,7 +74,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateAttributesModel, title="Validate Attributes" ) - + ValidateCameraAttributes: ValidateCameraAttributesModel = Field( + default_factory=ValidateCameraAttributesModel, + title="Validate Camera Attributes" + ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" @@ -101,6 +115,16 @@ class PublishersModel(BaseSettingsModel): "enabled": False, "attributes": "{}" }, + "ValidateCameraAttributes": { + "enabled": True, + "optional": True, + "active": False, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": False, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6a..1276d0254ff 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From c29a4fdfd64dccb22375749b766b9e01aca9d815 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 21:08:51 +0800 Subject: [PATCH 06/91] hound shut --- .../hosts/max/plugins/publish/validate_camera_attributes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index e279b414757..d97d5529e3d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -23,7 +23,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, optional = True DEFAULTS = ["fov", "nearrange", "farrange", - "nearclip","farclip"] + "nearclip", "farclip"] CAM_TYPE = ["Freecamera", "Targetcamera", "Physical"] @@ -32,7 +32,9 @@ def get_invalid(cls, instance): invalid = [] cameras = instance.data["members"] project_settings = instance.context.data["project_settings"].get("max") - cam_attr_settings = project_settings["publish"]["ValidateCameraAttributes"] + cam_attr_settings = ( + project_settings["publish"]["ValidateCameraAttributes"] + ) for camera in cameras: if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: cls.log.debug( From 3c878ccc3e34d8bb6884e731cc035881a0449947 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 13:27:32 +0800 Subject: [PATCH 07/91] make sure the validator skips checking when the value of the attribute sets to zero --- .../max/plugins/publish/validate_camera_attributes.py | 11 ++++++++--- .../projects_schema/schemas/schema_max_publish.json | 4 ++++ server_addon/max/server/settings/publishers.py | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index d97d5529e3d..36f0beab65d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -18,7 +18,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder families = ['camera'] hosts = ['max'] - label = 'Camera Attributes' + label = 'Validate Camera Attributes' actions = [SelectInvalidAction] optional = True @@ -42,9 +42,14 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) + if default_value == 0: + cls.log.debug( + f"the value of {attr} in setting set to" + " zero. Skipping the check..") + continue if rt.getProperty(camera, attr) != default_value: cls.log.error( - f"Invalid attribute value: {attr} " + f"Invalid attribute value for {camera.name}:{attr} " f"(should be: {default_value}))") invalid.append(camera) @@ -58,4 +63,4 @@ def process(self, instance): if invalid: raise PublishValidationError( - f"Invalid camera attributes: {invalid}") + "Invalid camera attributes found. See log.") diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index 90a5d2fc18d..1e7a7c0c739 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -79,6 +79,10 @@ "minimum": 0, "maximum": 100.0 }, + { + "type": "label", + "label": "If the value of the camera attributes set to 0, the system automatically skips checking it" + }, { "type": "number", "key": "nearrange", diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 340f344a9f9..a092f54a62b 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -76,7 +76,11 @@ class PublishersModel(BaseSettingsModel): ) ValidateCameraAttributes: ValidateCameraAttributesModel = Field( default_factory=ValidateCameraAttributesModel, - title="Validate Camera Attributes" + title="Validate Camera Attributes", + description=( + "If the value of the camera attributes set to 0, " + "the system automatically skips checking it" + ) ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, From 9baff5eccfea5e2c7a5dc8c15efc4b75a916799f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 18:04:19 +0800 Subject: [PATCH 08/91] add repair actions for validate camera attributes --- .../publish/validate_camera_attributes.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 36f0beab65d..94aa5742d82 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -2,6 +2,7 @@ from pymxs import runtime as rt from openpype.pipeline.publish import ( + RepairAction, OptionalPyblishPluginMixin, PublishValidationError ) @@ -19,7 +20,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, families = ['camera'] hosts = ['max'] label = 'Validate Camera Attributes' - actions = [SelectInvalidAction] + actions = [SelectInvalidAction, RepairAction] optional = True DEFAULTS = ["fov", "nearrange", "farrange", @@ -42,10 +43,11 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - if default_value == 0: + cls.log.debug(f"default value: {default_value}") + if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" - " zero. Skipping the check..") + " zero. Skipping the check.") continue if rt.getProperty(camera, attr) != default_value: cls.log.error( @@ -57,10 +59,26 @@ def get_invalid(cls, instance): def process(self, instance): if not self.is_active(instance.data): - self.log.debug("Skipping Validate Camera Attributes...") + self.log.debug("Skipping Validate Camera Attributes.") return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( "Invalid camera attributes found. See log.") + + @classmethod + def repair(cls, instance): + invalid_cameras = cls.get_invalid(instance) + project_settings = instance.context.data["project_settings"].get("max") + cam_attr_settings = ( + project_settings["publish"]["ValidateCameraAttributes"] + ) + for camera in invalid_cameras: + for attr in cls.DEFAULTS: + expected_value = cam_attr_settings.get(attr) + if expected_value == float(0): + cls.log.debug( + f"the value of {attr} in setting set to zero.") + continue + rt.setProperty(camera, attr, expected_value) From 15167fa0b805b03657857097db52dd554c163620 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 22:46:21 +0800 Subject: [PATCH 09/91] the validator posts on warning message if the users dont use generic types & round 1 decimal places for all check values --- .../max/plugins/publish/validate_camera_attributes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 94aa5742d82..4eec1951e5d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -31,6 +31,11 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, @classmethod def get_invalid(cls, instance): invalid = [] + if rt.units.DisplayType != rt.Name("Generic"): + cls.log.warning( + "Generic Type is not used as a scene unit\n\n" + "sure you tweak the settings with your own values\n\n" + "before validation.") cameras = instance.data["members"] project_settings = instance.context.data["project_settings"].get("max") cam_attr_settings = ( @@ -43,13 +48,12 @@ def get_invalid(cls, instance): continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - cls.log.debug(f"default value: {default_value}") if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" " zero. Skipping the check.") continue - if rt.getProperty(camera, attr) != default_value: + if round(rt.getProperty(camera, attr), 1) != default_value: cls.log.error( f"Invalid attribute value for {camera.name}:{attr} " f"(should be: {default_value}))") From e0e2cb06dd8d85ec73482285a75606bcf26b99ea Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:20:19 +0100 Subject: [PATCH 10/91] 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 3866477c77b..f7150e945b5 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 a00933405f1..a6123ed5d00 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -408,7 +408,14 @@ def load(self, files): _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 @@ def load(self, files): 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 11/91] 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 a6123ed5d00..77e2991d3f2 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -411,11 +411,7 @@ def load(self, files): # 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 12/91] 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 3866477c77b..96caacf8d10 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 a00933405f1..2346739e20b 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -477,14 +477,16 @@ def update(self, timeline_item, files): ) _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 13/91] 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 77e2991d3f2..d254e5213c9 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -412,6 +412,7 @@ def load(self, files): # 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 @@ def load(self, files): 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 0534c9c55d9eb5d0f193b2c64c3a870b6082a092 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:13:24 +0000 Subject: [PATCH 14/91] Fix error when getting setting value in Ayon --- openpype/hosts/blender/api/render_lib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index cdccf138055..904cabfc05f 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -2,6 +2,7 @@ import bpy +from openpype import AYON_SERVER_ENABLED from openpype.settings import get_project_settings from openpype.pipeline import get_current_project_name @@ -124,13 +125,14 @@ def set_render_passes(settings): aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: - cp_name = cp[0] + cp_name = cp["attribute"] if AYON_SERVER_ENABLED else cp[0] if cp_name not in aovs_names: aov = vl.aovs.add() aov.name = cp_name else: aov = vl.aovs[cp_name] - aov.type = cp[1].get("type", "VALUE") + aov.type = (cp["value"] + if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) return aov_list, custom_passes From 1eb07bcedb97edef9bbf57fa7c605bf85794a33f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:05 +0000 Subject: [PATCH 15/91] Added setting to choose render engine --- openpype/hosts/blender/api/render_lib.py | 11 ++++++++++- .../blender/server/settings/render_settings.py | 13 +++++++++++++ server_addon/blender/server/version.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 904cabfc05f..06353f8e8b5 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -48,6 +48,14 @@ def get_multilayer(settings): ["multilayer_exr"]) +def get_renderer(settings): + """Get renderer from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["renderer"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -254,9 +262,10 @@ def prepare_rendering(asset_group): aov_sep = get_aov_separator(settings) ext = get_image_format(settings) multilayer = get_multilayer(settings) + renderer = get_renderer(settings) set_render_format(ext, multilayer) - aov_list, custom_passes = set_render_passes(settings) + bpy.context.scene.render.engine = renderer output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index f62013982e8..53cefd145df 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -25,6 +25,13 @@ def image_format_enum(): ] +def renderers_enum(): + return [ + {"value": "CYCLES", "label": "Cycles"}, + {"value": "BLENDER_EEVEE", "label": "Eevee"}, + ] + + def aov_list_enum(): return [ {"value": "empty", "label": "< none >"}, @@ -83,6 +90,11 @@ class RenderSettingsModel(BaseSettingsModel): multilayer_exr: bool = Field( title="Multilayer (EXR)" ) + renderer: str = Field( + "CYCLES", + title="Renderer", + enum_resolver=renderers_enum + ) aov_list: list[str] = Field( default_factory=list, enum_resolver=aov_list_enum, @@ -104,6 +116,7 @@ class RenderSettingsModel(BaseSettingsModel): "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": True, + "renderer": "CYCLES", "aov_list": [], "custom_passes": [] } diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py index 1276d0254ff..0a8da882586 100644 --- a/server_addon/blender/server/version.py +++ b/server_addon/blender/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 2f58708f584a63a68ffcbe2c429704dca566f43b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:31 +0000 Subject: [PATCH 16/91] Updated aov list --- openpype/hosts/blender/api/render_lib.py | 64 ++++++++++++++----- .../server/settings/render_settings.py | 50 ++++++++++++--- 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 06353f8e8b5..44ee2be208c 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -100,36 +100,69 @@ def set_render_format(ext, multilayer): image_settings.file_format = "TIFF" -def set_render_passes(settings): - aov_list = (settings["blender"] - ["RenderSettings"] - ["aov_list"]) - - custom_passes = (settings["blender"] - ["RenderSettings"] - ["custom_passes"]) +def set_render_passes(settings, renderer): + aov_list = settings["blender"]["RenderSettings"]["aov_list"] + custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] + # Common passes for both renderers vl = bpy.context.view_layer + # Data Passes vl.use_pass_combined = "combined" in aov_list vl.use_pass_z = "z" in aov_list vl.use_pass_mist = "mist" in aov_list vl.use_pass_normal = "normal" in aov_list + + # Light Passes vl.use_pass_diffuse_direct = "diffuse_light" in aov_list vl.use_pass_diffuse_color = "diffuse_color" in aov_list vl.use_pass_glossy_direct = "specular_light" in aov_list vl.use_pass_glossy_color = "specular_color" in aov_list - vl.eevee.use_pass_volume_direct = "volume_light" in aov_list vl.use_pass_emit = "emission" in aov_list vl.use_pass_environment = "environment" in aov_list - vl.use_pass_shadow = "shadow" in aov_list vl.use_pass_ambient_occlusion = "ao" in aov_list - cycles = vl.cycles - - cycles.denoising_store_passes = "denoising" in aov_list - cycles.use_pass_volume_direct = "volume_direct" in aov_list - cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + # Cryptomatte Passes + vl.use_pass_cryptomatte_object = "cryptomatte_object" in aov_list + vl.use_pass_cryptomatte_material = "cryptomatte_material" in aov_list + vl.use_pass_cryptomatte_asset = "cryptomatte_asset" in aov_list + + if renderer == "BLENDER_EEVEE": + # Eevee exclusive passes + eevee = vl.eevee + + # Light Passes + vl.use_pass_shadow = "shadow" in aov_list + eevee.use_pass_volume_direct = "volume_light" in aov_list + + # Effects Passes + eevee.use_pass_bloom = "bloom" in aov_list + eevee.use_pass_transparent = "transparent" in aov_list + + # Cryptomatte Passes + vl.use_pass_cryptomatte_accurate = "cryptomatte_accurate" in aov_list + elif renderer == "CYCLES": + # Cycles exclusive passes + cycles = vl.cycles + + # Data Passes + vl.use_pass_position = "position" in aov_list + vl.use_pass_vector = "vector" in aov_list + vl.use_pass_uv = "uv" in aov_list + cycles.denoising_store_passes = "denoising" in aov_list + vl.use_pass_object_index = "object_index" in aov_list + vl.use_pass_material_index = "material_index" in aov_list + cycles.pass_debug_sample_count = "sample_count" in aov_list + + # Light Passes + vl.use_pass_diffuse_indirect = "diffuse_indirect" in aov_list + vl.use_pass_glossy_indirect = "specular_indirect" in aov_list + vl.use_pass_transmission_direct = "transmission_direct" in aov_list + vl.use_pass_transmission_indirect = "transmission_indirect" in aov_list + vl.use_pass_transmission_color = "transmission_color" in aov_list + cycles.use_pass_volume_direct = "volume_light" in aov_list + cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + cycles.use_pass_shadow_catcher = "shadow" in aov_list aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: @@ -266,6 +299,7 @@ def prepare_rendering(asset_group): set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer + aov_list, custom_passes = set_render_passes(settings, renderer) output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 53cefd145df..3ab720fc6a1 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -39,18 +39,52 @@ def aov_list_enum(): {"value": "z", "label": "Z"}, {"value": "mist", "label": "Mist"}, {"value": "normal", "label": "Normal"}, - {"value": "diffuse_light", "label": "Diffuse Light"}, + {"value": "position", "label": "Position (Cycles Only)"}, + {"value": "vector", "label": "Vector (Cycles Only)"}, + {"value": "uv", "label": "UV (Cycles Only)"}, + {"value": "denoising", "label": "Denoising Data (Cycles Only)"}, + {"value": "object_index", "label": "Object Index (Cycles Only)"}, + {"value": "material_index", "label": "Material Index (Cycles Only)"}, + {"value": "sample_count", "label": "Sample Count (Cycles Only)"}, + {"value": "diffuse_light", "label": "Diffuse Light/Direct"}, + { + "value": "diffuse_indirect", + "label": "Diffuse Indirect (Cycles Only)" + }, {"value": "diffuse_color", "label": "Diffuse Color"}, - {"value": "specular_light", "label": "Specular Light"}, - {"value": "specular_color", "label": "Specular Color"}, - {"value": "volume_light", "label": "Volume Light"}, + {"value": "specular_light", "label": "Specular (Glossy) Light/Direct"}, + { + "value": "specular_indirect", + "label": "Specular (Glossy) Indirect (Cycles Only)" + }, + {"value": "specular_color", "label": "Specular (Glossy) Color"}, + { + "value": "transmission_light", + "label": "Transmission Light/Direct (Cycles Only)" + }, + { + "value": "transmission_indirect", + "label": "Transmission Indirect (Cycles Only)" + }, + { + "value": "transmission_color", + "label": "Transmission Color (Cycles Only)" + }, + {"value": "volume_light", "label": "Volume Light/Direct"}, + {"value": "volume_indirect", "label": "Volume Indirect (Cycles Only)"}, {"value": "emission", "label": "Emission"}, {"value": "environment", "label": "Environment"}, - {"value": "shadow", "label": "Shadow"}, + {"value": "shadow", "label": "Shadow/Shadow Catcher"}, {"value": "ao", "label": "Ambient Occlusion"}, - {"value": "denoising", "label": "Denoising"}, - {"value": "volume_direct", "label": "Direct Volumetric Scattering"}, - {"value": "volume_indirect", "label": "Indirect Volumetric Scattering"} + {"value": "bloom", "label": "Bloom (Eevee Only)"}, + {"value": "transparent", "label": "Transparent (Eevee Only)"}, + {"value": "cryptomatte_object", "label": "Cryptomatte Object"}, + {"value": "cryptomatte_material", "label": "Cryptomatte Material"}, + {"value": "cryptomatte_asset", "label": "Cryptomatte Asset"}, + { + "value": "cryptomatte_accurate", + "label": "Cryptomatte Accurate Mode (Eevee Only)" + }, ] From 91f67ca9b0a199e110650fd899d562eae3cdcd16 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:00:58 +0000 Subject: [PATCH 17/91] Updated default settings for render passes --- server_addon/blender/server/settings/render_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 3ab720fc6a1..580547e5101 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -151,6 +151,6 @@ class RenderSettingsModel(BaseSettingsModel): "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", - "aov_list": [], + "aov_list": ["combined"], "custom_passes": [] } From dd0533ecb36b19988ccb67b0b2e83a50f7090be1 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:03:05 +0000 Subject: [PATCH 18/91] Added composite output --- openpype/hosts/blender/api/render_lib.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 44ee2be208c..fc47f5a6590 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -186,12 +186,17 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): comp_layer_type = "CompositorNodeRLayers" output_type = "CompositorNodeOutputFile" + compositor_type = "CompositorNodeComposite" - # Get the Render Layers node + # Get the Render Layer and Composite nodes rl_node = None + comp_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: rl_node = node + elif node.bl_idname == compositor_type: + comp_node = node + if rl_node and comp_node: break # If there's not a Render Layers node, we create it @@ -242,29 +247,38 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "rgba" if multi_exr else "beauty" else: pass_name = rpass.name - filepath = f"{name}{aov_sep}{pass_name}.####" + filename = f"{name}{aov_sep}{pass_name}.####" - slots.new(pass_name if multi_exr else filepath) + slots.new(pass_name if multi_exr else filename) - filename = str(output_path / filepath.lstrip("/")) + filepath = str(output_path / filename.lstrip("/")) - aov_file_products.append((rpass.name, filename)) + aov_file_products.append((rpass.name, filepath)) if not old_output: node_input = output.inputs[-1] tree.links.new(rpass, node_input) + # Create a new socket for the composite output + pass_name = "composite" + filename = f"{name}{aov_sep}{pass_name}.####" + comp_socket = slots.new(pass_name if multi_exr else filename) + aov_file_products.append(("Composite", filepath)) + for link in tree.links: if link.to_node == old_output: socket_name = link.to_socket.name new_socket = output.inputs.get(socket_name) if new_socket: tree.links.new(link.from_socket, new_socket) + elif comp_node and link.to_node == comp_node: + tree.links.new(link.from_socket, comp_socket) if old_output: output.location = old_output.location tree.nodes.remove(old_output) output.name = "AYON File Output" + output.label = "AYON File Output" return [] if multi_exr else aov_file_products From c911c67dae109a3c769fa4fe584f925f3afe7a2e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 11:04:25 +0000 Subject: [PATCH 19/91] Updated OpenPype Settings --- .../defaults/project_settings/blender.json | 3 +- .../schema_project_blender.json | 43 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 385e97ef91e..48f3ef8ef0e 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -22,7 +22,8 @@ "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": true, - "aov_list": [], + "renderer": "CYCLES", + "aov_list": ["combined"], "custom_passes": [] }, "workfile_builder": { 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 535d9434a39..bbed881ab0b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -103,6 +103,17 @@ "type": "label", "label": "Note: Multilayer EXR is only used when output format type set to EXR." }, + { + "key": "renderer", + "label": "Renderer", + "type": "enum", + "multiselection": false, + "defaults": "CYCLES", + "enum_items": [ + {"CYCLES": "Cycles"}, + {"BLENDER_EEVEE": "Eevee"} + ] + }, { "key": "aov_list", "label": "AOVs to create", @@ -115,18 +126,34 @@ {"z": "Z"}, {"mist": "Mist"}, {"normal": "Normal"}, - {"diffuse_light": "Diffuse Light"}, + {"position": "Position (Cycles Only)"}, + {"vector": "Vector (Cycles Only)"}, + {"uv": "UV (Cycles Only)"}, + {"denoising": "Denoising Data (Cycles Only)"}, + {"object_index": "Object Index (Cycles Only)"}, + {"material_index": "Material Index (Cycles Only)"}, + {"sample_count": "Sample Count (Cycles Only)"}, + {"diffuse_light": "Diffuse Light/Direct"}, + {"diffuse_indirect": "Diffuse Indirect (Cycles Only)"}, {"diffuse_color": "Diffuse Color"}, - {"specular_light": "Specular Light"}, - {"specular_color": "Specular Color"}, - {"volume_light": "Volume Light"}, + {"specular_light": "Specular (Glossy) Light/Direct"}, + {"specular_indirect": "Specular (Glossy) Indirect (Cycles Only)"}, + {"specular_color": "Specular (Glossy) Color"}, + {"transmission_light": "Transmission Light/Direct (Cycles Only)"}, + {"transmission_indirect": "Transmission Indirect (Cycles Only)"}, + {"transmission_color": "Transmission Color (Cycles Only)"}, + {"volume_light": "Volume Light/Direct"}, + {"volume_indirect": "Volume Indirect (Cycles Only)"}, {"emission": "Emission"}, {"environment": "Environment"}, - {"shadow": "Shadow"}, + {"shadow": "Shadow/Shadow Catcher"}, {"ao": "Ambient Occlusion"}, - {"denoising": "Denoising"}, - {"volume_direct": "Direct Volumetric Scattering"}, - {"volume_indirect": "Indirect Volumetric Scattering"} + {"bloom": "Bloom (Eevee Only)"}, + {"transparent": "Transparent (Eevee Only)"}, + {"cryptomatte_object": "Cryptomatte Object"}, + {"cryptomatte_material": "Cryptomatte Material"}, + {"cryptomatte_asset": "Cryptomatte Asset"}, + {"cryptomatte_accurate": "Cryptomatte Accurate Mode (Eevee Only)"} ] }, { From d387dca0272c9be2a80beb52ac8272cabb10f426 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 26 Jan 2024 10:44:13 +0000 Subject: [PATCH 20/91] Use duration from streams as its more precise --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb2614829..37f7ea77371 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,7 @@ def _create_frame_from_video(self, video_file_path, output_dir): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["format"]["duration"]) + duration = float(video_data["streams"][0]["duration"]) cmd_args = [ "-y", From e6818f94f5fead64b6ff8767462064fef8e10a41 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 09:17:07 +0000 Subject: [PATCH 21/91] Illicit suggestion --- openpype/plugins/publish/extract_thumbnail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 37f7ea77371..8a1e9fd12d0 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,11 @@ def _create_frame_from_video(self, video_file_path, output_dir): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["streams"][0]["duration"]) + duration = max( + float(stream.get("duration", 0)) + for stream in video_data["streams"] + if stream.get("codec_type") == "video" + ) cmd_args = [ "-y", From 3f8830e27d9c7f4002985ebcf05f7fe395fc3e17 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:18 +0000 Subject: [PATCH 22/91] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 8a1e9fd12d0..2e272b061b2 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,6 +445,10 @@ def _create_frame_from_video(self, video_file_path, output_dir): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) + # Use duration of the individual streams since it is returned with + # higher decimal precision than 'format.duration'. We need this + # more precise value for calculating the correct amount of frames + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 3807172faed3addc22790c6831065d7424e08035 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:40 +0000 Subject: [PATCH 23/91] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2e272b061b2..cbeada6bac4 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -448,7 +448,7 @@ def _create_frame_from_video(self, video_file_path, output_dir): # Use duration of the individual streams since it is returned with # higher decimal precision than 'format.duration'. We need this # more precise value for calculating the correct amount of frames - # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 44282f86df721a271a291bc005abd07f896acb49 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 1 Feb 2024 10:42:34 +0000 Subject: [PATCH 24/91] Fixed deadline validator to check compositor tree output --- .../publish/validate_deadline_publish.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index bb243f08cc0..f7860dd75d7 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -28,15 +28,27 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, def process(self, instance): if not self.is_active(instance.data): return + + tree = bpy.context.scene.node_tree + output_type = "CompositorNodeOutputFile" + output_node = None + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. + for node in tree.nodes: + if node.bl_idname == output_type and "AYON" in node.name: + output_node = node + break + if not output_node: + raise PublishValidationError( + "No output node found in the compositor tree." + ) filepath = bpy.data.filepath file = os.path.basename(filepath) filename, ext = os.path.splitext(file) - if filename not in bpy.context.scene.render.filepath: + if filename not in output_node.base_path: raise PublishValidationError( - "Render output folder " - "doesn't match the blender scene name! " - "Use Repair action to " - "fix the folder file path." + "Render output folder doesn't match the blender scene name! " + "Use Repair action to fix the folder file path." ) @classmethod From 27f869e204662ba9a0dd78400269499145a60ec1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 11:49:25 +0000 Subject: [PATCH 25/91] Fix error report --- openpype/hosts/nuke/plugins/publish/validate_write_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index f490b580d6f..648025adadc 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -129,7 +129,7 @@ def process(self, instance): and key != "file" and key != "tile_color" ): - check.append([key, node_value, write_node[key].value()]) + check.append([key, fixed_values, write_node[key].value()]) if check: self._make_error(check) @@ -137,7 +137,7 @@ def process(self, instance): def _make_error(self, check): # sourcery skip: merge-assign-and-aug-assign, move-assign-in-block dbg_msg = "Write node's knobs values are not correct!\n" - msg_add = "Knob '{0}' > Correct: `{1}` > Wrong: `{2}`" + msg_add = "Knob '{0}' > Expected: `{1}` > Current: `{2}`" details = [ msg_add.format(item[0], item[1], item[2]) From b26ea7a62039ba766085377bf51b8b521f8014a9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 12:18:46 +0000 Subject: [PATCH 26/91] Fix getting node and node name. --- openpype/hosts/nuke/plugins/load/load_camera_abc.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 898c5e4e7b8..7f0452d6d4a 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -112,8 +112,6 @@ def update(self, container, representation): project_name = get_current_project_name() version_doc = get_version_by_id(project_name, representation["parent"]) - object_name = container["node"] - # get main variables version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -139,7 +137,7 @@ def update(self, container, representation): file = get_representation_path(representation).replace("\\", "/") with maintained_selection(): - camera_node = nuke.toNode(object_name) + camera_node = container["node"] camera_node['selected'].setValue(True) # collect input output dependencies @@ -154,9 +152,10 @@ def update(self, container, representation): xpos = camera_node.xpos() ypos = camera_node.ypos() nuke.nodeCopy("%clipboard%") + camera_name = camera_node.name() nuke.delete(camera_node) nuke.nodePaste("%clipboard%") - camera_node = nuke.toNode(object_name) + camera_node = nuke.toNode(camera_name) camera_node.setXYpos(xpos, ypos) # link to original input nodes From d64eb3ccc2eba2a622631bac8041df66722648a1 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:26:38 +0000 Subject: [PATCH 27/91] 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 d0ae854dc8b..ce4afd2e8b7 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,9 +60,8 @@ def context_setting(): 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 28/91] 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 a40788ab41e..54f9f8d8eb0 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 29/91] 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 b80ead4b74a..3a4e76b3cf7 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 30/91] 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 54f9f8d8eb0..fa571be835c 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 be70b52285c1d0c1d4542c20b0d91de7b889c174 Mon Sep 17 00:00:00 2001 From: Sponge96 Date: Fri, 2 Feb 2024 09:26:30 +0000 Subject: [PATCH 31/91] 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 3a4e76b3cf7..5e79901cdd6 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 f5c38eb8d794fc6f3f3517322a796d18d8036d82 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 5 Feb 2024 10:25:01 +0000 Subject: [PATCH 32/91] Thumbnail subset filtering --- openpype/plugins/publish/extract_thumbnail.py | 30 +++++++++++++++++-- .../defaults/project_settings/global.json | 1 + .../schemas/schema_global_publish.json | 6 ++++ .../core/server/settings/publish_plugins.py | 5 ++++ server_addon/core/server/version.py | 2 +- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb2614829..6008b8323d6 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -2,6 +2,7 @@ import os import subprocess import tempfile +import re import pyblish.api from openpype.lib import ( @@ -14,9 +15,10 @@ path_to_subprocess_arg, run_subprocess, ) -from openpype.lib.transcoding import convert_colorspace - -from openpype.lib.transcoding import VIDEO_EXTENSIONS +from openpype.lib.transcoding import ( + convert_colorspace, + VIDEO_EXTENSIONS, +) class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -49,6 +51,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # attribute presets from settings oiiotool_defaults = None ffmpeg_args = None + subsets = [] + product_names = [] def process(self, instance): # run main process @@ -103,6 +107,26 @@ def _main_process(self, instance): self.log.debug("Skipping crypto passes.") return + # We only want to process the subsets needed from settings. + def validate_string_against_patterns(input_str, patterns): + for pattern in patterns: + if re.match(pattern, input_str): + return True + return False + + product_names = self.subsets + self.product_names + if product_names: + result = validate_string_against_patterns( + instance.data["subset"], product_names + ) + if not result: + self.log.debug( + "Subset \"{}\" did not match any valid subsets: {}".format( + instance.data["subset"], product_names + ) + ) + return + # first check for any explicitly marked representations for thumbnail explicit_repres = self._get_explicit_repres_for_thumbnail(instance) if explicit_repres: diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index bb7e3266bdb..782fff1052f 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,7 @@ }, "ExtractThumbnail": { "enabled": true, + "subsets": [], "integrate_thumbnail": false, "background_color": [ 0, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 64f292a1408..226a190dd41 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -202,6 +202,12 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "list", + "object_type": "text", + "key": "subsets", + "label": "Subsets" + }, { "type": "boolean", "key": "integrate_thumbnail", diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 7aa86aafa6f..8506801e7e6 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -176,6 +176,10 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True enabled: bool = SettingsField(True) + product_names: list[str] = SettingsField( + default_factory=list, + title="Product names" + ) integrate_thumbnail: bool = SettingsField( True, title="Integrate Thumbnail Representation" @@ -844,6 +848,7 @@ class PublishPuginsModel(BaseSettingsModel): }, "ExtractThumbnail": { "enabled": True, + "product_names": [], "integrate_thumbnail": True, "target_size": { "type": "source" diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index bbab0242f6a..1276d0254ff 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 78a3ec2118c9963e31ea0b4ca2907d0b84a14b39 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 5 Feb 2024 12:06:19 +0000 Subject: [PATCH 33/91] Fixed issue with double entries in the json manifest --- openpype/hosts/blender/api/render_lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index fc47f5a6590..c2792103e59 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -244,7 +244,9 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # and link it for rpass in passes: if rpass.name == "Image": - pass_name = "rgba" if multi_exr else "beauty" + if not multi_exr: + continue + pass_name = "rgba" else: pass_name = rpass.name filename = f"{name}{aov_sep}{pass_name}.####" @@ -263,6 +265,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "composite" filename = f"{name}{aov_sep}{pass_name}.####" comp_socket = slots.new(pass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) aov_file_products.append(("Composite", filepath)) for link in tree.links: From f83d0f974958ffef089e4fb362cf6f13514fe104 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Feb 2024 14:25:12 +0800 Subject: [PATCH 34/91] 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 d0ae854dc8b..18e287266a0 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -59,6 +59,8 @@ def context_setting(): 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 f2429680ff5aa68b358ececeba02d24e9d86ad0c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 6 Feb 2024 15:43:18 +0000 Subject: [PATCH 35/91] Fix old links and duplicated entries in the manifest --- openpype/hosts/blender/api/render_lib.py | 116 ++++++++++++----------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index c2792103e59..17b9d926ecf 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -178,6 +178,14 @@ def set_render_passes(settings, renderer): return aov_list, custom_passes +def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): + filename = f"{name}{aov_sep}{rpass_name}.####" + slot = slots.new(rpass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) + + return slot, filepath + + def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -188,40 +196,34 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): output_type = "CompositorNodeOutputFile" compositor_type = "CompositorNodeComposite" - # Get the Render Layer and Composite nodes - rl_node = None - comp_node = None + # Get the Render Layer, Composite and the previous output nodes + render_layer_node = None + composite_node = None + old_output_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: - rl_node = node + render_layer_node = node elif node.bl_idname == compositor_type: - comp_node = node - if rl_node and comp_node: + composite_node = node + elif node.bl_idname == output_type and "AYON" in node.name: + old_output_node = node + if render_layer_node and composite_node and old_output_node: break # If there's not a Render Layers node, we create it - if not rl_node: - rl_node = tree.nodes.new(comp_layer_type) + if not render_layer_node: + render_layer_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Alpha", "Noisy Image"] + exclude_sockets = ["Image", "Alpha", "Noisy Image"] passes = [ socket - for socket in rl_node.outputs + for socket in render_layer_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - old_output = None - - # Remove all output nodes that inlcude "AYON" in the name. - # There should be only one. - for node in tree.nodes: - if node.bl_idname == output_type and "AYON" in node.name: - old_output = node - break - # Create a new output node output = tree.nodes.new(output_type) @@ -240,46 +242,54 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): aov_file_products = [] - # For each active render pass, we add a new socket to the output node - # and link it - for rpass in passes: - if rpass.name == "Image": - if not multi_exr: - continue - pass_name = "rgba" - else: - pass_name = rpass.name - filename = f"{name}{aov_sep}{pass_name}.####" + old_links = { + link.from_socket.name: link for link in tree.links + if link.to_node == old_output_node} - slots.new(pass_name if multi_exr else filename) - - filepath = str(output_path / filename.lstrip("/")) - - aov_file_products.append((rpass.name, filepath)) - - if not old_output: - node_input = output.inputs[-1] - tree.links.new(rpass, node_input) + # Create a new socket for the beauty output + pass_name = "rgba" if multi_exr else "beauty" + slot, _ = _create_aov_slot( + 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" - filename = f"{name}{aov_sep}{pass_name}.####" - comp_socket = slots.new(pass_name if multi_exr else filename) - filepath = str(output_path / filename.lstrip("/")) + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) aov_file_products.append(("Composite", filepath)) - for link in tree.links: - if link.to_node == old_output: - socket_name = link.to_socket.name - new_socket = output.inputs.get(socket_name) - if new_socket: - tree.links.new(link.from_socket, new_socket) - elif comp_node and link.to_node == comp_node: - tree.links.new(link.from_socket, comp_socket) - - if old_output: - output.location = old_output.location - tree.nodes.remove(old_output) + # For each active render pass, we add a new socket to the output node + # and link it + for rpass in passes: + slot, filepath = _create_aov_slot( + name, aov_sep, slots, rpass.name, multi_exr, output_path) + aov_file_products.append((rpass.name, filepath)) + + # If the rpass was not connected with the old output node, we connect + # it with the new one. + if not old_links.get(rpass.name): + tree.links.new(rpass, slot) + + for link in list(old_links.values()): + # Check if the socket is still available in the new output node. + socket = output.inputs.get(link.to_socket.name) + # If it is, we connect it with the new output node. + if socket: + tree.links.new(link.from_socket, socket) + # Then, we remove the old link. + tree.links.remove(link) + + # If there's a composite node, we connect its input with the new output + if composite_node: + for link in tree.links: + if link.to_node == composite_node: + tree.links.new(link.from_socket, comp_socket) + break + + if old_output_node: + output.location = old_output_node.location + tree.nodes.remove(old_output_node) + output.name = "AYON File Output" output.label = "AYON File Output" From 1c9c125dc2dcb6662c69cff95f881263b49711c5 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:00:33 +1300 Subject: [PATCH 36/91] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 +++-- openpype/modules/timers_manager/widget_user_idle.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 674d834a1dd..e684737d5e8 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,6 +162,7 @@ def tray_init(self): def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() + self.show_message() def tray_exit(self): if self._idle_manager: @@ -373,8 +374,8 @@ def connect_with_modules(self, enabled_modules): ).format(module.name)) def show_message(self): - if self.is_running is False: - return + # if self.is_running is False: + # return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() diff --git a/openpype/modules/timers_manager/widget_user_idle.py b/openpype/modules/timers_manager/widget_user_idle.py index 9df328e6b25..8cc78cf1023 100644 --- a/openpype/modules/timers_manager/widget_user_idle.py +++ b/openpype/modules/timers_manager/widget_user_idle.py @@ -17,6 +17,7 @@ def __init__(self, module): self.setWindowFlags( QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowStaysOnTopHint ) self._is_showed = False From 1d3c1c7c6c0bb5a0a4bd3c62e91c7a58e1a72c86 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:06:28 +1300 Subject: [PATCH 37/91] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index e684737d5e8..674d834a1dd 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,7 +162,6 @@ def tray_init(self): def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() - self.show_message() def tray_exit(self): if self._idle_manager: @@ -374,8 +373,8 @@ def connect_with_modules(self, enabled_modules): ).format(module.name)) def show_message(self): - # if self.is_running is False: - # return + if self.is_running is False: + return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() From 14fc4ce6be35f02096ecc56cc865300ef67a705c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:51:52 +0800 Subject: [PATCH 38/91] Implementation of workfile creator --- .../max/plugins/create/create_workfile.py | 130 ++++++++++++++++++ .../max/plugins/publish/collect_members.py | 5 +- .../max/plugins/publish/collect_workfile.py | 14 +- 3 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_workfile.py diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py new file mode 100644 index 00000000000..b43a3531360 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating workfiles.""" +from openpype import AYON_SERVER_ENABLED +from openpype.pipeline import CreatedInstance, AutoCreator, CreatorError +from openpype.client import get_asset_by_name, get_asset_name_identifier +from openpype.hosts.max.api import plugin +from openpype.hosts.max.api.lib import read, imprint +from pymxs import runtime as rt + + +class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): + """Workfile auto-creator.""" + identifier = "io.openpype.creators.max.workfile" + label = "Workfile" + family = "workfile" + icon = "fa5.file" + + default_variant = "Main" + + def create(self): + variant = self.default_variant + current_instance = next( + ( + instance for instance in self.create_context.instances + if instance.creator_identifier == self.identifier + ), None) + project_name = self.project_name + asset_name = self.create_context.get_current_asset_name() + task_name = self.create_context.get_current_task_name() + host_name = self.create_context.host_name + + if current_instance is None: + current_instance_asset = None + elif AYON_SERVER_ENABLED: + current_instance_asset = current_instance["folderPath"] + else: + current_instance_asset = current_instance["asset"] + + if current_instance is None: + asset_doc = get_asset_by_name(project_name, asset_name) + data = { + "task": task_name, + "variant": variant + } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name + + data.update( + self.get_dynamic_data( + variant, task_name, asset_doc, + project_name, host_name, current_instance) + ) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + self.log.info("Auto-creating workfile instance...") + instance_node = self.create_node(subset_name) + data["instance_node"] = instance_node.name + current_instance = CreatedInstance( + self.family, subset_name, data, self + ) + self._add_instance_to_context(current_instance) + imprint(instance_node.name, current_instance.data) + elif ( + current_instance_asset != asset_name + or current_instance["task"] != task_name + ): + # Update instance context if is not the same + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + asset_name = get_asset_name_identifier(asset_doc) + + if AYON_SERVER_ENABLED: + current_instance["folderPath"] = asset_name + else: + current_instance["asset"] = asset_name + current_instance["task"] = task_name + current_instance["subset"] = subset_name + + def collect_instances(self): + self.cache_subsets(self.collection_shared_data) + for instance in self.collection_shared_data["max_cached_subsets"].get(self.identifier, []): # noqa + if not rt.getNodeByName(instance): + continue + created_instance = CreatedInstance.from_existing( + read(rt.GetNodeByName(instance)), self + ) + self._add_instance_to_context(created_instance) + + + def update_instances(self, update_list): + for created_inst, changes in update_list: + instance_node = created_inst.get("instance_node") + new_values = { + key: changes[key].new_value + for key in changes.changed_keys + } + + imprint( + instance_node, + created_inst.data_to_store() + ) + + def remove_instances(self, instances): + """Remove specified instance from the scene. + + This is only removing `id` parameter so instance is no longer + instance, because it might contain valuable data for artist. + + """ + for instance in instances: + instance_node = rt.GetNodeByName( + instance.data.get("instance_node")) + if instance_node: + rt.Delete(instance_node) + + self._remove_instance_from_context(instance) + + + def create_node(self, subset_name): + if rt.getNodeByName(subset_name): + node = rt.getNodeByName(subset_name) + return node + node = rt.Container(name=subset_name) + node.isHidden = True + return node diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 2970cf0e247..7cd646e0e72 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -12,7 +12,10 @@ class CollectMembers(pyblish.api.InstancePlugin): hosts = ['max'] def process(self, instance): - + if instance.data["family"] == "workfile": + self.log.debug("Skipping Actions for workfile family.") + self.log.debug("{}".format(instance.data["subset"])) + return if instance.data.get("instance_node"): container = rt.GetNodeByName(instance.data["instance_node"]) instance.data["members"] = [ diff --git a/openpype/hosts/max/plugins/publish/collect_workfile.py b/openpype/hosts/max/plugins/publish/collect_workfile.py index 0eb4bb731e1..446175c0ed0 100644 --- a/openpype/hosts/max/plugins/publish/collect_workfile.py +++ b/openpype/hosts/max/plugins/publish/collect_workfile.py @@ -6,15 +6,16 @@ from pymxs import runtime as rt -class CollectWorkfile(pyblish.api.ContextPlugin): +class CollectWorkfile(pyblish.api.InstancePlugin): """Inject the current working file into context""" order = pyblish.api.CollectorOrder - 0.01 label = "Collect 3dsmax Workfile" hosts = ['max'] - def process(self, context): + def process(self, instance): """Inject the current working file.""" + context = instance.context folder = rt.maxFilePath file = rt.maxFileName if not folder or not file: @@ -23,15 +24,12 @@ def process(self, context): context.data['currentFile'] = current_file - filename, ext = os.path.splitext(file) - - task = context.data["task"] + ext = os.path.splitext(file)[-1].lstrip(".") data = {} # create instance - instance = context.create_instance(name=filename) - subset = 'workfile' + task.capitalize() + subset = instance.data["subset"] data.update({ "subset": subset, @@ -55,7 +53,7 @@ def process(self, context): }] instance.data.update(data) - + self.log.info('Collected data: {}'.format(data)) self.log.info('Collected instance: {}'.format(file)) self.log.info('Scene path: {}'.format(current_file)) self.log.info('staging Dir: {}'.format(folder)) From 8bac54fcf0745645489a29fc2e78bac1d9d4c284 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:54:39 +0800 Subject: [PATCH 39/91] move back the subset name before dynamic data --- openpype/hosts/max/plugins/create/create_workfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index b43a3531360..70944b3e2cb 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -38,6 +38,9 @@ def create(self): if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) data = { "task": task_name, "variant": variant @@ -52,9 +55,6 @@ def create(self): variant, task_name, asset_doc, project_name, host_name, current_instance) ) - subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name - ) self.log.info("Auto-creating workfile instance...") instance_node = self.create_node(subset_name) data["instance_node"] = instance_node.name From af022f3561ac96b3d891b52238219ddd913d5f5c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:59:20 +0800 Subject: [PATCH 40/91] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 70944b3e2cb..25213871d8c 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating workfiles.""" from openpype import AYON_SERVER_ENABLED -from openpype.pipeline import CreatedInstance, AutoCreator, CreatorError +from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name, get_asset_name_identifier from openpype.hosts.max.api import plugin from openpype.hosts.max.api.lib import read, imprint @@ -91,15 +91,9 @@ def collect_instances(self): ) self._add_instance_to_context(created_instance) - def update_instances(self, update_list): for created_inst, changes in update_list: instance_node = created_inst.get("instance_node") - new_values = { - key: changes[key].new_value - for key in changes.changed_keys - } - imprint( instance_node, created_inst.data_to_store() @@ -120,7 +114,6 @@ def remove_instances(self, instances): self._remove_instance_from_context(instance) - def create_node(self, subset_name): if rt.getNodeByName(subset_name): node = rt.getNodeByName(subset_name) From 2248240306fdf01dc2f756982b11304c48304cc5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 18:00:08 +0800 Subject: [PATCH 41/91] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 25213871d8c..4ec3c6d3ad1 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -92,7 +92,7 @@ def collect_instances(self): self._add_instance_to_context(created_instance) def update_instances(self, update_list): - for created_inst, changes in update_list: + for created_inst, _ in update_list: instance_node = created_inst.get("instance_node") imprint( instance_node, From bd8135f4dcea13240c8a894b9f70472b7a535851 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 18:01:15 +0800 Subject: [PATCH 42/91] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 4ec3c6d3ad1..30692ccd064 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -93,7 +93,7 @@ def collect_instances(self): def update_instances(self, update_list): for created_inst, _ in update_list: - instance_node = created_inst.get("instance_node") + instance_node = created_inst.get("instance_node") imprint( instance_node, created_inst.data_to_store() From 3c47798d121215505c7b0349482f7947f1b79dbe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 20:07:16 +0800 Subject: [PATCH 43/91] edit the action.py too regarding to #6164 --- openpype/hosts/max/api/action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652bb..c3c1957af10 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,7 +31,10 @@ def process(self, context, plugin): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + if not invalid_names: + invalid_names = [obj.name for obj, _ in invalid] + invalid = [obj for obj, _ in invalid] self.log.info( "Selecting invalid objects: %s", ", ".join(invalid_names) ) From 1fcdde0a9cc3a8b31697540ce320286d83d30e99 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 8 Feb 2024 13:46:56 +0100 Subject: [PATCH 44/91] AfterEffects: added toggle for applying values from DB during creation (#6204) * OP-8130 - After Effects added flag to force values from Asset to composition during creation This allows controlling setting of values (resolution, duration) from Asset (DB) to the created instance. Default is to set it automatically. * OP-8130 - Ayon version of Settings for AE creator --- openpype/hosts/aftereffects/plugins/create/create_render.py | 6 +++++- .../settings/defaults/project_settings/aftereffects.json | 3 ++- .../projects_schema/schema_project_aftereffects.json | 6 ++++++ .../aftereffects/server/settings/creator_plugins.py | 2 ++ server_addon/aftereffects/server/version.py | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index fadfc0c2061..b4fb20f922c 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -29,6 +29,7 @@ class RenderCreator(Creator): # Settings mark_for_review = True + force_setting_values = True def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up @@ -96,7 +97,9 @@ def create(self, subset_name_from_ui, data, pre_create_data): self._add_instance_to_context(new_instance) stub.rename_item(comp.id, subset_name) - set_settings(True, True, [comp.id], print_msg=False) + + if self.force_setting_values: + set_settings(True, True, [comp.id], print_msg=False) def get_pre_create_attr_defs(self): output = [ @@ -173,6 +176,7 @@ def apply_settings(self, project_settings): ) self.mark_for_review = plugin_settings["mark_for_review"] + self.force_setting_values = plugin_settings["force_setting_values"] self.default_variants = plugin_settings.get( "default_variants", plugin_settings.get("defaults") or [] diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 77ccb744102..9e2ab7334b4 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -15,7 +15,8 @@ "default_variants": [ "Main" ], - "mark_for_review": true + "mark_for_review": true, + "force_setting_values": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 72f09a641de..b0f8a7357f9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -42,6 +42,12 @@ "key": "mark_for_review", "label": "Review", "default": true + }, + { + "type": "boolean", + "key": "force_setting_values", + "label": "Force resolution and duration values from Asset", + "default": true } ] } diff --git a/server_addon/aftereffects/server/settings/creator_plugins.py b/server_addon/aftereffects/server/settings/creator_plugins.py index 988a0365897..5d4ba30cd0a 100644 --- a/server_addon/aftereffects/server/settings/creator_plugins.py +++ b/server_addon/aftereffects/server/settings/creator_plugins.py @@ -7,6 +7,8 @@ class CreateRenderPlugin(BaseSettingsModel): default_factory=list, title="Default Variants" ) + force_setting_values: bool = SettingsField( + True, title="Force resolution and duration values from Asset") class AfterEffectsCreatorPlugins(BaseSettingsModel): diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py index df0c92f1e27..e57ad007184 100644 --- a/server_addon/aftereffects/server/version.py +++ b/server_addon/aftereffects/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.2" +__version__ = "0.1.3" From b70b418dee0a08b320eed557490edc237a48a0ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Feb 2024 14:34:41 +0100 Subject: [PATCH 45/91] update ayon unreal plugin --- openpype/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration index 63266607ceb..a4755d28696 160000 --- a/openpype/hosts/unreal/integration +++ b/openpype/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757 +Subproject commit a4755d2869694fcf58c98119298cde8d204e2ce4 From d502a26bfe55b8062dc383a00b61c8f21b490c99 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:47:37 +0100 Subject: [PATCH 46/91] Update CONTRIBUTING.md --- CONTRIBUTING.md | 55 +++++++------------------------------------------ 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 644a74c1f7c..2898c13acd8 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 47/91] 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 2898c13acd8..5f3fb90dfaa 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 48/91] 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 5f3fb90dfaa..27294b19beb 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 49/91] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a79b9f25822..5b8d3692dc0 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 50/91] 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 c8301b81fda..fe89a790961 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 51/91] 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 ce4afd2e8b7..1b74b8131c6 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,7 +60,7 @@ def context_setting(): 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 52/91] 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 10eb2614829..291345abb17 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 53/91] 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 fe5644f0c91..f592fc4a444 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 @@ def process(self, instance): 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 54/91] [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 d105b0169e1..39fb10bb6e5 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 55/91] 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 f751a541162..7cf51713e4a 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 56/91] 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 30d95128a23..24762a4232c 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.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -38,7 +39,7 @@ def process(self, instance): 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 57/91] 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 24762a4232c..2bb5036f8ba 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 @@ def process(self, instance): 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 58/91] [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 39fb10bb6e5..9e1bd39b3a5 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 59/91] 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 7cf51713e4a..bc0e00f7404 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 60/91] 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 17b9d926ecf..bb35dd44ea7 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 61/91] 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 bb35dd44ea7..c09cfce5027 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 48f3ef8ef0e..03a5400ced3 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 bbed881ab0b..13e460b74cf 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 a1f6d3114a8..f992ea6fcc8 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 @@ class RenderSettingsModel(BaseSettingsModel): "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", + "compositing": True, "aov_list": ["combined"], "custom_passes": [] } From fa404cb9404a4b0642dec6f1f3325f7e7cad0e8f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 15 Feb 2024 17:14:24 +0000 Subject: [PATCH 62/91] Fix settings colorspace from representation data. --- openpype/hosts/nuke/api/utils.py | 33 ++++-- openpype/hosts/nuke/plugins/load/load_clip.py | 112 +++++++++++++----- 2 files changed, 103 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index a7df1dee71d..41857bd4ca8 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -103,33 +103,42 @@ def colorspace_exists_on_node(node, colorspace_name): except ValueError: # knob is not available on input node return False - all_clrs = get_colorspace_list(colorspace_knob) - return colorspace_name in all_clrs + return colorspace_name in get_colorspace_list(colorspace_knob) def get_colorspace_list(colorspace_knob): """Get available colorspace profile names + Because the values returned from colorspace_knob.values() do not correspond + to the value returned from colorspace_knob.value(), and the extracted + colorspace comes from using colorspace_knob.value(), we need to iterate + through all values to get the correct value. + + A code example of the above would be: + + for count, value in enumerate(colorspace_knob.values()): + colorspace_knob.setValue(count) + print(colorspace_knob.value() in colorspace_knob.values()) + Args: colorspace_knob (nuke.Knob): nuke knob object Returns: list: list of strings names of profiles """ + original_value = colorspace_knob.value() - all_clrs = list(colorspace_knob.values()) - reduced_clrs = [] + colorspaces = [] - if not colorspace_knob.getFlag(nuke.STRIP_CASCADE_PREFIX): - return all_clrs - - # strip colorspace with nested path - for clrs in all_clrs: - clrs = clrs.split('/')[-1] - reduced_clrs.append(clrs) + try: + for count, value in enumerate(colorspace_knob.values()): + colorspace_knob.setValue(count) + colorspaces.append(colorspace_knob.value()) + finally: + colorspace_knob.setValue(original_value) - return reduced_clrs + return colorspaces def is_headless(): diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 3a2ec3dbee5..2c40d9d6577 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -11,6 +11,9 @@ get_current_project_name, get_representation_path, ) +from openpype.pipeline.colorspace import ( + get_imageio_file_rules_colorspace_from_filepath +) from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace, maintained_selection @@ -101,7 +104,6 @@ def load(self, context, name, namespace, options): filepath = self.filepath_from_context(context) filepath = filepath.replace("\\", "/") - self.log.debug("_ filepath: {}".format(filepath)) start_at_workfile = options.get( "start_at_workfile", self.options_defaults["start_at_workfile"]) @@ -140,6 +142,7 @@ def load(self, context, name, namespace, options): "Representation id `{}` is failing to load".format(repre_id)) return + print(representation["data"]["colorspaceData"]) read_name = self._get_node_name(representation) # Create the Loader with the filename path set @@ -154,8 +157,8 @@ def load(self, context, name, namespace, options): with viewer_update_and_undo_stop(): read_node["file"].setValue(filepath) - used_colorspace = self._set_colorspace( - read_node, version_data, representation["data"], filepath) + self.set_colorspace_to_node( + read_node, filepath, version, representation) self._set_range_to_node(read_node, first, last, start_at_workfile) @@ -180,8 +183,6 @@ def load(self, context, name, namespace, options): colorspace = representation["data"].get(key) colorspace = colorspace or version_data.get(key) data_imprint["db_colorspace"] = colorspace - if used_colorspace: - data_imprint["used_colorspace"] = used_colorspace else: value_ = context["version"]['data'].get( key, str(None)) @@ -302,8 +303,8 @@ def update(self, container, representation): # to avoid multiple undo steps for rest of process # we will switch off undo-ing with viewer_update_and_undo_stop(): - used_colorspace = self._set_colorspace( - read_node, version_data, representation["data"], filepath) + self.set_colorspace_to_node( + read_node, filepath, version_doc, representation) self._set_range_to_node(read_node, first, last, start_at_workfile) @@ -320,10 +321,6 @@ def update(self, container, representation): "author": version_data.get("author") } - # add used colorspace if found any - if used_colorspace: - updated_dict["used_colorspace"] = used_colorspace - last_version_doc = get_last_version_by_subset_id( project_name, version_doc["parent"], fields=["_id"] ) @@ -350,6 +347,36 @@ def update(self, container, representation): self.set_as_member(read_node) + def set_colorspace_to_node( + self, + read_node, + filepath, + version_doc, + representation_doc, + ): + """Set colorspace to read node. + + Sets colorspace with available names validation. + + Args: + read_node (nuke.Node): The nuke's read node + filepath (str): file path + version_doc (dict): version document + representation_doc (dict): representation document + + """ + used_colorspace = self._get_colorspace_data( + version_doc, representation_doc, filepath) + + if ( + used_colorspace + and colorspace_exists_on_node(read_node, used_colorspace) + ): + self.log.info(f"Used colorspace: {used_colorspace}") + read_node["colorspace"].setValue(used_colorspace) + else: + self.log.info("Colorspace not set...") + def remove(self, container): read_node = container["node"] assert read_node.Class() == "Read", "Must be Read" @@ -450,25 +477,50 @@ def _get_node_name(self, representation): return self.node_name_template.format(**name_data) - def _set_colorspace(self, node, version_data, repre_data, path): - output_color = None - path = path.replace("\\", "/") - # get colorspace - colorspace = repre_data.get("colorspace") - colorspace = colorspace or version_data.get("colorspace") + def _get_colorspace_data(self, version_doc, representation_doc, filepath): + """Get colorspace data from version and representation documents - # colorspace from `project_settings/nuke/imageio/regexInputs` - iio_colorspace = get_imageio_input_colorspace(path) + Args: + version_doc (dict): version document + representation_doc (dict): representation document + filepath (str): file path - # Set colorspace defined in version data - if ( - colorspace is not None - and colorspace_exists_on_node(node, str(colorspace)) - ): - node["colorspace"].setValue(str(colorspace)) - output_color = str(colorspace) - elif iio_colorspace is not None: - node["colorspace"].setValue(iio_colorspace) - output_color = iio_colorspace + Returns: + Any[str,None]: colorspace name or None + """ + # Get backward compatible colorspace key. + representation_data = representation_doc["data"] + colorspace = representation_doc["data"].get("colorspace") + self.log.debug( + f"Colorspace from representation colorspace: {colorspace}" + ) + + # Get backward compatible version data key if colorspace is not found. + colorspace = colorspace or version_doc["data"].get("colorspace") + self.log.debug(f"Colorspace from version colorspace: {colorspace}") + + # Get colorspace from representation colorspaceData if colorspace is + # not found. + colorspace_data = representation_doc["data"].get("colorspaceData", {}) + colorspace = colorspace or colorspace_data.get("colorspace") + self.log.debug( + f"Colorspace from representation colorspaceData: {colorspace}" + ) + + print(f"Colorspace found: {colorspace}") + + # check if any filerules are not applicable + new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa + filepath, "nuke", get_current_project_name() + ) + self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}") + + # colorspace from `project_settings/nuke/imageio/regexInputs` + old_parsed_colorspace = get_imageio_input_colorspace(filepath) + self.log.debug(f"Colorspace old filerules: {old_parsed_colorspace}") - return output_color + return ( + new_parsed_colorspace + or old_parsed_colorspace + or colorspace + ) From f285c1f9f00c7f820356729ef5516bcaa70370f3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 15 Feb 2024 17:21:18 +0000 Subject: [PATCH 63/91] Hound --- openpype/hosts/nuke/api/utils.py | 2 +- openpype/hosts/nuke/plugins/load/load_clip.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index 41857bd4ca8..62552a24083 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -132,7 +132,7 @@ def get_colorspace_list(colorspace_knob): colorspaces = [] try: - for count, value in enumerate(colorspace_knob.values()): + for count, _ in enumerate(colorspace_knob.values()): colorspace_knob.setValue(count) colorspaces.append(colorspace_knob.value()) finally: diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 2c40d9d6577..0cdb7fc5969 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -489,7 +489,6 @@ def _get_colorspace_data(self, version_doc, representation_doc, filepath): Any[str,None]: colorspace name or None """ # Get backward compatible colorspace key. - representation_data = representation_doc["data"] colorspace = representation_doc["data"].get("colorspace") self.log.debug( f"Colorspace from representation colorspace: {colorspace}" From ea00109d86dd5f40422ee0eabe046ed65d50586a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 12:35:05 +0000 Subject: [PATCH 64/91] 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 b02cfa82079..0c4b282d102 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 42dea33ef9e..bb8e0b5cd48 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 65/91] 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 009375e87ee..d40c371de0e 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 @@ def get_job_info(self): "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 8f9e9a74253..58e69d0aeae 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 @@ def get_job_info(self): "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 9a718aa089a..dcb79588a74 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 @@ def process(self, instance): "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 17e672334cf..73bc10465d1 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 @@ def get_job_info(self): "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 ada69575a8b..bef93b3947c 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 @@ def get_job_info(self): "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 bf7fb45a8b7..6ed9e66ce08 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 @@ def get_job_info(self, dependency_job_ids=None): "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 f06bd4dbe69..e31de0a101d 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 @@ def get_job_info(self): "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 5591db151a2..4cd417b83b2 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 @@ def get_job_info(self): "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 41a2a64ab58..a9fb10de8b3 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 @@ def get_job_info(self): 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 746b009255f..9c2d2128067 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 @@ def payload_submit( 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 1bb45b77ccf..434a823cfe6 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 @@ def _submit_deadline_post_job(self, instance, job): 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 82971daee5f..f622ec9a00a 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 @@ def _submit_deadline_post_job(self, instance, job, instances): 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 66/91] 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 82971daee5f..4e9df976cd2 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 @@ def _submit_deadline_post_job(self, instance, job, instances): return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. @@ -338,151 +337,6 @@ def process(self, instance): 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 67/91] [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 9e1bd39b3a5..64d8075c4a8 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 68/91] 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 bc0e00f7404..4d48212d4a1 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 1d16f25b793908986cbc7002f81c7d4e30fc85b3 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 19 Feb 2024 11:58:13 +0000 Subject: [PATCH 69/91] Update openpype/hosts/nuke/plugins/load/load_clip.py --- openpype/hosts/nuke/plugins/load/load_clip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 0cdb7fc5969..e2bd2180b76 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -142,7 +142,6 @@ def load(self, context, name, namespace, options): "Representation id `{}` is failing to load".format(repre_id)) return - print(representation["data"]["colorspaceData"]) read_name = self._get_node_name(representation) # Create the Loader with the filename path set From 6aa534dbc4af1a8f59381617332c7db16c8dd40c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Feb 2024 16:44:17 +0100 Subject: [PATCH 70/91] 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 5c780a51c42..1876ff20ebc 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 @@ def ftrack_status_change(self, session, entity, project_name): 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 71/91] 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 13e460b74cf..2ffdc6070dc 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 72/91] [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 009150ae7d2..4ec3448570a 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 64d8075c4a8..a3892807757 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 453833aae2b..eef6a2e978a 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 73/91] 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 4d48212d4a1..7fe6c792593 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 7ea09acb3307507576ea248eb996f5431ce71c2c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 Feb 2024 03:24:33 +0000 Subject: [PATCH 74/91] [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 a3892807757..95203e17c9b 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 75/91] 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 7fe6c792593..c01ab5122cf 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 669e0a79550766030d7e573b45c41176bb9d1266 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Feb 2024 13:16:30 +0000 Subject: [PATCH 76/91] 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 d7f646ebc9f..44767e458ac 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 @@ def process(self, instance): 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 9c83733b096..efd80a8876d 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 d80edf902b2..73bd475815a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -312,6 +312,31 @@ "label": "Tags addition" } ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectClipEffects", + "label": "Collect Clip Effects", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "key": "effect_categories", + "label": "Effect Categories", + "object_type": { + "type": "list", + "key": "effects_classes", + "object_type": "text" + } + } + ] } ] }, From 81c657227ef19487c03a91d92bebca1705cbb830 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Feb 2024 17:12:51 +0000 Subject: [PATCH 77/91] 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 dad0310dfcc..d7b9191fa32 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 @@ def process(self, instance): 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 78/91] 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 13d007dd356..41039a5be0d 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,7 @@ def run_action(self, plugin_id, action_id): "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 79/91] 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 41039a5be0d..f9b8bcc5129 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,11 @@ def run_action(self, plugin_id, action_id): "title": "Action failed", "message": "Action failed.", "traceback": "".join( - traceback.format_exception(type(exception), exception, exception.__traceback__) + traceback.format_exception( + type(exception), + exception, + exception.__traceback__ + ) ), "label": action.__name__, "identifier": action.id From 3a3b535001460d4e20cbccc3e05ca265872c4037 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Mar 2024 11:01:59 +0000 Subject: [PATCH 80/91] 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 e31de0a101d..07bbb1cacbb 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 @@ 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 @@ def get_plugin_info_through_camera(self, camera): 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 @@ def _use_published_name_for_multiples(self, data, project_settings): job_info_list (list): A list of multiple job infos plugin_info_list (list): A list of multiple plugin infos """ + from openpype.hosts.max.api.lib import get_multipass_setting + job_info_list = [] plugin_info_list = [] instance = self._instance From 73bc49750eb1f5a71d063082e5a2500c152b303f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:36:47 +0100 Subject: [PATCH 81/91] 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 e9c4921bb54..96caacf8d10 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 82/91] 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 2781ab6b758..36f5a2db7d1 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -408,11 +408,12 @@ def load(self, files): _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 83/91] 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 36f5a2db7d1..fc7900321c4 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -409,7 +409,6 @@ def load(self, files): 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 b976a5d05a13d8636ba79d544bd7ec126eb80e5a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 11:40:53 +0000 Subject: [PATCH 84/91] Dont brute force the colorspace list --- openpype/hosts/nuke/api/utils.py | 41 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index 62552a24083..db9a6fcd055 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -1,4 +1,6 @@ import os +import re + import nuke from openpype import resources @@ -110,35 +112,28 @@ def colorspace_exists_on_node(node, colorspace_name): def get_colorspace_list(colorspace_knob): """Get available colorspace profile names - Because the values returned from colorspace_knob.values() do not correspond - to the value returned from colorspace_knob.value(), and the extracted - colorspace comes from using colorspace_knob.value(), we need to iterate - through all values to get the correct value. - - A code example of the above would be: - - for count, value in enumerate(colorspace_knob.values()): - colorspace_knob.setValue(count) - print(colorspace_knob.value() in colorspace_knob.values()) - Args: colorspace_knob (nuke.Knob): nuke knob object Returns: list: list of strings names of profiles """ - original_value = colorspace_knob.value() - - colorspaces = [] - - try: - for count, _ in enumerate(colorspace_knob.values()): - colorspace_knob.setValue(count) - colorspaces.append(colorspace_knob.value()) - finally: - colorspace_knob.setValue(original_value) - - return colorspaces + results = [] + + # This pattern is to match with roles which uses an indentation and + # parentheses with original colorspace. The value returned from the + # colorspace is the string before the indentation, so we'll need to + # convert the values to match with value returned from the knob, + # ei. knob.value(). + pattern = r".*\t.* \(.*\)" + for colorspace in nuke.getColorspaceList(colorspace_knob): + match = re.search(pattern, colorspace) + if match: + results.append(colorspace.split("\t")[0]) + else: + results.append(colorspace) + + return results def is_headless(): From c4d58ca363676b6c7d8af0a5538bcec074270ccf Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 7 Mar 2024 12:30:54 +0000 Subject: [PATCH 85/91] Update openpype/hosts/nuke/api/utils.py --- openpype/hosts/nuke/api/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index db9a6fcd055..4220f007354 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -129,7 +129,7 @@ def get_colorspace_list(colorspace_knob): for colorspace in nuke.getColorspaceList(colorspace_knob): match = re.search(pattern, colorspace) if match: - results.append(colorspace.split("\t")[0]) + results.append(colorspace.split("\t", 1)[0]) else: results.append(colorspace) From 75d74bf826ae1f418bb7fdfbcf5b5726c97e611c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 19:10:40 +0000 Subject: [PATCH 86/91] Remove representation unnessecary frameStart --- openpype/hosts/nuke/plugins/publish/collect_writes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 6f9245f5b96..2bf43ed75a8 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -194,7 +194,6 @@ def _set_additional_instance_data( "frameEndHandle": last_frame, }) - # TODO temporarily set stagingDir as persistent for backward # compatibility. This is mainly focused on `renders`folders which # were previously not cleaned up (and could be used in read notes) @@ -269,10 +268,6 @@ def _get_existing_frames_representation( "tags": [] } - frame_start_str = self._get_frame_start_str(first_frame, last_frame) - - representation['frameStart'] = frame_start_str - # set slate frame collected_frames = self._add_slate_frame_to_collected_frames( instance, From d99ad33be394a7c30cc705c11b3d8d7f22bcbb1a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 7 Mar 2024 19:10:58 +0000 Subject: [PATCH 87/91] Exclude instances nodes from slate collection. --- openpype/hosts/nuke/plugins/publish/collect_slate_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py index 3baa0cd9b56..ac30bd6051a 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py +++ b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py @@ -17,7 +17,8 @@ def process(self, instance): ( n_ for n_ in nuke.allNodes() if "slate" in n_.name().lower() - if not n_["disable"].getValue() + if not n_["disable"].getValue() and + "publish_instance" not in n_.knobs() # Exclude instance nodes. ), None ) From 30f476782327fe0f8d66482579e1a9896696fff9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 12 Mar 2024 16:26:46 +0100 Subject: [PATCH 88/91] copy and open of published workfile should work --- openpype/tools/ayon_workfiles/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/ayon_workfiles/control.py b/openpype/tools/ayon_workfiles/control.py index 9d19571267e..b932f4eeb2c 100644 --- a/openpype/tools/ayon_workfiles/control.py +++ b/openpype/tools/ayon_workfiles/control.py @@ -573,6 +573,7 @@ def copy_workfile_representation( workdir, filename, template_key, + src_filepath=representation_filepath ) except Exception: failed = True From 69c31961a81ba1559d22307d1214816d0e5dc6ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Mar 2024 12:48:04 +0100 Subject: [PATCH 89/91] Refactor print statements to use format method - Updated print statements to use the format method for string interpolation. This improves readability and consistency in the codebase. --- openpype/scripts/ocio_wrapper.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 0a78e33c1ff..45227c11f8b 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -83,7 +83,7 @@ def get_colorspace(in_path, out_path): with open(json_path, "w") as f_: json.dump(out_data, f_) - print(f"Colorspace data are saved to '{json_path}'") + print("Colorspace data are saved to '{}'".format(json_path)) def _get_colorspace_data(config_path): @@ -102,7 +102,7 @@ def _get_colorspace_data(config_path): if not config_path.is_file(): raise IOError( - f"Input path `{config_path}` should be `config.ocio` file") + "Input path `{}` should be `config.ocio` file".format(config_path)) config = ocio.Config().CreateFromFile(str(config_path)) @@ -118,10 +118,9 @@ def _get_colorspace_data(config_path): for color in config.getColorSpaces() }, "displays_views": { - f"{view} ({display})": { + "{} ({})".format(display, view): { "display": display, "view": view - } for display in config.getDisplays() for view in config.getViews(display) @@ -181,7 +180,7 @@ def get_views(in_path, out_path): with open(json_path, "w") as f_: json.dump(out_data, f_) - print(f"Viewer data are saved to '{json_path}'") + print("Viewer data are saved to '{}'".format(json_path)) def _get_views_data(config_path): @@ -207,11 +206,12 @@ def _get_views_data(config_path): 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 + # Special token. + # See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa if colorspace == "": colorspace = display - data_[f"{display}/{view}"] = { + data_["{}/{}".format(display, view)] = { "display": display, "view": view, "colorspace": colorspace @@ -254,7 +254,7 @@ def get_version(config_path, out_path): with open(json_path, "w") as f_: json.dump(out_data, f_) - print(f"Config version data are saved to '{json_path}'") + print("Config version data are saved to '{}'".format(json_path)) def _get_version_data(config_path): @@ -325,7 +325,7 @@ def get_config_file_rules_colorspace_from_filepath( with open(json_path, "w") as f_: json.dump(colorspace, f_) - print(f"Colorspace name is saved to '{json_path}'") + print("Colorspace name is saved to '{}'".format(json_path)) def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): @@ -345,7 +345,7 @@ def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): if not config_path.is_file(): raise IOError( - f"Input path `{config_path}` should be `config.ocio` file") + "Input path `{}` should be `config.ocio` file".format(config_path)) config = ocio.Config().CreateFromFile(str(config_path)) @@ -427,7 +427,8 @@ def get_display_view_colorspace_name(in_path, out_path, with open(out_path, "w") as f: json.dump(out_data, f) - print(f"Display view colorspace saved to '{out_path}'") + print("Display view colorspace saved to '{}'".format(out_path)) + if __name__ == '__main__': main() From d43458945a3495080200c54bdba8e32156439cc0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Mar 2024 16:37:41 +0100 Subject: [PATCH 90/91] nuke12 is having its own PyOpenColorIO 1.1.1 which is missing some functionalinty --- openpype/pipeline/colorspace.py | 4 +++ openpype/scripts/ocio_wrapper.py | 60 +++++++++++--------------------- 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 9f720f6ae95..2d1837e8ead 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -463,9 +463,13 @@ def compatibility_check(): try: import PyOpenColorIO # noqa: F401 + config = PyOpenColorIO.GetCurrentConfig() + config.getDisplayViewColorSpaceName("sRGB", "sRGB") CachedData.has_compatible_ocio_package = True except ImportError: CachedData.has_compatible_ocio_package = False + except AttributeError: + CachedData.has_compatible_ocio_package = False # compatible return CachedData.has_compatible_ocio_package diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 45227c11f8b..fe3fbb5d920 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -18,10 +18,9 @@ - returning all available viewers found in input config path. """ - +import os import click import json -from pathlib import Path import PyOpenColorIO as ocio @@ -76,14 +75,12 @@ def get_colorspace(in_path, out_path): > pyton.exe ./ocio_wrapper.py config get_colorspace --in_path= --out_path= """ - json_path = Path(out_path) - out_data = _get_colorspace_data(in_path) - with open(json_path, "w") as f_: + with open(out_path, "w") as f_: json.dump(out_data, f_) - print("Colorspace data are saved to '{}'".format(json_path)) + print("Colorspace data are saved to '{}'".format(out_path)) def _get_colorspace_data(config_path): @@ -98,13 +95,11 @@ def _get_colorspace_data(config_path): Returns: dict: aggregated available colorspaces """ - config_path = Path(config_path) - - if not config_path.is_file(): + if not os.path.isfile(config_path): raise IOError( "Input path `{}` should be `config.ocio` file".format(config_path)) - config = ocio.Config().CreateFromFile(str(config_path)) + config = ocio.Config().CreateFromFile(config_path) colorspace_data = { "roles": {}, @@ -173,14 +168,12 @@ def get_views(in_path, out_path): > pyton.exe ./ocio_wrapper.py config get_views \ --in_path= --out_path= """ - json_path = Path(out_path) - out_data = _get_views_data(in_path) - with open(json_path, "w") as f_: + with open(out_path, "w") as f_: json.dump(out_data, f_) - print("Viewer data are saved to '{}'".format(json_path)) + print("Viewer data are saved to '{}'".format(out_path)) def _get_views_data(config_path): @@ -195,12 +188,10 @@ def _get_views_data(config_path): Returns: dict: aggregated available viewers """ - config_path = Path(config_path) - - if not config_path.is_file(): + if not os.path.isfile(config_path): raise IOError("Input path should be `config.ocio` file") - config = ocio.Config().CreateFromFile(str(config_path)) + config = ocio.Config().CreateFromFile(config_path) data_ = {} for display in config.getDisplays(): @@ -247,14 +238,12 @@ def get_version(config_path, out_path): > pyton.exe ./ocio_wrapper.py config get_version \ --config_path= --out_path= """ - json_path = Path(out_path) - out_data = _get_version_data(config_path) - with open(json_path, "w") as f_: + with open(out_path, "w") as f_: json.dump(out_data, f_) - print("Config version data are saved to '{}'".format(json_path)) + print("Config version data are saved to '{}'".format(out_path)) def _get_version_data(config_path): @@ -269,12 +258,10 @@ def _get_version_data(config_path): Returns: dict: minor and major keys with values """ - config_path = Path(config_path) - - if not config_path.is_file(): + if not os.path.isfile(config_path): raise IOError("Input path should be `config.ocio` file") - config = ocio.Config().CreateFromFile(str(config_path)) + config = ocio.Config().CreateFromFile(config_path) return { "major": config.getMajorVersion(), @@ -317,15 +304,13 @@ def get_config_file_rules_colorspace_from_filepath( colorspace get_config_file_rules_colorspace_from_filepath \ --config_path= --filepath= --out_path= """ - json_path = Path(out_path) - colorspace = _get_config_file_rules_colorspace_from_filepath( config_path, filepath) - with open(json_path, "w") as f_: + with open(out_path, "w") as f_: json.dump(colorspace, f_) - print("Colorspace name is saved to '{}'".format(json_path)) + print("Colorspace name is saved to '{}'".format(out_path)) def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): @@ -341,16 +326,14 @@ def _get_config_file_rules_colorspace_from_filepath(config_path, filepath): Returns: dict: aggregated available colorspaces """ - config_path = Path(config_path) - - if not config_path.is_file(): + if not os.path.isfile(config_path): raise IOError( "Input path `{}` should be `config.ocio` file".format(config_path)) - config = ocio.Config().CreateFromFile(str(config_path)) + config = ocio.Config().CreateFromFile(config_path) # TODO: use `parseColorSpaceFromString` instead if ocio v1 - colorspace = config.getColorSpaceFromFilepath(str(filepath)) + colorspace = config.getColorSpaceFromFilepath(filepath) return colorspace @@ -370,13 +353,10 @@ def _get_display_view_colorspace_name(config_path, display, view): Returns: view color space name (str) e.g. "Output - sRGB" """ - - config_path = Path(config_path) - - if not config_path.is_file(): + if not os.path.isfile(config_path): raise IOError("Input path should be `config.ocio` file") - config = ocio.Config.CreateFromFile(str(config_path)) + config = ocio.Config.CreateFromFile(config_path) colorspace = config.getDisplayViewColorSpaceName(display, view) return colorspace From 34359c217180c55e7bd56c53be6cfbf8f327aced Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Mar 2024 17:04:05 +0100 Subject: [PATCH 91/91] improving compatibility check --- openpype/pipeline/colorspace.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 2d1837e8ead..b42080ef061 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -463,13 +463,13 @@ def compatibility_check(): try: import PyOpenColorIO # noqa: F401 + # Requirement, introduced in newer ocio version config = PyOpenColorIO.GetCurrentConfig() - config.getDisplayViewColorSpaceName("sRGB", "sRGB") - CachedData.has_compatible_ocio_package = True + CachedData.has_compatible_ocio_package = ( + hasattr(config, "getDisplayViewColorSpaceName") + ) except ImportError: CachedData.has_compatible_ocio_package = False - except AttributeError: - CachedData.has_compatible_ocio_package = False # compatible return CachedData.has_compatible_ocio_package