diff --git a/README.md b/README.md index bbd8357..abdcb1e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff * Save LoRA Block Weight: Save LBW_MODEL as a .lbw.safetensors file * Load LoRA Block Weight: Load LBW_MODEL from .lbw.safetensors file + ### SEGS Supports nodes - This is a node that supports ApplyControlNet (SEGS) from the Impact Pack. * `OpenPose Preprocessor Provider (SEGS)`: OpenPose preprocessor is applied for the purpose of using OpenPose ControlNet in SEGS. * You need to install [ControlNet Auxiliary Preprocessors](https://github.com/Fannovel16/comfyui_controlnet_aux) to use this. @@ -34,6 +35,7 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff `Color Preprocessor Provider (SEGS)`, `Inpaint Preprocessor Provider (SEGS)`, `Tile Preprocessor Provider (SEGS)`, `MeshGraphormer Depth Map Preprocessor Provider (SEGS)` * `MediaPipeFaceMeshDetectorProvider`: This node provides `BBOX_DETECTOR` and `SEGM_DETECTOR` that can be used in Impact Pack's Detector using the `MediaPipe-FaceMesh Preprocessor` of ControlNet Auxiliary Preprocessors. + ### A1111 Compatibility support - These nodes assists in replicating the creation of A1111 in ComfyUI exactly. * `KSampler (Inspire)`: ComfyUI uses the CPU for generating random noise, while A1111 uses the GPU. One of the three factors that significantly impact reproducing A1111's results in ComfyUI can be addressed using `KSampler (Inspire)`. * Other point #1 : Please make sure you haven't forgotten to include 'embedding:' in the embedding used in the prompt, like 'embedding:easynegative.' @@ -48,6 +50,7 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff * `variation_seed` and `variation_strength` - Initial noise generated by the seed is transformed to the shape of `variation_seed` by `variation_strength`. If `variation_strength` is 0, it only relies on the influence of the seed, and if `variation_strength` is 1.0, it is solely influenced by `variation_seed`. * These parameters are used when you want to maintain the composition of an image generated by the seed but wish to introduce slight changes. + ### Sampler nodes * `KSampler Progress (Inspire)` - In KSampler, the sampling process generates latent batches. By using `Video Combine` node from [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite), you can create a video from the progress. * `Scheduled CFGGuider (Inspire)` - This is a CFGGuider that adjusts the schedule from from_cfg to to_cfg using linear, log, and exp methods. @@ -154,8 +157,13 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff * `IPAdapter Model Helper (Inspire)`: This provides presets that allow for easy loading of the IPAdapter related models. However, it is essential for the model's name to be accurate. * You can download the appropriate model through ComfyUI-Manager. -### Util - Utilities +### List - Nodes for List processing * `Float Range (Inspire)`: Create a float list that increases the value by `step` from `start` to `stop`. A list as large as the maximum limit is created, and when `ensure_end` is enabled, the last value of the list becomes the stop value. + * `Worklist To Item List (Inspire)`: The list in ComfyUI allows for repeated execution of a sub-workflow. This groups these repetitions (a.k.a. list) into a single ITEM_LIST output. ITEM_LIST can then be used in ForeachList. + * `▶Foreach List (Inspire)`: A starting node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nGenerate a new intermediate_output using item and intermediate_output as inputs, then connect it to ForeachListEnd.\nNOTE:If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list. + * `Foreach List◀ (Inspire)`: A end node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nNOTE:Directly connect the outputs of ForeachListBegin to 'flow_control' and 'remained_list'. + +### Util - Utilities * `ToIPAdapterPipe (Inspire)`, `FromIPAdapterPipe (Inspire)`: These nodes assists in conveniently using the bundled ipadapter_model, clip_vision, and model required for applying IPAdapter. * `List Counter (Inspire)`: When each item in the list traverses through this node, it increments a counter by one, generating an integer value. * `RGB Hex To HSV (Inspire)`: Convert an RGB hex string like `#FFD500` to HSV: @@ -179,3 +187,5 @@ cubiq/[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) Davemane42/[ComfyUI_Dave_CustomNode](https://github.com/Davemane42/ComfyUI_Dave_CustomNode) - Original author of ConditioningStretch, ConditioningUpscale BlenderNeko/[ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) - slerp code for noise variation + +BadCafeCode/[execution-inversion-demo-comfyui](https://github.com/BadCafeCode/execution-inversion-demo-comfyui) - reference loop implementation for ComfyUI diff --git a/__init__.py b/__init__.py index 20c2f22..cb2848e 100644 --- a/__init__.py +++ b/__init__.py @@ -2,12 +2,12 @@ @author: Dr.Lt.Data @title: Inspire Pack @nickname: Inspire Pack -@description: This extension provides various nodes to support Lora Block Weight and the Impact Pack. +@description: This extension provides various nodes to support Lora Block Weight, Regional Nodes, Backend Cache, Prompt Utils, List Utils and the Impact Pack. """ import importlib -version_code = [1, 6, 1] +version_code = [1, 7] version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') print(f"### Loading: ComfyUI-Inspire-Pack ({version_str})") diff --git a/inspire/list_nodes.py b/inspire/list_nodes.py index 4c9f718..547c393 100644 --- a/inspire/list_nodes.py +++ b/inspire/list_nodes.py @@ -1,3 +1,6 @@ +from comfy_execution.graph_utils import GraphBuilder, is_link +from .libs.utils import any_typ + class FloatRange: @classmethod def INPUT_TYPES(s): @@ -15,7 +18,7 @@ def INPUT_TYPES(s): FUNCTION = "doit" - CATEGORY = "InspirePack/Util" + CATEGORY = "InspirePack/List" def doit(self, start, stop, step, limit, ensure_end): if start == stop or step == 0: @@ -47,10 +50,170 @@ def doit(self, start, stop, step, limit, ensure_end): return (res, ) +class WorklistToItemList: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "item": (any_typ, ), + } + } + + INPUT_IS_LIST = True + + RETURN_TYPES = ("ITEM_LIST",) + RETURN_NAMES = ("item_list",) + + FUNCTION = "doit" + + DESCRIPTION = "The list in ComfyUI allows for repeated execution of a sub-workflow.\nThis groups these repetitions (a.k.a. list) into a single ITEM_LIST output.\nITEM_LIST can then be used in ForeachList." + + CATEGORY = "InspirePack/List" + + def doit(self, item): + return (item, ) + + +# Loop nodes are implemented based on BadCafeCode's reference loop implementation +# https://github.com/BadCafeCode/execution-inversion-demo-comfyui/blob/main/flow_control.py + +class ForeachListBegin: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "item_list": ("ITEM_LIST", {"tooltip": "ITEM_LIST containing items to be processed iteratively."}), + }, + "optional": { + "initial_input": (any_typ, {"tooltip": "If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list."}), + } + } + + RETURN_TYPES = ("FOREACH_LIST_CONTROL", "ITEM_LIST", any_typ, any_typ) + RETURN_NAMES = ("flow_control", "remained_list", "item", "intermediate_output") + OUTPUT_TOOLTIPS = ( + "Pass ForeachListEnd as is to indicate the end of the iteration.", + "Output the ITEM_LIST containing the remaining items during the iteration, passing ForeachListEnd as is to indicate the end of the iteration.", + "Output the current item during the iteration.", + "Output the intermediate results during the iteration.") + + FUNCTION = "doit" + + DESCRIPTION = "A starting node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nGenerate a new intermediate_output using item and intermediate_output as inputs, then connect it to ForeachListEnd.\nNOTE:If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list." + + CATEGORY = "InspirePack/List" + + def doit(self, item_list, initial_input=None): + if initial_input is None: + initial_input = item_list[0] + item_list = item_list[1:] + + if len(item_list) > 0: + return ("stub", item_list[1:], item_list[0], initial_input) + + return ("stub", [], None, initial_input) + + +class ForeachListEnd: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "flow_control": ("FOREACH_LIST_CONTROL", {"rawLink": True, "tooltip": "Directly connect the output of ForeachListBegin, the starting node of the iteration."}), + "remained_list": ("ITEM_LIST", {"tooltip":"Directly connect the output of ForeachListBegin, the starting node of the iteration."}), + "intermediate_output": (any_typ, {"tooltip":"Connect the intermediate outputs processed within the iteration here."}), + }, + "hidden": { + "dynprompt": "DYNPROMPT", + "unique_id": "UNIQUE_ID", + } + } + + RETURN_TYPES = (any_typ,) + RETURN_NAMES = ("result",) + OUTPUT_TOOLTIPS = ("This is the final output value.",) + + FUNCTION = "doit" + + DESCRIPTION = "A end node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nNOTE:Directly connect the outputs of ForeachListBegin to 'flow_control' and 'remained_list'." + + CATEGORY = "InspirePack/List" + + def explore_dependencies(self, node_id, dynprompt, upstream): + node_info = dynprompt.get_node(node_id) + if "inputs" not in node_info: + return + for k, v in node_info["inputs"].items(): + if is_link(v): + parent_id = v[0] + if parent_id not in upstream: + upstream[parent_id] = [] + self.explore_dependencies(parent_id, dynprompt, upstream) + upstream[parent_id].append(node_id) + + def collect_contained(self, node_id, upstream, contained): + if node_id not in upstream: + return + for child_id in upstream[node_id]: + if child_id not in contained: + contained[child_id] = True + self.collect_contained(child_id, upstream, contained) + + def doit(self, flow_control, remained_list, intermediate_output, dynprompt, unique_id): + if len(remained_list) == 0: + return (intermediate_output,) + + # We want to loop + this_node = dynprompt.get_node(unique_id) + upstream = {} + + # Get the list of all nodes between the open and close nodes + self.explore_dependencies(unique_id, dynprompt, upstream) + + contained = {} + open_node = flow_control[0] + self.collect_contained(open_node, upstream, contained) + contained[unique_id] = True + contained[open_node] = True + + # We'll use the default prefix, but to avoid having node names grow exponentially in size, + # we'll use "Recurse" for the name of the recursively-generated copy of this node. + graph = GraphBuilder() + for node_id in contained: + original_node = dynprompt.get_node(node_id) + node = graph.node(original_node["class_type"], "Recurse" if node_id == unique_id else node_id) + node.set_override_display_id(node_id) + + for node_id in contained: + original_node = dynprompt.get_node(node_id) + node = graph.lookup_node("Recurse" if node_id == unique_id else node_id) + for k, v in original_node["inputs"].items(): + if is_link(v) and v[0] in contained: + parent = graph.lookup_node(v[0]) + node.set_input(k, parent.out(v[1])) + else: + node.set_input(k, v) + + new_open = graph.lookup_node(open_node) + new_open.set_input("item_list", remained_list) + new_open.set_input("initial_input", intermediate_output) + + my_clone = graph.lookup_node("Recurse" ) + result = (my_clone.out(0),) + + return { + "result": result, + "expand": graph.finalize(), + } + + NODE_CLASS_MAPPINGS = { "FloatRange //Inspire": FloatRange, + "WorklistToItemList //Inspire": WorklistToItemList, + "ForeachListBegin //Inspire": ForeachListBegin, + "ForeachListEnd //Inspire": ForeachListEnd, } NODE_DISPLAY_NAME_MAPPINGS = { - "FloatRange //Inspire": "Float Range (Inspire)" + "FloatRange //Inspire": "Float Range (Inspire)", + "WorklistToItemList //Inspire": "Worklist To Item List (Inspire)", + "ForeachListBegin //Inspire": "▶Foreach List (Inspire)", + "ForeachListEnd //Inspire": "Foreach List◀ (Inspire)", } diff --git a/pyproject.toml b/pyproject.toml index b3f1e5d..066056e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-inspire-pack" -description = "This extension provides various nodes to support Lora Block Weight and the Impact Pack. Provides many easily applicable regional features and applications for Variation Seed." -version = "1.6.1" +description = "This extension provides various nodes to support Lora Block Weight, Regional Nodes, Backend Cache, Prompt Utils, List Utils, Noise(Seed) Utils, ... and the Impact Pack." +version = "1.7" license = { file = "LICENSE" } dependencies = ["matplotlib", "cachetools"]