diff --git a/__init__.py b/__init__.py index 5e5e098..7f8fee1 100644 --- a/__init__.py +++ b/__init__.py @@ -19,6 +19,7 @@ try: from .text_combiner import TextCombiner from .multiline_text_input import MultilineTextInput from .click_popup import ClickPopup + from .dynamic_image_input import DynamicImageCombiner # 节点映射 @@ -29,6 +30,7 @@ try: "TextCombiner": TextCombiner, "MultilineTextInput": MultilineTextInput, "ClickPopup": ClickPopup, + "DynamicImageCombiner": DynamicImageCombiner, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -37,7 +39,8 @@ try: "RemoveEmptyLinesAndLeadingSpaces": "Remove Empty Lines And Leading Spaces", "TextCombiner": "Text Combiner", "MultilineTextInput": "Multiline Text Input", - "ClickPopup": "Click Popup" + "ClickPopup": "Click Popup", + "DynamicImageCombiner": "Dynamic Image Combiner" } diff --git a/__pycache__/__init__.cpython-311.pyc b/__pycache__/__init__.cpython-311.pyc index 9059387..a627e8d 100644 Binary files a/__pycache__/__init__.cpython-311.pyc and b/__pycache__/__init__.cpython-311.pyc differ diff --git a/__pycache__/dynamic_image_input.cpython-311.pyc b/__pycache__/dynamic_image_input.cpython-311.pyc index 0bf5b53..ca1b7b3 100644 Binary files a/__pycache__/dynamic_image_input.cpython-311.pyc and b/__pycache__/dynamic_image_input.cpython-311.pyc differ diff --git a/__pycache__/text_combiner.cpython-311.pyc b/__pycache__/text_combiner.cpython-311.pyc index 0462ab7..8cb2e31 100644 Binary files a/__pycache__/text_combiner.cpython-311.pyc and b/__pycache__/text_combiner.cpython-311.pyc differ diff --git a/dynamic_image_input.py b/dynamic_image_input.py index 33fce3c..014e743 100644 --- a/dynamic_image_input.py +++ b/dynamic_image_input.py @@ -1,45 +1,53 @@ -class DynamicImageInputs: - """动态图片输入节点 - - 这个节点允许动态添加多个图片输入端口,并将所有输入的图片合并成一个列表输出。 - - 特点: - 1. 支持动态增减图片输入端口 - 2. 自动忽略未连接的输入端口 - 3. 输出一个包含所有输入图片的列表 - - 使用方法: - 1. 将节点添加到工作流中 - 2. 连接图片到输入端口 - 3. 使用 get_input 方法动态增加更多输入端口 - """ - +# -*- coding: utf-8 -*- +# Filename: dynamic_image_input.py +# Developer: jammyfu +# Category: 🎨Painting👓Coder/🖼️Image + +import torch + +class DynamicImageCombiner: @classmethod - def INPUT_TYPES(cls): - """定义节点的输入类型 - - Returns: - dict: 包含默认的单个图片输入端口 - """ - return {"required": {"image_1": ("IMAGE",)}} + def INPUT_TYPES(s): + return { + "required": {}, # 不需要默认输入 + "optional": {} + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + OUTPUT_IS_LIST = (True,) # 标记输出为列表 + FUNCTION = "combine_images" + CATEGORY = "🎨Painting👓Coder/🖼️Image" - RETURN_TYPES = ("IMAGE",) # 返回图片类型 - RETURN_NAMES = ("images",) # 输出端口名称 - OUTPUT_IS_LIST = (True,) # 标记输出为列表类型 - FUNCTION = "process_images" # 处理函数名称 - CATEGORY = "🎨Painting👓Coder/🖼️Image" # 节点分类 + def combine_images(self, **image_inputs): + try: + # 收集所有非空图像 + images = [] + for i in range(1, len(image_inputs) + 1): + key = f"image_{i}" + if key in image_inputs and image_inputs[key] is not None: + if isinstance(image_inputs[key], torch.Tensor): + images.append(image_inputs[key]) - def process_images(self, **kwargs): - """处理所有输入的图片 - - Args: - **kwargs: 包含所有输入图片的字典,键名格式为 "image_1", "image_2" 等 - - Returns: - tuple: 包含所有输入图片的列表 - """ - images = [] - for k, v in kwargs.items(): - if k.startswith("image_") and v is not None: - images.append(v) - return (images,) \ No newline at end of file + # 如果没有图像,返回一个包含空白图像的列表 + if not images: + empty_image = torch.zeros((1, 512, 512, 3)) + return ([empty_image],) + + # 直接返回图像列表 + return (images,) + + except Exception as e: + print(f"Error in DynamicImageCombiner: {str(e)}") + # 发生错误时返回包含空白图像的列表 + empty_image = torch.zeros((1, 512, 512, 3)) + return ([empty_image],) + +# 添加到 ComfyUI 节点注册 +NODE_CLASS_MAPPINGS = { + "DynamicImageCombiner": DynamicImageCombiner, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DynamicImageCombiner": "Dynamic Image Input 🖼️", +} \ No newline at end of file diff --git a/web/js/dynamic_image_input.js b/web/js/dynamic_image_input.js new file mode 100644 index 0000000..5d164fd --- /dev/null +++ b/web/js/dynamic_image_input.js @@ -0,0 +1,64 @@ +import { app } from "/scripts/app.js"; + +app.registerExtension({ + name: "Comfy.PaintingCoder.DynamicImageCombiner", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "DynamicImageCombiner") { + // 重写 onNodeCreated + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + // 初始化状态 + this.lastConnectedCount = 0; + + // 添加第一个输入点 + console.log("初始化:添加第一个图像输入点"); + this.addInput("image_1", "IMAGE"); + + return result; + }; + + // 添加连接监听 + nodeType.prototype.onConnectionsChange = function(connectionType, slot, isConnected) { + if (connectionType === 1) { // 输入连接 + console.log(`连接变化 - 槽位: ${slot}, 已连接: ${isConnected}`); + + // 计算已连接的输入点数量 + const connectedCount = this.inputs.reduce((count, input) => + count + (input.link ? 1 : 0), 0); + + console.log(`当前连接数量: ${connectedCount}, 上次连接数量: ${this.lastConnectedCount}`); + + // 只在连接数量发生变化时更新 + if (connectedCount !== this.lastConnectedCount) { + this.lastConnectedCount = connectedCount; + const targetCount = connectedCount + 1; + console.log(`目标输入点数量: ${targetCount}`); + + // 添加新的输入点 + while (this.inputs.length < targetCount) { + const newIndex = this.inputs.length + 1; + console.log(`添加新输入点: image_${newIndex}`); + this.addInput(`image_${newIndex}`, "IMAGE"); + } + + // 移除多余的输入点,但至少保留一个 + while (this.inputs.length > Math.max(targetCount, 1)) { + const lastInput = this.inputs[this.inputs.length - 1]; + if (!lastInput.link) { + console.log(`移除输入点: ${this.inputs.length}`); + this.removeInput(this.inputs.length - 1); + } + } + + // 更新节点大小 + console.log(`更新节点大小,当前输入点数量: ${this.inputs.length}`); + this.setSize(this.computeSize()); + app.graph.setDirtyCanvas(true); + } + } + }; + } + } +}); \ No newline at end of file