From a40a845a0346576200795681b295229bf5dfbd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 23 Dec 2025 16:15:21 +0100 Subject: [PATCH 1/7] Feature: Override template backdrop container name --- .../ayon_harmony/js/loaders/TemplateLoader.js | 22 ++++++++++++++++--- .../plugins/load/load_template.py | 13 +++++++++-- server/settings/load_plugins.py | 22 +++++++++++++++++++ server/settings/main.py | 11 ++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 server/settings/load_plugins.py diff --git a/client/ayon_harmony/js/loaders/TemplateLoader.js b/client/ayon_harmony/js/loaders/TemplateLoader.js index c6d2b2cb..555145ab 100644 --- a/client/ayon_harmony/js/loaders/TemplateLoader.js +++ b/client/ayon_harmony/js/loaders/TemplateLoader.js @@ -23,12 +23,22 @@ var TemplateLoader = function() {}; /** * Load template as container. * @function - * @param {string} templatePath Path to tpl file. + * @param {array} args Array of arguments. * @return {string} Name of backdrop container. + * @example + * // arguments are in this order: + * var args = [ + * templatePath, // Path to tpl file + * overrideName // Override name of backdrop container + * ]; */ -TemplateLoader.prototype.loadContainer = function(templatePath) { +TemplateLoader.prototype.loadContainer = function(args) { + var templatePath = args[0]; + var overrideName = args[1] || ""; + // Copy from template file MessageLog.trace("loadContainer:: "); + var hasOverrideName = overrideName !== undefined && overrideName !== null && overrideName !== ""; function splitByLastDelimiter(str, delimiter) { var lastIndex = str.lastIndexOf(delimiter); @@ -81,7 +91,7 @@ TemplateLoader.prototype.loadContainer = function(templatePath) { } count = backdropCounts[backdropName] !== undefined ? backdropCounts[backdropName] : 1; - if (count > 1){ + if (!hasOverrideName && count > 1){ // count -1 to match imported nodes which start from _1 mainBackdropName = mainBackdropName + "_" + (count - 1); @@ -90,6 +100,12 @@ TemplateLoader.prototype.loadContainer = function(templatePath) { Backdrop.setBackdrops("Top", allBackdrops); } + if (hasOverrideName) { + mainBackdropName = overrideName; + allBackdrops[0].title.text = mainBackdropName; + Backdrop.setBackdrops("Top", allBackdrops); + } + return mainBackdropName; }; diff --git a/client/ayon_harmony/plugins/load/load_template.py b/client/ayon_harmony/plugins/load/load_template.py index 58dc9b79..39c47758 100644 --- a/client/ayon_harmony/plugins/load/load_template.py +++ b/client/ayon_harmony/plugins/load/load_template.py @@ -15,6 +15,7 @@ class TemplateLoader(harmony.BackdropBaseLoader): representations = {"tpl"} label = "Load Template" icon = "gift" + override_name = "" def load(self, context, name=None, namespace=None, data=None): """Plugin entry point. @@ -30,6 +31,11 @@ def load(self, context, name=None, namespace=None, data=None): self_name = self.__class__.__name__ temp_dir = tempfile.mkdtemp() zip_file = self.filepath_from_context(context) + + # Override container name + override_name = "" + if self.override_name: + override_name = self.override_name.format(**context) with zipfile.ZipFile(zip_file, "r") as zip_ref: zip_ref.extractall(temp_dir) @@ -39,7 +45,10 @@ def load(self, context, name=None, namespace=None, data=None): "function": f"AyonHarmony.Loaders.{self_name}.loadContainer", # Published tpl name is not consistent, use first found, # must be only one - "args": next(Path(temp_dir).glob("*.tpl")).as_posix(), + "args": [ + next(Path(temp_dir).glob("*.tpl")).as_posix(), + override_name + ], } )["result"] @@ -48,7 +57,7 @@ def load(self, context, name=None, namespace=None, data=None): # We must validate the group_node return harmony.containerise( - name, + backdrop_name, namespace, backdrop_name, context, diff --git a/server/settings/load_plugins.py b/server/settings/load_plugins.py new file mode 100644 index 00000000..0f68fa62 --- /dev/null +++ b/server/settings/load_plugins.py @@ -0,0 +1,22 @@ +from ayon_server.settings import BaseSettingsModel, SettingsField + + +class TemplateLoaderPluginModel(BaseSettingsModel): + enabled: bool = SettingsField(True, title="Enabled") + override_name: str = SettingsField( + "", + title="Override container name", + description=( + "Optional container name to override the default name.\n" + "Keys are available in the context of the loader plugin.", + "Example keys: project[name], folder[name], product[name], version[name]..." + ), + placeholder="{folder[name]}_{product[name]}", + ) + + +class HarmonyLoadPlugins(BaseSettingsModel): + TemplateLoader: TemplateLoaderPluginModel = SettingsField( + default_factory=TemplateLoaderPluginModel, + title="Template Loader", + ) \ No newline at end of file diff --git a/server/settings/main.py b/server/settings/main.py index f90000b9..aabfbe8b 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -2,6 +2,7 @@ from .imageio import HarmonyImageIOModel from .creator_plugins import HarmonyCreatePlugins +from .load_plugins import HarmonyLoadPlugins from .publish_plugins import HarmonyPublishPlugins @@ -16,6 +17,10 @@ class HarmonySettings(BaseSettingsModel): default_factory=HarmonyCreatePlugins, title="Creator plugins" ) + load: HarmonyLoadPlugins = SettingsField( + default_factory=HarmonyLoadPlugins, + title="Loader plugins", + ) publish: HarmonyPublishPlugins = SettingsField( default_factory=HarmonyPublishPlugins, title="Publish plugins" @@ -84,6 +89,12 @@ class HarmonySettings(BaseSettingsModel): } } }, + "load": { + "TemplateLoader": { + "enabled": True, + "override_name": "" + } + }, "publish": { "CollectPalettes": { "allowed_tasks": [ From 7f2da0e09668cd106f4994d489ae2a94d22453c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 23 Dec 2025 16:36:12 +0100 Subject: [PATCH 2/7] linting --- client/ayon_harmony/plugins/load/load_template.py | 2 +- server/settings/load_plugins.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_harmony/plugins/load/load_template.py b/client/ayon_harmony/plugins/load/load_template.py index 39c47758..9c5132d3 100644 --- a/client/ayon_harmony/plugins/load/load_template.py +++ b/client/ayon_harmony/plugins/load/load_template.py @@ -31,7 +31,7 @@ def load(self, context, name=None, namespace=None, data=None): self_name = self.__class__.__name__ temp_dir = tempfile.mkdtemp() zip_file = self.filepath_from_context(context) - + # Override container name override_name = "" if self.override_name: diff --git a/server/settings/load_plugins.py b/server/settings/load_plugins.py index 0f68fa62..7559d645 100644 --- a/server/settings/load_plugins.py +++ b/server/settings/load_plugins.py @@ -8,8 +8,8 @@ class TemplateLoaderPluginModel(BaseSettingsModel): title="Override container name", description=( "Optional container name to override the default name.\n" - "Keys are available in the context of the loader plugin.", - "Example keys: project[name], folder[name], product[name], version[name]..." + "Keys are available in the context of the loader plugin.\n" + "Example keys: project[name], folder[name], product[name]..." ), placeholder="{folder[name]}_{product[name]}", ) @@ -19,4 +19,4 @@ class HarmonyLoadPlugins(BaseSettingsModel): TemplateLoader: TemplateLoaderPluginModel = SettingsField( default_factory=TemplateLoaderPluginModel, title="Template Loader", - ) \ No newline at end of file + ) From 71f0aef547fbe65087fbc2d67f25d98657ecb5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 9 Jan 2026 17:10:07 +0100 Subject: [PATCH 3/7] restoring anti duplicate name --- client/ayon_harmony/js/loaders/TemplateLoader.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/ayon_harmony/js/loaders/TemplateLoader.js b/client/ayon_harmony/js/loaders/TemplateLoader.js index 555145ab..92ad91bf 100644 --- a/client/ayon_harmony/js/loaders/TemplateLoader.js +++ b/client/ayon_harmony/js/loaders/TemplateLoader.js @@ -38,7 +38,6 @@ TemplateLoader.prototype.loadContainer = function(args) { // Copy from template file MessageLog.trace("loadContainer:: "); - var hasOverrideName = overrideName !== undefined && overrideName !== null && overrideName !== ""; function splitByLastDelimiter(str, delimiter) { var lastIndex = str.lastIndexOf(delimiter); @@ -72,6 +71,9 @@ TemplateLoader.prototype.loadContainer = function(args) { } }); + // If overrideName is provided, use it instead of the main backdrop name + if (overrideName) { mainBackdropName = overrideName; } + var allBackdrops = Backdrop.backdrops("Top"); var backdropCounts = {}; for (var i = 0; i < allBackdrops.length; i++) { @@ -91,7 +93,7 @@ TemplateLoader.prototype.loadContainer = function(args) { } count = backdropCounts[backdropName] !== undefined ? backdropCounts[backdropName] : 1; - if (!hasOverrideName && count > 1){ + if (count > 1){ // count -1 to match imported nodes which start from _1 mainBackdropName = mainBackdropName + "_" + (count - 1); @@ -100,12 +102,6 @@ TemplateLoader.prototype.loadContainer = function(args) { Backdrop.setBackdrops("Top", allBackdrops); } - if (hasOverrideName) { - mainBackdropName = overrideName; - allBackdrops[0].title.text = mainBackdropName; - Backdrop.setBackdrops("Top", allBackdrops); - } - return mainBackdropName; }; From 59da6bbfe3f2a821e194124541ac3ed2c1b36057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 23 Dec 2025 16:15:21 +0100 Subject: [PATCH 4/7] Feature: Override template backdrop container name --- .../ayon_harmony/js/loaders/TemplateLoader.js | 22 ++++++++++++++++--- .../plugins/load/load_template.py | 13 +++++++++-- server/settings/load_plugins.py | 22 +++++++++++++++++++ server/settings/main.py | 11 ++++++++++ 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 server/settings/load_plugins.py diff --git a/client/ayon_harmony/js/loaders/TemplateLoader.js b/client/ayon_harmony/js/loaders/TemplateLoader.js index 8b012a59..655b96ec 100644 --- a/client/ayon_harmony/js/loaders/TemplateLoader.js +++ b/client/ayon_harmony/js/loaders/TemplateLoader.js @@ -23,12 +23,22 @@ var TemplateLoader = function() {}; /** * Load template as container. * @function - * @param {string} templatePath Path to tpl file. + * @param {array} args Array of arguments. * @return {string} Name of backdrop container. + * @example + * // arguments are in this order: + * var args = [ + * templatePath, // Path to tpl file + * overrideName // Override name of backdrop container + * ]; */ -TemplateLoader.prototype.loadContainer = function(templatePath) { +TemplateLoader.prototype.loadContainer = function(args) { + var templatePath = args[0]; + var overrideName = args[1] || ""; + // Copy from template file MessageLog.trace("loadContainer:: "); + var hasOverrideName = overrideName !== undefined && overrideName !== null && overrideName !== ""; /** * Parse a backdrop name into its base name and numeric suffix count. @@ -206,7 +216,7 @@ TemplateLoader.prototype.loadContainer = function(templatePath) { var mainBackdropParsed = parseBackdropName(mainBackdropName); var count = backdropCounts[mainBackdropParsed.baseName] !== undefined ? backdropCounts[mainBackdropParsed.baseName] : 1; - if (count > 1){ + if (!hasOverrideName && count > 1){ // count -1 to match imported nodes which start from _1 mainBackdropName = mainBackdropName + "_" + (count - 1); @@ -215,6 +225,12 @@ TemplateLoader.prototype.loadContainer = function(templatePath) { Backdrop.setBackdrops("Top", allBackdrops); } + if (hasOverrideName) { + mainBackdropName = overrideName; + allBackdrops[0].title.text = mainBackdropName; + Backdrop.setBackdrops("Top", allBackdrops); + } + return mainBackdropName; }; diff --git a/client/ayon_harmony/plugins/load/load_template.py b/client/ayon_harmony/plugins/load/load_template.py index 58dc9b79..39c47758 100644 --- a/client/ayon_harmony/plugins/load/load_template.py +++ b/client/ayon_harmony/plugins/load/load_template.py @@ -15,6 +15,7 @@ class TemplateLoader(harmony.BackdropBaseLoader): representations = {"tpl"} label = "Load Template" icon = "gift" + override_name = "" def load(self, context, name=None, namespace=None, data=None): """Plugin entry point. @@ -30,6 +31,11 @@ def load(self, context, name=None, namespace=None, data=None): self_name = self.__class__.__name__ temp_dir = tempfile.mkdtemp() zip_file = self.filepath_from_context(context) + + # Override container name + override_name = "" + if self.override_name: + override_name = self.override_name.format(**context) with zipfile.ZipFile(zip_file, "r") as zip_ref: zip_ref.extractall(temp_dir) @@ -39,7 +45,10 @@ def load(self, context, name=None, namespace=None, data=None): "function": f"AyonHarmony.Loaders.{self_name}.loadContainer", # Published tpl name is not consistent, use first found, # must be only one - "args": next(Path(temp_dir).glob("*.tpl")).as_posix(), + "args": [ + next(Path(temp_dir).glob("*.tpl")).as_posix(), + override_name + ], } )["result"] @@ -48,7 +57,7 @@ def load(self, context, name=None, namespace=None, data=None): # We must validate the group_node return harmony.containerise( - name, + backdrop_name, namespace, backdrop_name, context, diff --git a/server/settings/load_plugins.py b/server/settings/load_plugins.py new file mode 100644 index 00000000..0f68fa62 --- /dev/null +++ b/server/settings/load_plugins.py @@ -0,0 +1,22 @@ +from ayon_server.settings import BaseSettingsModel, SettingsField + + +class TemplateLoaderPluginModel(BaseSettingsModel): + enabled: bool = SettingsField(True, title="Enabled") + override_name: str = SettingsField( + "", + title="Override container name", + description=( + "Optional container name to override the default name.\n" + "Keys are available in the context of the loader plugin.", + "Example keys: project[name], folder[name], product[name], version[name]..." + ), + placeholder="{folder[name]}_{product[name]}", + ) + + +class HarmonyLoadPlugins(BaseSettingsModel): + TemplateLoader: TemplateLoaderPluginModel = SettingsField( + default_factory=TemplateLoaderPluginModel, + title="Template Loader", + ) \ No newline at end of file diff --git a/server/settings/main.py b/server/settings/main.py index f90000b9..aabfbe8b 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -2,6 +2,7 @@ from .imageio import HarmonyImageIOModel from .creator_plugins import HarmonyCreatePlugins +from .load_plugins import HarmonyLoadPlugins from .publish_plugins import HarmonyPublishPlugins @@ -16,6 +17,10 @@ class HarmonySettings(BaseSettingsModel): default_factory=HarmonyCreatePlugins, title="Creator plugins" ) + load: HarmonyLoadPlugins = SettingsField( + default_factory=HarmonyLoadPlugins, + title="Loader plugins", + ) publish: HarmonyPublishPlugins = SettingsField( default_factory=HarmonyPublishPlugins, title="Publish plugins" @@ -84,6 +89,12 @@ class HarmonySettings(BaseSettingsModel): } } }, + "load": { + "TemplateLoader": { + "enabled": True, + "override_name": "" + } + }, "publish": { "CollectPalettes": { "allowed_tasks": [ From b49119bf61988798cad2500f8ad4b5b1117ac28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Tue, 23 Dec 2025 16:36:12 +0100 Subject: [PATCH 5/7] linting --- client/ayon_harmony/plugins/load/load_template.py | 2 +- server/settings/load_plugins.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_harmony/plugins/load/load_template.py b/client/ayon_harmony/plugins/load/load_template.py index 39c47758..9c5132d3 100644 --- a/client/ayon_harmony/plugins/load/load_template.py +++ b/client/ayon_harmony/plugins/load/load_template.py @@ -31,7 +31,7 @@ def load(self, context, name=None, namespace=None, data=None): self_name = self.__class__.__name__ temp_dir = tempfile.mkdtemp() zip_file = self.filepath_from_context(context) - + # Override container name override_name = "" if self.override_name: diff --git a/server/settings/load_plugins.py b/server/settings/load_plugins.py index 0f68fa62..7559d645 100644 --- a/server/settings/load_plugins.py +++ b/server/settings/load_plugins.py @@ -8,8 +8,8 @@ class TemplateLoaderPluginModel(BaseSettingsModel): title="Override container name", description=( "Optional container name to override the default name.\n" - "Keys are available in the context of the loader plugin.", - "Example keys: project[name], folder[name], product[name], version[name]..." + "Keys are available in the context of the loader plugin.\n" + "Example keys: project[name], folder[name], product[name]..." ), placeholder="{folder[name]}_{product[name]}", ) @@ -19,4 +19,4 @@ class HarmonyLoadPlugins(BaseSettingsModel): TemplateLoader: TemplateLoaderPluginModel = SettingsField( default_factory=TemplateLoaderPluginModel, title="Template Loader", - ) \ No newline at end of file + ) From b29ae6b6d9a2947f472cdb5f5a53441bb9683dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 9 Jan 2026 17:10:07 +0100 Subject: [PATCH 6/7] restoring anti duplicate name --- client/ayon_harmony/js/loaders/TemplateLoader.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/ayon_harmony/js/loaders/TemplateLoader.js b/client/ayon_harmony/js/loaders/TemplateLoader.js index 655b96ec..5b81673f 100644 --- a/client/ayon_harmony/js/loaders/TemplateLoader.js +++ b/client/ayon_harmony/js/loaders/TemplateLoader.js @@ -38,7 +38,6 @@ TemplateLoader.prototype.loadContainer = function(args) { // Copy from template file MessageLog.trace("loadContainer:: "); - var hasOverrideName = overrideName !== undefined && overrideName !== null && overrideName !== ""; /** * Parse a backdrop name into its base name and numeric suffix count. @@ -199,6 +198,9 @@ TemplateLoader.prototype.loadContainer = function(args) { } }); + // If overrideName is provided, use it instead of the main backdrop name + if (overrideName) { mainBackdropName = overrideName; } + var allBackdrops = Backdrop.backdrops("Top"); var backdropCounts = {}; for (var i = 0; i < allBackdrops.length; i++) { @@ -216,7 +218,7 @@ TemplateLoader.prototype.loadContainer = function(args) { var mainBackdropParsed = parseBackdropName(mainBackdropName); var count = backdropCounts[mainBackdropParsed.baseName] !== undefined ? backdropCounts[mainBackdropParsed.baseName] : 1; - if (!hasOverrideName && count > 1){ + if (count > 1){ // count -1 to match imported nodes which start from _1 mainBackdropName = mainBackdropName + "_" + (count - 1); @@ -225,12 +227,6 @@ TemplateLoader.prototype.loadContainer = function(args) { Backdrop.setBackdrops("Top", allBackdrops); } - if (hasOverrideName) { - mainBackdropName = overrideName; - allBackdrops[0].title.text = mainBackdropName; - Backdrop.setBackdrops("Top", allBackdrops); - } - return mainBackdropName; }; From 9d4b21aa6c230b7f319ae4e45c30ce8fcb887659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 5 Feb 2026 16:38:42 +0100 Subject: [PATCH 7/7] correct override with increment --- .../ayon_harmony/js/loaders/TemplateLoader.js | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/client/ayon_harmony/js/loaders/TemplateLoader.js b/client/ayon_harmony/js/loaders/TemplateLoader.js index 5b81673f..5f9e6ec8 100644 --- a/client/ayon_harmony/js/loaders/TemplateLoader.js +++ b/client/ayon_harmony/js/loaders/TemplateLoader.js @@ -182,26 +182,14 @@ TemplateLoader.prototype.loadContainer = function(args) { allBackdrops[idx].position.x += offsetX; allBackdrops[idx].position.y += offsetY; }); - Backdrop.setBackdrops("Top", allBackdrops); } - // Find main backdrop name - // The main backdrop is the one with the smallest x + y value (top left corner) - var selectedBackdrops = selection.selectedBackdrops(); - var mainBackdropName = selectedBackdrops[0].title.text; - var mainAnchorValue = selectedBackdrops[0].position.x + selectedBackdrops[0].position.y; - selectedBackdrops.slice(1).forEach(function(backdrop) { - var anchor = backdrop.position.x + backdrop.position.y; - if (mainAnchorValue > anchor) { - mainBackdropName = backdrop.title.text; - mainAnchorValue = anchor; - } - }); - - // If overrideName is provided, use it instead of the main backdrop name - if (overrideName) { mainBackdropName = overrideName; } + // Override name if provided + if (overrideName) { + allBackdrops[0].title.text = overrideName; + } - var allBackdrops = Backdrop.backdrops("Top"); + // Count existing backdrops by base name var backdropCounts = {}; for (var i = 0; i < allBackdrops.length; i++) { var parsed = parseBackdropName(allBackdrops[i].title.text); @@ -215,18 +203,21 @@ TemplateLoader.prototype.loadContainer = function(args) { } } + // Increment count of backdrop with the same base name + var mainBackdropName = allBackdrops[0].title.text; var mainBackdropParsed = parseBackdropName(mainBackdropName); var count = backdropCounts[mainBackdropParsed.baseName] !== undefined ? backdropCounts[mainBackdropParsed.baseName] : 1; - if (count > 1){ // count -1 to match imported nodes which start from _1 mainBackdropName = mainBackdropName + "_" + (count - 1); - - // new backdrop always at 0 - allBackdrops[0].title.text = mainBackdropName; - Backdrop.setBackdrops("Top", allBackdrops); } + // Set name of main backdrop (always at index 0) + allBackdrops[0].title.text = mainBackdropName; + + // Update backdrops in scene + Backdrop.setBackdrops("Top", allBackdrops); + return mainBackdropName; };