diff --git a/nodes/graph_utils.py b/nodes/graph_utils.py index 0060148..bc98497 100644 --- a/nodes/graph_utils.py +++ b/nodes/graph_utils.py @@ -19,6 +19,7 @@ from ..utils import ( LazyProxyTensor, apply_easing, get_server_info, + get_torch_tensor_info, numpy_NFOV, pil2tensor, tensor2np, @@ -135,12 +136,57 @@ class MTB_ApplyTextTemplate: CATEGORY = "mtb/utils" FUNCTION = "execute" - def execute(self, *, template: str, **kwargs): - res = f"{template}" - for k, v in kwargs.items(): - res = res.replace(f"{{{k}}}", f"{v}") + def execute(self, *, template: str, **kwargs) -> tuple[str | list[str]]: + keys = list(kwargs.keys()) + values = list(kwargs.values()) - return (res,) + has_list = any(isinstance(v, list) for v in values) + target_length = -1 + + if has_list: + first_list = next(x for x in values if isinstance(x, list)) + + target_length = len(first_list) + same_length = all( + len(v) == target_length for v in values if isinstance(v, list) + ) + if not same_length: + raise ValueError( + "Text template received multiple list[str] but their size is varying, they should match..." + ) + + if has_list: + results = [] + + for it in range(target_length): + res = f"{template}" + for k, v in kwargs.items(): + if isinstance(v, list): + res = self.apply_res(res, k, v[it]) + else: + res = self.apply_res(res, k, v) + results.append(res) + + return (results,) + + else: + res = f"{template}" + for k, v in kwargs.items(): + res = self.apply_res(res, k, v) + + return (res,) + + def apply_res(self, res, key, value): + if isinstance(value, float): + value = f"{value:.3f}" + elif isinstance(value, torch.Tensor): + value = get_torch_tensor_info(value) + else: + log.debug( + f"Falling back to default string conversion for {key} of type {type(value).__name__}" + ) + + return res.replace(f"{{{key}}}", f"{value}") class MTB_MatchDimensions: diff --git a/web/comfy_shared.js b/web/comfy_shared.js index bf1c60c..b2f03e8 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -431,7 +431,7 @@ const update_dynamic_properties = (node) => { * @param {NodeType} nodeType The nodetype to attach the documentation to * @param {str} prefix A prefix added to each dynamic inputs * @param {str | [str]} inputType The datatype(s) of those dynamic inputs - * @param {{separator?:string, start_index?:number, link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} [opts] Extra options + * @param {{separator?:string,rename_menu?:'label'|'name', start_index?:number, link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} [opts] Extra options * @returns */ export const setupDynamicConnections = ( @@ -445,31 +445,55 @@ export const setupDynamicConnections = ( Object.getOwnPropertyDescriptors(nodeType).title.value, ) - /** @type {{separator:string, start_index:number, link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} */ + /** @type {{separator:string,rename_menu?:"label"|"name" start_index:number, link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} */ const options = Object.assign( { separator: '_', start_index: 1, + rename_menu: 'label', }, opts || {}, ) - nodeType.prototype.getSlotMenuOptions = function (slot) { + const is_valid_name = (node, val) => { + return true + } + nodeType.prototype.getSlotMenuOptions = (slot) => { + if (!slot.input) { + return + } + infoLogger('Slot Menu', { slot }) return [ { - content: 'Rename Input', + content: `Rename Input (${options.rename_menu})`, callback: () => { - let dialog = app.canvas.createDialog( + const dialog = app.canvas.createDialog( "Name", {}, ) - let dialogInput = dialog.querySelector('input') + const dialogInput = dialog.querySelector('input') if (dialogInput) { - dialogInput.value = slot.input.label || slot.input.name || '' + if (options.rename_menu === 'label') { + dialogInput.value = slot.input.label || slot.input.name || '' + } else if (options.rename_menu === 'name') { + dialogInput.value = slot.input.name || '' + } } - let inner = () => { - app.graph.beforeChange() + const inner = () => { // TODO: check if name exists or other guards - slot.input.label = dialogInput.value + const val = dialogInput.value + if (!is_valid_name(slot.node, val)) { + dialog.close() + return + } + + app.graph.beforeChange() + if (options.rename_menu === 'label') { + slot.input.label = val + } else if (options.rename_menu === 'name') { + slot.input.name = val + slot.input.label = val + } + app.graph.afterChange() dialog.close() @@ -491,7 +515,6 @@ export const setupDynamicConnections = ( }, }, ] - } const onConfigure = nodeType.prototype.onConfigure diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index 2bc2f4d..9e6bbbb 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -1196,7 +1196,9 @@ const mtb_widgets = { //NOTE: dynamic nodes case 'Apply Text Template (mtb)': { - shared.setupDynamicConnections(nodeType, 'var', '*') + shared.setupDynamicConnections(nodeType, 'var', '*', { + rename_menu: 'name', + }) break } case 'Save Data Bundle (mtb)': {