From 396036432ebdb4f1b9ad3f739d522884c7faa80b Mon Sep 17 00:00:00 2001 From: Ben Souchet Date: Wed, 11 Sep 2024 12:17:13 +0200 Subject: [PATCH] Maya Instance Frame Range: Add functionality to properly update the frame range --- openpype/hosts/maya/api/lib.py | 88 ++++++++++++++----- openpype/hosts/maya/api/lib_rendersettings.py | 7 +- openpype/hosts/maya/api/pipeline.py | 3 +- .../maya/api/workfile_template_builder.py | 11 ++- .../defaults/project_settings/maya.json | 3 + .../projects_schema/schema_project_maya.json | 18 ++++ 6 files changed, 103 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6d4bda5d2ae..3a4bbfd4999 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2361,7 +2361,7 @@ def get_frame_range(include_animation_range=False): return frame_range -def reset_frame_range(playback=True, render=True, fps=True): +def reset_frame_range(playback=True, render=True, fps=True, instances=True): """Set frame range to current asset Args: @@ -2370,6 +2370,8 @@ def reset_frame_range(playback=True, render=True, fps=True): render (bool, Optional): Whether to set the maya render frame range. Defaults to True. fps (bool, Optional): Whether to set scene FPS. Defaults to True. + instances (bool, Optional): Whether to update publishable instances. + Defaults to True. """ if fps: set_scene_fps(get_fps_for_current_context()) @@ -2397,6 +2399,12 @@ def reset_frame_range(playback=True, render=True, fps=True): cmds.setAttr("defaultRenderGlobals.startFrame", animation_start) cmds.setAttr("defaultRenderGlobals.endFrame", animation_end) + if instances: + project_name = get_current_project_name() + settings = get_project_settings(project_name) + if settings["maya"]["update_publishable_frame_range"]["enabled"]: + update_instances_frame_range() + def reset_scene_resolution(): """Apply the scene resolution from the project definition @@ -2939,31 +2947,63 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def update_content_on_context_change(): +def iter_publish_instances(): + """Iterate over publishable instances (their objectSets). """ - This will update scene content to match new asset on context change + for node in cmds.ls( + "*.id", + long=True, + type="objectSet", + recursive=True, + objectsOnly=True + ): + if cmds.getAttr("{}.id".format(node)) != "pyblish.avalon.instance": + continue + yield node + + +def update_instances_asset_name(): + """Update 'asset' attribute of publishable instances (their objectSets) + that got one. """ - scene_sets = cmds.listSets(allSets=True) - asset_doc = get_current_project_asset() - new_asset = asset_doc["name"] - new_data = asset_doc["data"] - for s in scene_sets: - try: - if cmds.getAttr("{}.id".format(s)) == "pyblish.avalon.instance": - attr = cmds.listAttr(s) - print(s) - if "asset" in attr: - print(" - setting asset to: [ {} ]".format(new_asset)) - cmds.setAttr("{}.asset".format(s), - new_asset, type="string") - if "frameStart" in attr: - cmds.setAttr("{}.frameStart".format(s), - new_data["frameStart"]) - if "frameEnd" in attr: - cmds.setAttr("{}.frameEnd".format(s), - new_data["frameEnd"],) - except ValueError: - pass + + for instance in iter_publish_instances(): + if not cmds.attributeQuery("asset", node=instance, exists=True): + continue + attr = "{}.asset".format(instance) + cmds.setAttr(attr, get_current_asset_name(), type="string") + + +def update_instances_frame_range(): + """Update 'frameStart', 'frameEnd', 'handleStart', 'handleEnd' and 'fps' + attributes of publishable instances (their objectSets) that got one. + """ + + attributes = ["frameStart", "frameEnd", "handleStart", "handleEnd", "fps"] + + attrs_per_instance = {} + for instance in iter_publish_instances(): + instance_attrs = [ + attr for attr in attributes + if cmds.attributeQuery(attr, node=instance, exists=True) + ] + + if instance_attrs: + attrs_per_instance[instance] = instance_attrs + + if not attrs_per_instance: + # no instances with any frame related attributes + return + + fields = ["data.{}".format(key) for key in attributes] + asset_doc = get_current_project_asset(fields=fields) + asset_data = asset_doc["data"] + + for node, attrs in attrs_per_instance.items(): + for attr in attrs: + plug = "{}.{}".format(node, attr) + value = asset_data[attr] + cmds.setAttr(plug, value) def show_message(title, msg): diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 1f964589a9a..a4a846c5f80 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -172,7 +172,12 @@ def _set_arnold_settings(self, width, height): cmds.setAttr( "defaultArnoldDriver.mergeAOVs", multi_exr) self._additional_attribs_setter(additional_options) - reset_frame_range(playback=False, fps=False, render=True) + reset_frame_range( + playback=False, + fps=False, + render=True, + instances=False + ) def _set_redshift_settings(self, width, height): """Sets settings for Redshift.""" diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 1ecfdfaa404..edb2bbc8268 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -646,7 +646,8 @@ def on_task_changed(): with lib.suspended_refresh(): lib.set_context_settings() - lib.update_content_on_context_change() + lib.update_instances_frame_range() + lib.update_instances_asset_name() def before_workfile_open(): diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index d1ba3cc306d..0bf9008b5eb 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -14,7 +14,14 @@ WorkfileBuildPlaceholderDialog, ) -from .lib import read, imprint, get_reference_node, get_main_window +from .lib import ( + read, + imprint, + get_reference_node, + get_main_window, + update_instances_frame_range, + update_instances_asset_name, +) PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -257,6 +264,8 @@ def post_placeholder_process(self, placeholder, failed): cmds.sets(node, addElement=PLACEHOLDER_SET) cmds.hide(node) cmds.setAttr(node + ".hiddenInOutliner", True) + update_instances_frame_range() + update_instances_asset_name() def delete_placeholder(self, placeholder): """Remove placeholder if building was successful""" diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8a3791efb54..63b17a82c79 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -457,6 +457,9 @@ "include_handles_default": false, "per_task_type": [] }, + "update_publishable_frame_range": { + "enabled": true + }, "scriptsmenu": { "name": "OpenPype Tools", "definition": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index a6fd742b40d..04ece3c8d2d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -197,6 +197,24 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "update_publishable_frame_range", + "label": "Update publishable instances on Reset Frame Range", + "checkbox_key": "enabled", + "children": [ + { + "type": "label", + "label": "If enabled, the frame range and the handles of all the publishable instances will be updated when using the 'Reset Frame Range' functionality" + }, + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "dict", "key": "include_handles",