From 8e87ef674daba9f3bb7ae6edc1fd89e617ee304e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 20:32:36 +0200 Subject: [PATCH 1/2] Maya: Implement workfile template run script placeholder --- .../plugins/template/script_placeholder.py | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 client/ayon_core/hosts/maya/plugins/template/script_placeholder.py diff --git a/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py b/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py new file mode 100644 index 0000000000..893e2ec0cb --- /dev/null +++ b/client/ayon_core/hosts/maya/plugins/template/script_placeholder.py @@ -0,0 +1,201 @@ +from maya import cmds + +from ayon_core.hosts.maya.api.workfile_template_builder import ( + MayaPlaceholderPlugin +) +from ayon_core.lib import NumberDef, TextDef, EnumDef +from ayon_core.lib.events import weakref_partial + + +EXAMPLE_SCRIPT = """ +# Access maya commands +from maya import cmds + +# Access the placeholder node +placeholder_node = placeholder.scene_identifier + +# Access the event callback +if event is None: + print(f"Populating {placeholder}") +else: + if event.topic == "template.depth_processed": + print(f"Processed depth: {event.get('depth')}") + elif event.topic == "template.finished": + print("Build finished.") +""".strip() + + +class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): + """Execute a script at the given `order` during workfile build. + + This is a very low-level placeholder to run Python scripts at a given + point in time during the workfile template build. + + It can create either a locator or an objectSet as placeholder node. + It defaults to an objectSet, since allowing to run on e.g. other + placeholder node members can be useful, e.g. using: + + >>> members = cmds.sets(placeholder.scene_identifier, query=True) + + """ + + identifier = "maya.runscript" + label = "Run Python Script" + + use_selection_as_parent = False + + def get_placeholder_options(self, options=None): + options = options or {} + return [ + NumberDef( + "order", + label="Order", + default=options.get("order") or 0, + decimals=0, + minimum=0, + maximum=999, + tooltip=( + "Order" + "\nOrder defines asset loading priority (0 to 999)" + "\nPriority rule is : \"lowest is first to load\"." + ) + ), + TextDef( + "prepare_script", + label="Run at\nprepare", + tooltip="Run before populate at prepare order", + multiline=True, + default=options.get("prepare_script", "") + ), + TextDef( + "populate_script", + label="Run at\npopulate", + tooltip="Run script at populate node order
" + "This is the default behavior", + multiline=True, + default=options.get("populate_script", EXAMPLE_SCRIPT) + ), + TextDef( + "depth_processed_script", + label="Run after\ndepth\niteration", + tooltip="Run script after every build depth iteration", + multiline=True, + default=options.get("depth_processed_script", "") + ), + TextDef( + "finished_script", + label="Run after\nbuild", + tooltip=( + "Run script at build finished.
" + "Note: this even runs if other placeholders had " + "errors during the build" + ), + multiline=True, + default=options.get("finished_script", "") + ), + EnumDef( + "create_nodetype", + label="Nodetype", + items={ + "spaceLocator": "Locator", + "objectSet": "ObjectSet" + }, + tooltip=( + "The placeholder's node type to be created.
" + "Note this only works on create, not on update" + ), + default=options.get("create_nodetype", "objectSet") + ), + ] + + def create_placeholder(self, placeholder_data): + nodetype = placeholder_data.get("create_nodetype", "objectSet") + + if nodetype == "spaceLocator": + super(MayaPlaceholderScriptPlugin, self).create_placeholder( + placeholder_data + ) + elif nodetype == "objectSet": + placeholder_data["plugin_identifier"] = self.identifier + + # Create maya objectSet on selection + selection = cmds.ls(selection=True, long=True) + name = self._create_placeholder_name(placeholder_data) + node = cmds.sets(selection, name=name) + + self.imprint(node, placeholder_data) + + def prepare_placeholders(self, placeholders): + super(MayaPlaceholderScriptPlugin, self).prepare_placeholders( + placeholders + ) + for placeholder in placeholders: + prepare_script = placeholder.data.get("prepare_script") + if not prepare_script: + continue + + self.run_script(placeholder, prepare_script) + + def populate_placeholder(self, placeholder): + + populate_script = placeholder.data.get("populate_script") + depth_script = placeholder.data.get("depth_processed_script") + finished_script = placeholder.data.get("finished_script") + + # Run now + if populate_script: + self.run_script(placeholder, populate_script) + + if not any([depth_script, finished_script]): + # No callback scripts to run + if not placeholder.data.get("keep_placeholder", True): + self.delete_placeholder(placeholder) + return + + # Run at each depth processed + if depth_script: + callback = weakref_partial( + self.run_script, placeholder, depth_script) + self.builder.register_on_depth_processed_callback( + callback, order=placeholder.order) + + # Run at build finish + if finished_script: + callback = weakref_partial( + self.run_script, placeholder, finished_script) + self.builder.register_on_finished_callback( + callback, order=placeholder.order) + + # If placeholder should be deleted, delete it after finish so + # the scripts have access to it up to the last run + if not placeholder.data.get("keep_placeholder", True): + delete_callback = weakref_partial( + self.delete_placeholder, placeholder) + self.builder.register_on_finished_callback( + delete_callback, order=placeholder.order + 1) + + def run_script(self, placeholder, script, event=None): + """Run script + + Even though `placeholder` is an unused arguments by exposing it as + an input argument it means it makes it available through + globals()/locals() in the `exec` call, giving the script access + to the placeholder. + + For example: + >>> node = placeholder.scene_identifier + + In the case the script is running at a callback level (not during + populate) then it has access to the `event` as well, otherwise the + value is None if it runs during `populate_placeholder` directly. + + For example adding this as the callback script: + >>> if event is not None: + >>> if event.topic == "on_depth_processed": + >>> print(f"Processed depth: {event.get('depth')}") + >>> elif event.topic == "on_finished": + >>> print("Build finished.") + + """ + self.log.debug(f"Running script at event: {event}") + exec(script, locals()) From 7cf7e33452b22499db077876343af210913b8dd2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 22:35:57 +0200 Subject: [PATCH 2/2] Refactor adding callbacks --- .../hosts/maya/plugins/workfile_build/script_placeholder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py b/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py index 893e2ec0cb..62e10ba023 100644 --- a/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py +++ b/client/ayon_core/hosts/maya/plugins/workfile_build/script_placeholder.py @@ -156,14 +156,14 @@ class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): if depth_script: callback = weakref_partial( self.run_script, placeholder, depth_script) - self.builder.register_on_depth_processed_callback( + self.builder.add_on_depth_processed_callback( callback, order=placeholder.order) # Run at build finish if finished_script: callback = weakref_partial( self.run_script, placeholder, finished_script) - self.builder.register_on_finished_callback( + self.builder.add_on_finished_callback( callback, order=placeholder.order) # If placeholder should be deleted, delete it after finish so @@ -171,7 +171,7 @@ class MayaPlaceholderScriptPlugin(MayaPlaceholderPlugin): if not placeholder.data.get("keep_placeholder", True): delete_callback = weakref_partial( self.delete_placeholder, placeholder) - self.builder.register_on_finished_callback( + self.builder.add_on_finished_callback( delete_callback, order=placeholder.order + 1) def run_script(self, placeholder, script, event=None):