From 345ea8ed871a618f256f045e895f87edc952d548 Mon Sep 17 00:00:00 2001 From: Rocky-PC Date: Sun, 25 Jan 2026 03:59:50 +0800 Subject: [PATCH] Simple File Path Loop --- README.md | 72 +++++++++++++++- __pycache__/nodes.cpython-312.pyc | Bin 3080 -> 7286 bytes nodes.py | 134 +++++++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fde566..3088ac9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ComfyUI-FilePathsFromFolder -A simple ComfyUI custom node pack for loading and selecting files from folders. +A simple ComfyUI custom node pack for loading and selecting files from folders, with for-loop support for batch processing. ## Nodes @@ -30,6 +30,76 @@ Selects a specific file from the lists by index. - `filename`: Selected filename - `filepath`: Selected filepath +## Loop Nodes + +### FilePathsForLoop +Initiates iteration through files in a folder. Uses ComfyUI's list-based batching to process each file individually. + +**Parameters:** +- `folder_path`: Path to the folder to scan +- `extension`: File extension pattern (e.g., "*.png", "*.jpg", "*.txt") + +**Outputs:** +- `filepath`: Full path of current file (list output - each item processed separately) +- `filename`: Filename without extension (list output) +- `current_index`: Current iteration index, 0-based (list output) +- `total_count`: Total number of files (list output) + +**Important:** This node outputs lists with `OUTPUT_IS_LIST = True`. ComfyUI will automatically run all connected nodes once for each item in the list. Each connected node receives a **single value** from the list per execution, not the entire list. + +### FilePathsLoopClose +Closes the file paths loop and collects iteration results. + +**Inputs:** +- `current_index`: Current index from FilePathsForLoop (required, force input) +- `passthrough_1` through `passthrough_5`: Optional outputs from nodes in the loop + +**Outputs:** +- `total_iterations`: Total number of iterations completed + +**Usage:** This is the termination point of your loop. Connect the `current_index` from FilePathsForLoop (or through other nodes) to ensure the loop completes. You can also connect outputs from processing nodes to the passthrough inputs to collect results. + +### FilePathsEnsureExecution +Ensures connected nodes execute during loop iteration by collecting their outputs. + +**Inputs:** +- `input_1` through `input_10`: Optional inputs (supports any type with wildcard `*`) + +**Outputs:** +- `passthrough`: The first non-None input value + +**Usage:** Connect outputs from any nodes that need to execute within the loop to this node's inputs. Then connect this node's output downstream (e.g., to FilePathsLoopClose) to ensure all connected nodes complete execution. + +## How ComfyUI List-Based Loops Work + +When a node has `OUTPUT_IS_LIST = True`: +1. The node returns lists: `["file1.png", "file2.png", "file3.png"]` +2. ComfyUI automatically executes **all downstream nodes once per list item** +3. Each execution receives **one item from the list**, not the whole list +4. **Iteration 1**: downstream nodes receive `"file1.png"` +5. **Iteration 2**: downstream nodes receive `"file2.png"` +6. **Iteration 3**: downstream nodes receive `"file3.png"` + +This is ComfyUI's native batching/looping mechanism. + +## Example Workflows + +### Simple Loop Processing +1. **FilePathsForLoop** - Set folder path and extension (e.g., "C:/images", "*.png") +2. Connect `filepath` to your image loader node (e.g., Load Image) +3. Connect your processing nodes (e.g., filters, effects) +4. Connect the final processing output to **FilePathsEnsureExecution** inputs +5. Connect **FilePathsForLoop**'s `current_index` to **FilePathsLoopClose**'s `current_index` +6. (Optional) Connect **FilePathsEnsureExecution**'s output to **FilePathsLoopClose**'s `passthrough_1` + +This setup will automatically process each file in the folder sequentially. + +### Why Three Nodes? + +- **FilePathsForLoop**: Initiates the loop, provides file paths and metadata +- **FilePathsEnsureExecution**: Optional helper to chain dependencies and ensure execution order +- **FilePathsLoopClose**: Terminates the loop, provides completion signal, collects results + ## Installation Place this folder in your ComfyUI `custom_nodes` directory and restart ComfyUI. diff --git a/__pycache__/nodes.cpython-312.pyc b/__pycache__/nodes.cpython-312.pyc index 0199d9fdfc1570033b3c26668f131e46b9544b77..deeff2f1c46706020cca796f0c0d110ad881d000 100644 GIT binary patch literal 7286 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 EKZ{bbv;Y7A diff --git a/nodes.py b/nodes.py index 5caf79f..3fdfcee 100644 --- a/nodes.py +++ b/nodes.py @@ -2,6 +2,133 @@ import os import glob +class FilePathsForLoop: + """ + Iterates through files in a folder one at a time. + Returns each file path individually for processing in a loop. + Connect to any node that processes files, then to FilePathsLoopClose. + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "folder_path": ("STRING", {"default": ""}), + "extension": ("STRING", {"default": "*.png"}), + } + } + + RETURN_TYPES = ("STRING", "STRING", "INT", "INT") + RETURN_NAMES = ("filepath", "filename", "current_index", "total_count") + FUNCTION = "iterate_files" + CATEGORY = "FilePathsFromFolder/Loop" + OUTPUT_NODE = False + OUTPUT_IS_LIST = (True, True, True, True) + + def iterate_files(self, folder_path, extension): + # Build search pattern + search_pattern = os.path.join(folder_path, extension) + + # Find all matching files + matched_files = glob.glob(search_pattern) + matched_files.sort() + + total_count = len(matched_files) + + # If no files, return empty lists + if not matched_files: + return ([], [], [], []) + + # Build output lists + filepaths = [] + filenames = [] + indices = [] + counts = [] + + for idx, filepath in enumerate(matched_files): + basename = os.path.basename(filepath) + filename_without_ext = os.path.splitext(basename)[0] + + filepaths.append(filepath) + filenames.append(filename_without_ext) + indices.append(idx) + counts.append(total_count) + + # Return as lists - OUTPUT_IS_LIST tells ComfyUI this is a batch + return (filepaths, filenames, indices, counts) + + +class FilePathsLoopClose: + """ + Closes the file paths loop. + Connect any outputs you want to collect from the loop iterations. + This ensures all iterations complete before proceeding. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "current_index": ("INT", {"default": 0, "forceInput": True}), + }, + "optional": { + "passthrough_1": ("*",), + "passthrough_2": ("*",), + "passthrough_3": ("*",), + "passthrough_4": ("*",), + "passthrough_5": ("*",), + } + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("total_iterations",) + FUNCTION = "close_loop" + CATEGORY = "FilePathsFromFolder/Loop" + OUTPUT_NODE = True + + def close_loop(self, current_index, **kwargs): + # Count total iterations + total = len(current_index) if isinstance(current_index, list) else 1 + return (total,) + + +class FilePathsEnsureExecution: + """ + Ensures connected nodes execute by passing through their outputs. + Use this to chain multiple nodes that need to execute during each loop iteration. + Connect up to 10 node outputs, then connect this node's output to FilePathsLoopClose. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "input_1": ("*",), + "input_2": ("*",), + "input_3": ("*",), + "input_4": ("*",), + "input_5": ("*",), + "input_6": ("*",), + "input_7": ("*",), + "input_8": ("*",), + "input_9": ("*",), + "input_10": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("passthrough",) + FUNCTION = "passthrough" + CATEGORY = "FilePathsFromFolder/Loop" + OUTPUT_NODE = False + + def passthrough(self, **kwargs): + # Return the first non-None input as passthrough + for key in sorted(kwargs.keys()): + if kwargs[key] is not None: + return (kwargs[key],) + return (None,) + + class LoadFilePathsFromFolder: @classmethod def INPUT_TYPES(cls): @@ -75,10 +202,15 @@ class GetPathByIndex: NODE_CLASS_MAPPINGS = { "LoadFilePathsFromFolder": LoadFilePathsFromFolder, "GetPathByIndex": GetPathByIndex, + "FilePathsForLoop": FilePathsForLoop, + "FilePathsLoopClose": FilePathsLoopClose, + "FilePathsEnsureExecution": FilePathsEnsureExecution, } NODE_DISPLAY_NAME_MAPPINGS = { "LoadFilePathsFromFolder": "Load File Paths From Folder", "GetPathByIndex": "Get Path By Index", + "FilePathsForLoop": "File Paths For Loop", + "FilePathsLoopClose": "File Paths Loop Close", + "FilePathsEnsureExecution": "File Paths Ensure Execution", } -