From 90fb363ce069d85e153c483d15e47533b058a579 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 15:56:30 +0200 Subject: [PATCH 01/24] allow to pass list of lists or dict to prepare_template_data --- openpype/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index a5254af0da..b27830f048 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -91,7 +91,7 @@ def prepare_template_data(fill_pairs): """ fill_data = {} - for key, value in fill_pairs: + for key, value in dict(fill_pairs).items(): # Handle cases when value is `None` (standalone publisher) if value is None: continue From bdc009bf5b06dce9e83b429074418f755587bf8e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 15:56:56 +0200 Subject: [PATCH 02/24] get_subset_name can accept dynamic_data --- openpype/lib/plugin_tools.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index b27830f048..26f1a2f7c4 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -34,7 +34,8 @@ def get_subset_name( asset_id, project_name=None, host_name=None, - default_template=None + default_template=None, + dynamic_data=None ): if not family: return "" @@ -68,11 +69,16 @@ def get_subset_name( if not task_name and "{task" in template.lower(): raise TaskNotSetError() - fill_pairs = ( - ("variant", variant), - ("family", family), - ("task", task_name) - ) + fill_pairs = { + "variant": variant, + "family": family, + "task": task_name + } + if dynamic_data: + # Dynamic data may override default values + for key, value in dynamic_data.items(): + fill_pairs[key] = value + return template.format(**prepare_template_data(fill_pairs)) From 6553a19c65859561bf7695c4f796f5f8dc89f9bc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 15:59:50 +0200 Subject: [PATCH 03/24] creator may have defined dynamic subset keys for subset name filling --- openpype/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugin.py b/openpype/plugin.py index 8ade0f3825..836d37ce70 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -16,6 +16,8 @@ class PypeCreatorMixin: Mixin class must be used as first in inheritance order to override methods. """ + dynamic_subset_keys = [] + @classmethod def get_subset_name( From c5972cd08f8ca25514c2ff0228551004e5a38099 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:00:48 +0200 Subject: [PATCH 04/24] implemented creator method to get dynamic data of the creator --- openpype/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/plugin.py b/openpype/plugin.py index 836d37ce70..e84d402844 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -18,6 +18,14 @@ class PypeCreatorMixin: """ dynamic_subset_keys = [] + @classmethod + def get_dynamic_data( + cls, variant, task_name, asset_id, project_name, host_name + ): + dynamic_data = {} + for key in cls.dynamic_subset_keys: + dynamic_data[key] = "{" + key + "}" + return dynamic_data @classmethod def get_subset_name( From 826d106f59b48a69f0738f2792edc27e55094f58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:01:10 +0200 Subject: [PATCH 05/24] creator's dynamic data are passed to get_subset_name --- openpype/plugin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/plugin.py b/openpype/plugin.py index e84d402844..27a75daedf 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -31,8 +31,18 @@ class PypeCreatorMixin: def get_subset_name( cls, variant, task_name, asset_id, project_name, host_name=None ): + dynamic_data = cls.get_dynamic_data( + variant, task_name, asset_id, project_name, host_name + ) + return get_subset_name( - cls.family, variant, task_name, asset_id, project_name, host_name + cls.family, + variant, + task_name, + asset_id, + project_name, + host_name, + dynamic_data=dynamic_data ) From 602214bbcd3341fc06036c3011944a767f8f6763 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:46:21 +0200 Subject: [PATCH 06/24] create render layer has dynamic data definitions --- .../plugins/create/create_render_layer.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 585f0c87d7..82defceeb0 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -11,6 +11,7 @@ class CreateRenderlayer(plugin.Creator): defaults = ["Main"] rename_group = True + render_pass = "beauty" subset_template = "{family}_{name}" rename_script_template = ( @@ -18,6 +19,24 @@ class CreateRenderlayer(plugin.Creator): " {clip_id} {group_id} {r} {g} {b} \"{name}\"" ) + dynamic_subset_keys = ["render_pass", "render_layer", "group"] + + @classmethod + def get_dynamic_data( + cls, variant, task_name, asset_id, project_name, host_name + ): + dynamic_data = super(CreateRenderlayer, cls).get_dynamic_data( + variant, task_name, asset_id, project_name, host_name + ) + # Use render pass name from creator's plugin + dynamic_data["render_pass"] = cls.render_pass + # Add variant to render layer + dynamic_data["render_layer"] = variant + # Change family for subset name fill + dynamic_data["family"] = "render" + + return dynamic_data + def process(self): self.log.debug("Query data from workfile.") instances = pipeline.list_instances() From c480fc837ada2258cfc3232ed9356081377a2cec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:46:39 +0200 Subject: [PATCH 07/24] create render pass has definition of dynamic keys --- .../tvpaint/plugins/create/create_render_pass.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 09c68930f2..8c72a69d05 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -15,6 +15,19 @@ class CreateRenderPass(plugin.Creator): defaults = ["Main"] subset_template = "{family}_{render_layer}_{pass}" + dynamic_subset_keys = ["render_pass", "render_layer", "layer"] + + @classmethod + def get_dynamic_data( + cls, variant, task_name, asset_id, project_name, host_name + ): + dynamic_data = super(CreateRenderPass, cls).get_dynamic_data( + variant, task_name, asset_id, project_name, host_name + ) + dynamic_data["render_pass"] = variant + dynamic_data["family"] = "render" + + return dynamic_data def process(self): self.log.debug("Query data from workfile.") From c698f1fd5537bb08e34b5b9b874f9bc0d80221a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:47:30 +0200 Subject: [PATCH 08/24] removed extraction of variant from subset name --- .../tvpaint/plugins/create/create_render_layer.py | 12 ------------ .../tvpaint/plugins/create/create_render_pass.py | 6 ------ 2 files changed, 18 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 82defceeb0..65aa39f166 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -67,18 +67,6 @@ class CreateRenderlayer(plugin.Creator): self.log.debug(f"Selected group id is \"{group_id}\".") self.data["group_id"] = group_id - family = self.data["family"] - # Extract entered name - name = self.data["subset"][len(family):] - self.log.info(f"Extracted name from subset name \"{name}\".") - self.data["name"] = name - - # Change subset name by template - subset_name = self.subset_template.format(**{ - "family": self.family, - "name": name - }) - self.log.info(f"New subset name \"{subset_name}\".") self.data["subset"] = subset_name # Check for instances of same group diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 8c72a69d05..d9a1455cb7 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -70,12 +70,6 @@ class CreateRenderPass(plugin.Creator): render_layer = beauty_instance["name"] - # Extract entered name - family = self.data["family"] - name = self.data["subset"] - # Is this right way how to get name? - name = name[len(family):] - self.log.info(f"Extracted name from subset name \"{name}\".") self.data["group_id"] = group_id self.data["pass"] = name From 2321e707712ca9ca5a42adc4f93226f6e7563e44 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:49:20 +0200 Subject: [PATCH 09/24] use "variant" instead of previously extracted name --- .../hosts/tvpaint/plugins/create/create_render_layer.py | 2 +- .../hosts/tvpaint/plugins/create/create_render_pass.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 65aa39f166..04bd341bab 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -122,7 +122,7 @@ class CreateRenderlayer(plugin.Creator): # Rename TVPaint group (keep color same) # - groups can't contain spaces - new_group_name = name.replace(" ", "_") + new_group_name = self.data["variant"].replace(" ", "_") rename_script = self.rename_script_template.format( clip_id=selected_group["clip_id"], group_id=selected_group["group_id"], diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index d9a1455cb7..f2b86985ec 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -71,8 +71,10 @@ class CreateRenderPass(plugin.Creator): render_layer = beauty_instance["name"] + variant = self.data["variant"] + self.data["group_id"] = group_id - self.data["pass"] = name + self.data["pass"] = variant self.data["render_layer"] = render_layer # Collect selected layer ids to be stored into instance @@ -95,7 +97,7 @@ class CreateRenderPass(plugin.Creator): if ( instance["family"] == family and instance["group_id"] == group_id - and instance["pass"] == name + and instance["pass"] == variant ): existing_instance = instance existing_instance_idx = idx @@ -104,7 +106,7 @@ class CreateRenderPass(plugin.Creator): if existing_instance is not None: self.log.info( f"Render pass instance for group id {group_id}" - f" and name \"{name}\" already exists, overriding." + f" and name \"{variant}\" already exists, overriding." ) instances[existing_instance_idx] = self.data else: From f12f0694af42aae630ce3c645433a09a14b35b2a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:50:02 +0200 Subject: [PATCH 10/24] set family back to creator's family --- openpype/hosts/tvpaint/plugins/create/create_render_layer.py | 4 ++++ openpype/hosts/tvpaint/plugins/create/create_render_pass.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 04bd341bab..2a98fa54ac 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -67,6 +67,10 @@ class CreateRenderlayer(plugin.Creator): self.log.debug(f"Selected group id is \"{group_id}\".") self.data["group_id"] = group_id + + # Set family back to "renderLayer" + family = self.family + self.data["family"] = family self.data["subset"] = subset_name # Check for instances of same group diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index f2b86985ec..4698c0b985 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -70,6 +70,9 @@ class CreateRenderPass(plugin.Creator): render_layer = beauty_instance["name"] + # Set family back to "renderPass" + family = self.family + self.data["family"] = family variant = self.data["variant"] From e669de4ba1da5ffb30acf0122676f0e0071cd21c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:51:18 +0200 Subject: [PATCH 11/24] replaced creation of new subset name with formatting dynamic keys in existing subset name --- .../plugins/create/create_render_pass.py | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 4698c0b985..da14326bc9 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -1,5 +1,6 @@ from avalon.tvpaint import pipeline, lib from openpype.hosts.tvpaint.api import plugin +from openpype.lib import prepare_template_data class CreateRenderPass(plugin.Creator): @@ -68,7 +69,35 @@ class CreateRenderPass(plugin.Creator): if beauty_instance is None: raise AssertionError("Beauty pass does not exist yet.") - render_layer = beauty_instance["name"] + subset_name = self.data["subset"] + + subset_name_fill_data = {} + + layer_key = "{layer}" + if layer_key in subset_name.lower(): + if len(selected_layers) != 1: + raise CreatorError(( + "Subset name expect to use layer name but" + " multiple layers are selected." + )) + subset_name_fill_data["layer"] = selected_layers[0]["name"] + + # Backwards compatibility + # - beauty may be created with older creator where variant was not + # stored + if "variant" not in beauty_instance: + render_layer = beauty_instance["name"] + else: + render_layer = beauty_instance["variant"] + + subset_name_fill_data["render_layer"] = render_layer + + # Format dynamic keys in subset name + new_subset_name = subset_name.format( + **prepare_template_data(subset_name_fill_data) + ) + self.data["subset"] = new_subset_name + self.log.info(f"New subset name is \"{new_subset_name}\".") # Set family back to "renderPass" family = self.family @@ -84,15 +113,6 @@ class CreateRenderPass(plugin.Creator): layer_names = [layer["name"] for layer in selected_layers] self.data["layer_names"] = layer_names - # Replace `beauty` in beauty's subset name with entered name - subset_name = self.subset_template.format(**{ - "family": family, - "render_layer": render_layer, - "pass": name - }) - self.data["subset"] = subset_name - self.log.info(f"New subset name is \"{subset_name}\".") - # Check if same instance already exists existing_instance = None existing_instance_idx = None From 7b0126944bfd319d6bd95e38fa957092c2e16285 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:52:12 +0200 Subject: [PATCH 12/24] added filling of dynamic keys in render layer creator --- .../plugins/create/create_render_layer.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 2a98fa54ac..176bb52746 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,5 +1,6 @@ from avalon.tvpaint import pipeline, lib from openpype.hosts.tvpaint.api import plugin +from openpype.lib import prepare_template_data class CreateRenderlayer(plugin.Creator): @@ -13,7 +14,6 @@ class CreateRenderlayer(plugin.Creator): rename_group = True render_pass = "beauty" - subset_template = "{family}_{name}" rename_script_template = ( "tv_layercolor \"setcolor\"" " {clip_id} {group_id} {r} {g} {b} \"{name}\"" @@ -67,10 +67,30 @@ class CreateRenderlayer(plugin.Creator): self.log.debug(f"Selected group id is \"{group_id}\".") self.data["group_id"] = group_id + group_data = lib.groups_data() + group_name = None + for group in group_data: + if group["group_id"] == group_id: + group_name = group["name"] + break + + if group_name is None: + raise AssertionError( + "Couldn't find group by id \"{}\"".format(group_id) + ) + + subset_name_fill_data = { + "group": group_name + } # Set family back to "renderLayer" family = self.family self.data["family"] = family + + # Fill dynamic key 'group' + subset_name = self.data["subset"].format( + **prepare_template_data(subset_name_fill_data) + ) self.data["subset"] = subset_name # Check for instances of same group From e003afadf9709320aac0630dee463ebbb704388e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:52:30 +0200 Subject: [PATCH 13/24] use CreatorError for known issues on artist side --- .../hosts/tvpaint/plugins/create/create_render_layer.py | 7 ++++--- .../hosts/tvpaint/plugins/create/create_render_pass.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 176bb52746..75551bc69f 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,3 +1,4 @@ +from avalon.api import CreatorError from avalon.tvpaint import pipeline, lib from openpype.hosts.tvpaint.api import plugin from openpype.lib import prepare_template_data @@ -51,16 +52,16 @@ class CreateRenderlayer(plugin.Creator): # Raise if there is no selection if not group_ids: - raise AssertionError("Nothing is selected.") + raise CreatorError("Nothing is selected.") # This creator should run only on one group if len(group_ids) > 1: - raise AssertionError("More than one group is in selection.") + raise CreatorError("More than one group is in selection.") group_id = tuple(group_ids)[0] # If group id is `0` it is `default` group which is invalid if group_id == 0: - raise AssertionError( + raise CreatorError( "Selection is not in group. Can't mark selection as Beauty." ) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index da14326bc9..f5792cc45b 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -1,3 +1,4 @@ +from avalon.api import CreatorError from avalon.tvpaint import pipeline, lib from openpype.hosts.tvpaint.api import plugin from openpype.lib import prepare_template_data @@ -15,7 +16,6 @@ class CreateRenderPass(plugin.Creator): icon = "cube" defaults = ["Main"] - subset_template = "{family}_{render_layer}_{pass}" dynamic_subset_keys = ["render_pass", "render_layer", "layer"] @classmethod @@ -46,11 +46,11 @@ class CreateRenderPass(plugin.Creator): # Raise if nothing is selected if not selected_layers: - raise AssertionError("Nothing is selected.") + raise CreatorError("Nothing is selected.") # Raise if layers from multiple groups are selected if len(group_ids) != 1: - raise AssertionError("More than one group is in selection.") + raise CreatorError("More than one group is in selection.") group_id = tuple(group_ids)[0] self.log.debug(f"Selected group id is \"{group_id}\".") @@ -67,7 +67,7 @@ class CreateRenderPass(plugin.Creator): # Beauty is required for this creator so raise if was not found if beauty_instance is None: - raise AssertionError("Beauty pass does not exist yet.") + raise CreatorError("Beauty pass does not exist yet.") subset_name = self.data["subset"] From b912cd4ef0f05446d2f9bd32f776f0057d723fb8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 16:53:08 +0200 Subject: [PATCH 14/24] collector won't change subset name but kept backwards compatibility by checking "variant" key in instance data --- .../plugins/publish/collect_instances.py | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 9b11f9fe80..b7738b0ac0 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -119,19 +119,23 @@ class CollectInstances(pyblish.api.ContextPlugin): name = instance_data["name"] # Change label subset_name = instance_data["subset"] - instance_data["label"] = "{}_Beauty".format(name) - # Change subset name - # Final family of an instance will be `render` - new_family = "render" - task_name = io.Session["AVALON_TASK"] - new_subset_name = "{}{}_{}_Beauty".format( - new_family, task_name.capitalize(), name - ) - instance_data["subset"] = new_subset_name - self.log.debug("Changed subset name \"{}\"->\"{}\"".format( - subset_name, new_subset_name - )) + # Backwards compatibility + # - subset names were not stored as final subset names during creation + if "variant" not in instance_data: + instance_data["label"] = "{}_Beauty".format(name) + + # Change subset name + # Final family of an instance will be `render` + new_family = "render" + task_name = io.Session["AVALON_TASK"] + new_subset_name = "{}{}_{}_Beauty".format( + new_family, task_name.capitalize(), name + ) + instance_data["subset"] = new_subset_name + self.log.debug("Changed subset name \"{}\"->\"{}\"".format( + subset_name, new_subset_name + )) # Get all layers for the layer layers_data = context.data["layersData"] @@ -165,18 +169,21 @@ class CollectInstances(pyblish.api.ContextPlugin): render_layer = instance_data["render_layer"] instance_data["label"] = "{}_{}".format(render_layer, pass_name) - # Change subset name - # Final family of an instance will be `render` - new_family = "render" - old_subset_name = instance_data["subset"] - task_name = io.Session["AVALON_TASK"] - new_subset_name = "{}{}_{}_{}".format( - new_family, task_name.capitalize(), render_layer, pass_name - ) - instance_data["subset"] = new_subset_name - self.log.debug("Changed subset name \"{}\"->\"{}\"".format( - old_subset_name, new_subset_name - )) + # Backwards compatibility + # - subset names were not stored as final subset names during creation + if "variant" not in instance_data: + # Change subset name + # Final family of an instance will be `render` + new_family = "render" + old_subset_name = instance_data["subset"] + task_name = io.Session["AVALON_TASK"] + new_subset_name = "{}{}_{}_{}".format( + new_family, task_name.capitalize(), render_layer, pass_name + ) + instance_data["subset"] = new_subset_name + self.log.debug("Changed subset name \"{}\"->\"{}\"".format( + old_subset_name, new_subset_name + )) layers_data = context.data["layersData"] layers_by_name = { From 7ae3cf157eb6714d00d83bde8591845afacaad22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Jun 2021 18:45:22 +0200 Subject: [PATCH 15/24] capitalization works only on char and number symbols --- openpype/lib/plugin_tools.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 26f1a2f7c4..1f2fb7a46e 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -97,6 +97,7 @@ def prepare_template_data(fill_pairs): """ fill_data = {} + regex = re.compile(r"[a-zA-Z0-9]") for key, value in dict(fill_pairs).items(): # Handle cases when value is `None` (standalone publisher) if value is None: @@ -108,13 +109,18 @@ def prepare_template_data(fill_pairs): # Capitalize only first char of value # - conditions are because of possible index errors + # - regex is to skip symbols that are not chars or numbers + # - e.g. "{key}" which starts with curly bracket capitalized = "" - if value: - # Upper first character - capitalized += value[0].upper() - # Append rest of string if there is any - if len(value) > 1: - capitalized += value[1:] + for idx in range(len(value or "")): + char = value[idx] + if not regex.match(char): + capitalized += char + else: + capitalized += char.upper() + capitalized += value[idx + 1:] + break + fill_data[key.capitalize()] = capitalized return fill_data From ef756634be163836cc97250047f3a473ba80def8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 10:07:10 +0200 Subject: [PATCH 16/24] added default subset template for tvpaint render families to settings --- .../settings/defaults/project_settings/global.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 5f779fccfa..4351f18a60 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -215,6 +215,17 @@ "hosts": [], "tasks": [], "template": "{family}{Task}{Variant}" + }, + { + "families": [ + "renderLayer", + "renderPass" + ], + "hosts": [ + "tvpaint" + ], + "tasks": [], + "template": "{family}{Task}_{Render_layer}_{Render_pass}" } ] }, From fd350b2c8417d5a029a43fa55ccbfebc595feace Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 10:32:16 +0200 Subject: [PATCH 17/24] process data contain right family --- openpype/hosts/tvpaint/plugins/create/create_render_layer.py | 4 +--- openpype/hosts/tvpaint/plugins/create/create_render_pass.py | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py index 75551bc69f..cd0a86bda3 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -84,9 +84,7 @@ class CreateRenderlayer(plugin.Creator): "group": group_name } - # Set family back to "renderLayer" - family = self.family - self.data["family"] = family + family = self.family = self.data["family"] # Fill dynamic key 'group' subset_name = self.data["subset"].format( diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index f5792cc45b..4ace19dff5 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -99,10 +99,7 @@ class CreateRenderPass(plugin.Creator): self.data["subset"] = new_subset_name self.log.info(f"New subset name is \"{new_subset_name}\".") - # Set family back to "renderPass" - family = self.family - self.data["family"] = family - + family = self.data["family"] variant = self.data["variant"] self.data["group_id"] = group_id From 5e355657c3413397a1fa31e95352e58eb9600e4d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 11:04:52 +0200 Subject: [PATCH 18/24] removed layer dynamic key from render pass --- .../tvpaint/plugins/create/create_render_pass.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 4ace19dff5..ec04e9b2ee 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -16,7 +16,7 @@ class CreateRenderPass(plugin.Creator): icon = "cube" defaults = ["Main"] - dynamic_subset_keys = ["render_pass", "render_layer", "layer"] + dynamic_subset_keys = ["render_pass", "render_layer"] @classmethod def get_dynamic_data( @@ -73,15 +73,6 @@ class CreateRenderPass(plugin.Creator): subset_name_fill_data = {} - layer_key = "{layer}" - if layer_key in subset_name.lower(): - if len(selected_layers) != 1: - raise CreatorError(( - "Subset name expect to use layer name but" - " multiple layers are selected." - )) - subset_name_fill_data["layer"] = selected_layers[0]["name"] - # Backwards compatibility # - beauty may be created with older creator where variant was not # stored From 0c34a12ae7bc1e1ac89b112316e45cee35aec8d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Jun 2021 12:07:27 +0200 Subject: [PATCH 19/24] added docstring for get_dynamic_data --- openpype/plugin.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/plugin.py b/openpype/plugin.py index 27a75daedf..45c9a08209 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -22,8 +22,34 @@ class PypeCreatorMixin: def get_dynamic_data( cls, variant, task_name, asset_id, project_name, host_name ): + """Return dynamic data for current Creator plugin. + + By default return keys from `dynamic_subset_keys` attribute as mapping + to keep formatted template unchanged. + + ``` + dynamic_subset_keys = ["my_key"] + --- + output = { + "my_key": "{my_key}" + } + ``` + + Dynamic keys may override default Creator keys (family, task, asset, + ...) but do it wisely if you need. + + All of keys will be converted into 3 variants unchanged, capitalized + and all upper letters. Because of that are all keys lowered. + + This method can be modified to prefill some values just keep in mind it + is class method. + + Returns: + dict: Fill data for subset name template. + """ dynamic_data = {} for key in cls.dynamic_subset_keys: + key = key.lower() dynamic_data[key] = "{" + key + "}" return dynamic_data From 7c6d609c701bc2b6dfe57da6118e23fb860c8879 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Jun 2021 16:44:50 +0200 Subject: [PATCH 20/24] don't change label of render pass --- openpype/hosts/tvpaint/plugins/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index b7738b0ac0..898c056f31 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -167,11 +167,11 @@ class CollectInstances(pyblish.api.ContextPlugin): ) # Change label render_layer = instance_data["render_layer"] - instance_data["label"] = "{}_{}".format(render_layer, pass_name) # Backwards compatibility # - subset names were not stored as final subset names during creation if "variant" not in instance_data: + instance_data["label"] = "{}_{}".format(render_layer, pass_name) # Change subset name # Final family of an instance will be `render` new_family = "render" From b4f8c2943425d34dcb3b3b8d05f82397bad939ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Jun 2021 17:11:17 +0200 Subject: [PATCH 21/24] use get_subset_name to get subset name for workfile and review instances --- .../plugins/publish/collect_instances.py | 35 +++++++++++++++++-- .../plugins/publish/collect_workfile.py | 34 +++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 898c056f31..4468bfae40 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -4,6 +4,8 @@ import copy import pyblish.api from avalon import io +from openpype.lib import get_subset_name + class CollectInstances(pyblish.api.ContextPlugin): label = "Collect Instances" @@ -62,9 +64,38 @@ class CollectInstances(pyblish.api.ContextPlugin): # Different instance creation based on family instance = None if family == "review": - # Change subset name + # Change subset name of review instance + + # Collect asset doc to get asset id + # - not sure if it's good idea to require asset id in + # get_subset_name? + asset_name = context.data["workfile_context"]["asset"] + asset_doc = io.find_one( + { + "type": "asset", + "name": asset_name + }, + {"_id": 1} + ) + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] + + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Host name from environemnt variable + host_name = os.environ["AVALON_APP"] + # Use empty variant value + variant = "" task_name = io.Session["AVALON_TASK"] - new_subset_name = "{}{}".format(family, task_name.capitalize()) + new_subset_name = get_subset_name( + family, + variant, + task_name, + asset_id, + project_name, + host_name + ) instance_data["subset"] = new_subset_name instance = context.create_instance(**instance_data) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py index b059be90bf..b61fec895f 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile.py @@ -3,6 +3,8 @@ import json import pyblish.api from avalon import io +from openpype.lib import get_subset_name + class CollectWorkfile(pyblish.api.ContextPlugin): label = "Collect Workfile" @@ -20,8 +22,38 @@ class CollectWorkfile(pyblish.api.ContextPlugin): basename, ext = os.path.splitext(filename) instance = context.create_instance(name=basename) + # Get subset name of workfile instance + # Collect asset doc to get asset id + # - not sure if it's good idea to require asset id in + # get_subset_name? + family = "workfile" + asset_name = context.data["workfile_context"]["asset"] + asset_doc = io.find_one( + { + "type": "asset", + "name": asset_name + }, + {"_id": 1} + ) + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] + + # Project name from workfile context + project_name = context.data["workfile_context"]["project"] + # Host name from environemnt variable + host_name = os.environ["AVALON_APP"] + # Use empty variant value + variant = "" task_name = io.Session["AVALON_TASK"] - subset_name = "workfile" + task_name.capitalize() + subset_name = get_subset_name( + family, + variant, + task_name, + asset_id, + project_name, + host_name + ) # Create Workfile instance instance.data.update({ From 913f4819596056643d0ba86f3c303bb0f2d724f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Jun 2021 17:23:01 +0200 Subject: [PATCH 22/24] with windows shell prelaunch hook to found app hook and simplyfied it --- openpype/hooks/pre_foundry_apps.py | 27 +++++++++++++++ openpype/hooks/pre_with_windows_shell.py | 44 ------------------------ 2 files changed, 27 insertions(+), 44 deletions(-) create mode 100644 openpype/hooks/pre_foundry_apps.py delete mode 100644 openpype/hooks/pre_with_windows_shell.py diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py new file mode 100644 index 0000000000..1b2c0c6eda --- /dev/null +++ b/openpype/hooks/pre_foundry_apps.py @@ -0,0 +1,27 @@ +import subprocess +from openpype.lib import PreLaunchHook + + +class LaunchWindowsShell(PreLaunchHook): + """Add shell command before executable. + + Some hosts have issues when are launched directly from python in that case + it is possible to prepend shell executable which will trigger process + instead. + """ + + # Should be as last hook because must change launch arguments to string + order = 1000 + app_groups = ["nuke", "nukex", "hiero", "nukestudio"] + platforms = ["windows"] + + def execute(self): + # Change `creationflags` to CREATE_NEW_CONSOLE + # - on Windows will nuke create new window using it's console + # Set `stdout` and `stderr` to None so new created console does not + # have redirected output to DEVNULL in build + self.launch_context.kwargs.update({ + "creationflags": subprocess.CREATE_NEW_CONSOLE, + "stdout": None, + "stderr": None + }) diff --git a/openpype/hooks/pre_with_windows_shell.py b/openpype/hooks/pre_with_windows_shell.py deleted file mode 100644 index 441ab1a675..0000000000 --- a/openpype/hooks/pre_with_windows_shell.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -import subprocess -from openpype.lib import PreLaunchHook - - -class LaunchWithWindowsShell(PreLaunchHook): - """Add shell command before executable. - - Some hosts have issues when are launched directly from python in that case - it is possible to prepend shell executable which will trigger process - instead. - """ - - # Should be as last hook because must change launch arguments to string - order = 1000 - app_groups = ["nuke", "nukex", "hiero", "nukestudio"] - platforms = ["windows"] - - def execute(self): - launch_args = self.launch_context.clear_launch_args( - self.launch_context.launch_args) - new_args = [ - # Get comspec which is cmd.exe in most cases. - os.environ.get("COMSPEC", "cmd.exe"), - # NOTE change to "/k" if want to keep console opened - "/c", - # Convert arguments to command line arguments (as string) - "\"{}\"".format( - subprocess.list2cmdline(launch_args) - ) - ] - # Convert list to string - # WARNING this only works if is used as string - args_string = " ".join(new_args) - self.log.info(( - "Modified launch arguments to be launched with shell \"{}\"." - ).format(args_string)) - - # Replace launch args with new one - self.launch_context.launch_args = args_string - # Change `creationflags` to CREATE_NEW_CONSOLE - self.launch_context.kwargs["creationflags"] = ( - subprocess.CREATE_NEW_CONSOLE - ) From c88fb84184886516f190b3a91e22e9b6b6c68061 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Jun 2021 17:32:14 +0200 Subject: [PATCH 23/24] changed class name and docstring --- openpype/hooks/pre_foundry_apps.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 1b2c0c6eda..85f68c6b60 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -2,12 +2,13 @@ import subprocess from openpype.lib import PreLaunchHook -class LaunchWindowsShell(PreLaunchHook): - """Add shell command before executable. +class LaunchFoundryAppsWindows(PreLaunchHook): + """Foundry applications have specific way how to launch them. - Some hosts have issues when are launched directly from python in that case - it is possible to prepend shell executable which will trigger process - instead. + Nuke is executed "like" python process so it is required to pass + `CREATE_NEW_CONSOLE` flag on windows to trigger creation of new console. + At the same time the newly created console won't create it's own stdout + and stderr handlers so they should not be redirected to DEVNULL. """ # Should be as last hook because must change launch arguments to string From 9ff545352a2560478c72ab801a0966a6f06317a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Jun 2021 10:11:34 +0200 Subject: [PATCH 24/24] Revert "with windows shell prelaunch hook to found app hook and simplyfied it" This reverts commit 913f4819596056643d0ba86f3c303bb0f2d724f3. # Conflicts: # openpype/hooks/pre_foundry_apps.py --- openpype/hooks/pre_foundry_apps.py | 28 --------------- openpype/hooks/pre_with_windows_shell.py | 44 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 28 deletions(-) delete mode 100644 openpype/hooks/pre_foundry_apps.py create mode 100644 openpype/hooks/pre_with_windows_shell.py diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py deleted file mode 100644 index 85f68c6b60..0000000000 --- a/openpype/hooks/pre_foundry_apps.py +++ /dev/null @@ -1,28 +0,0 @@ -import subprocess -from openpype.lib import PreLaunchHook - - -class LaunchFoundryAppsWindows(PreLaunchHook): - """Foundry applications have specific way how to launch them. - - Nuke is executed "like" python process so it is required to pass - `CREATE_NEW_CONSOLE` flag on windows to trigger creation of new console. - At the same time the newly created console won't create it's own stdout - and stderr handlers so they should not be redirected to DEVNULL. - """ - - # Should be as last hook because must change launch arguments to string - order = 1000 - app_groups = ["nuke", "nukex", "hiero", "nukestudio"] - platforms = ["windows"] - - def execute(self): - # Change `creationflags` to CREATE_NEW_CONSOLE - # - on Windows will nuke create new window using it's console - # Set `stdout` and `stderr` to None so new created console does not - # have redirected output to DEVNULL in build - self.launch_context.kwargs.update({ - "creationflags": subprocess.CREATE_NEW_CONSOLE, - "stdout": None, - "stderr": None - }) diff --git a/openpype/hooks/pre_with_windows_shell.py b/openpype/hooks/pre_with_windows_shell.py new file mode 100644 index 0000000000..441ab1a675 --- /dev/null +++ b/openpype/hooks/pre_with_windows_shell.py @@ -0,0 +1,44 @@ +import os +import subprocess +from openpype.lib import PreLaunchHook + + +class LaunchWithWindowsShell(PreLaunchHook): + """Add shell command before executable. + + Some hosts have issues when are launched directly from python in that case + it is possible to prepend shell executable which will trigger process + instead. + """ + + # Should be as last hook because must change launch arguments to string + order = 1000 + app_groups = ["nuke", "nukex", "hiero", "nukestudio"] + platforms = ["windows"] + + def execute(self): + launch_args = self.launch_context.clear_launch_args( + self.launch_context.launch_args) + new_args = [ + # Get comspec which is cmd.exe in most cases. + os.environ.get("COMSPEC", "cmd.exe"), + # NOTE change to "/k" if want to keep console opened + "/c", + # Convert arguments to command line arguments (as string) + "\"{}\"".format( + subprocess.list2cmdline(launch_args) + ) + ] + # Convert list to string + # WARNING this only works if is used as string + args_string = " ".join(new_args) + self.log.info(( + "Modified launch arguments to be launched with shell \"{}\"." + ).format(args_string)) + + # Replace launch args with new one + self.launch_context.launch_args = args_string + # Change `creationflags` to CREATE_NEW_CONSOLE + self.launch_context.kwargs["creationflags"] = ( + subprocess.CREATE_NEW_CONSOLE + )