From eddaa3cf2a22f99a5bfdd5f57d3b0a7c1a48d8a5 Mon Sep 17 00:00:00 2001 From: FaberVS <54268603+FaberVS@users.noreply.github.com> Date: Tue, 15 Apr 2025 14:00:55 +0300 Subject: [PATCH] Add files via upload --- ModelParamsNode.py | 107 +++++++++++++++++++++++++++++++++++++++++++++ WF_nodes.py | 103 +++++++++++++++++++++++++++++++++++++++++++ __init__.py | 12 +++++ 3 files changed, 222 insertions(+) create mode 100644 ModelParamsNode.py create mode 100644 WF_nodes.py create mode 100644 __init__.py diff --git a/ModelParamsNode.py b/ModelParamsNode.py new file mode 100644 index 0000000..58a135c --- /dev/null +++ b/ModelParamsNode.py @@ -0,0 +1,107 @@ +import comfy.sd +import comfy.samplers # For accessing lists of SAMPLERS and SCHEDULERS +import folder_paths +import json +import inspect # For type verification + +# Define a custom type for "pipe" for better readability +PIPE_TYPE_NAME = "PARAMS_PIPE" + +# --- Node 1: ModelParamsPipeNode --- +class ModelParamsPipeNode: + @classmethod + def INPUT_TYPES(cls): + samplers = comfy.samplers.KSampler.SAMPLERS + schedulers = comfy.samplers.KSampler.SCHEDULERS + return { + "required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.1}), + "sampler_name": (samplers,), + "scheduler": (schedulers,), + "positive_tag": ("STRING", {"multiline": True, "default": ""}), + "negative_tag": ("STRING", {"multiline": True, "default": ""}), + "clip_skip": ("INT", {"default": -2, "min": -12, "max": 0, "step": 1}), + }, + "optional": { + "vae_override": ("VAE",), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE", + "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", + PIPE_TYPE_NAME) + RETURN_NAMES = ("MODEL", "CLIP", "VAE", + "steps", "cfg", "sampler_name", "scheduler", + "positive_tag", "negative_tag", + "params_pipe") + FUNCTION = "process_to_pipe" + CATEGORY = "MultiModel" + + def process_to_pipe(self, ckpt_name, steps, cfg, sampler_name, scheduler, + positive_tag, negative_tag, clip_skip, vae_override=None): + ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name) + out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) + model = out[0] + clip = out[1] + vae_from_checkpoint = out[2] + clip_skipped = clip.clone() + clip_skipped.clip_layer(clip_skip) + final_vae = vae_override if vae_override is not None else vae_from_checkpoint + combined_params = { + "ckpt_name": ckpt_name, "steps": steps, "cfg": cfg, + "sampler_name": str(sampler_name), "scheduler": str(scheduler), + "positive_tag": positive_tag.strip() if isinstance(positive_tag, str) else positive_tag, + "negative_tag": negative_tag.strip() if isinstance(negative_tag, str) else negative_tag, + "clip_skip": clip_skip + } + pipe_data = (model, clip_skipped, final_vae, combined_params) + return (model, clip_skipped, final_vae, steps, cfg, str(sampler_name), str(scheduler), + positive_tag, negative_tag, pipe_data) + +# --- Node 2: ParamsPipeUnpack --- +class ParamsPipeUnpack: + @classmethod + def INPUT_TYPES(cls): + return { "required": { "params_pipe": (PIPE_TYPE_NAME,), } } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE", "INT", "FLOAT", + comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, + "STRING", "STRING", PIPE_TYPE_NAME) + RETURN_NAMES = ("MODEL", "CLIP", "VAE", "steps", "cfg", + "sampler_name", "scheduler", + "positive_tag", "negative_tag", "params_pipe") + FUNCTION = "unpack_pipe" + CATEGORY = "MultiModel" + + def unpack_pipe(self, params_pipe): + if not isinstance(params_pipe, tuple) or len(params_pipe) != 4: + raise ValueError(f"Unpack: Expected PARAMS_PIPE tuple of length 4, but got: {type(params_pipe)}") + model, clip, vae, params_dict = params_pipe + if not isinstance(params_dict, dict): + raise ValueError(f"Unpack: Expected dict as the 4th element in PARAMS_PIPE, but got: {type(params_dict)}") + steps = params_dict.get("steps") + cfg = params_dict.get("cfg") + sampler_name = params_dict.get("sampler_name") + scheduler = params_dict.get("scheduler") + positive_tag = params_dict.get("positive_tag") + negative_tag = params_dict.get("negative_tag") + return (model, clip, vae, steps, cfg, sampler_name, scheduler, + positive_tag, negative_tag, params_pipe) + + +# --- Registration for only the first two nodes --- +NODE_CLASS_MAPPINGS = { + "ModelParamsPipeNode": ModelParamsPipeNode, + "ParamsPipeUnpack": ParamsPipeUnpack +} + +# --- Display names for only the first two nodes --- +NODE_DISPLAY_NAME_MAPPINGS = { + "ModelParamsPipeNode": "Model Parameters Pipe Hub", + "ParamsPipeUnpack": "Unpack Parameters Pipe" +} + +# Loading message +print("✅ Loaded custom nodes (Reverted): ModelParamsPipeNode, ParamsPipeUnpack ▪B ▪B ▪С") diff --git a/WF_nodes.py b/WF_nodes.py new file mode 100644 index 0000000..1f35695 --- /dev/null +++ b/WF_nodes.py @@ -0,0 +1,103 @@ +class ListSelectorNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + # Input for multiline text where each line is a list item + "list_items": ("STRING", {"multiline": True, "default": "Item 1\nItem 2\nItem 3"}), + # Input for selecting the item's number (1-based index) + "index": ("INT", {"default": 1, "min": 1, "max": 10000, "step": 1}), + } + } + + # Return types: selected string and its number + RETURN_TYPES = ("STRING", "INT") + # Output names + RETURN_NAMES = ("selected_item", "selected_index") + + FUNCTION = "get_item_by_index" + CATEGORY = "MultiModel" # Category for the node + + def get_item_by_index(self, list_items, index): + # Split the input text into lines + lines = list_items.splitlines() + + # Filter out empty lines and trim unnecessary spaces + items = [line.strip() for line in lines if line.strip()] + + selected_item_str = "" + selected_item_index = 1 # Default to the first item + + if not items: + # If the list is empty after filtering + print("Warning: ListSelectorNode - Input list is empty.") + selected_item_str = "" + selected_item_index = 1 # Nothing to select, return index 1 + else: + # Adjust the 1-based input index to 0-based for Python list access + zero_based_index = index - 1 + + # Check if the index is within bounds + # If index is too small (<0), use 0 + if zero_based_index < 0: + zero_based_index = 0 + # If index is too large, use the index of the last item + elif zero_based_index >= len(items): + zero_based_index = len(items) - 1 + + # Get the selected item + selected_item_str = items[zero_based_index] + # Determine the actual 1-based index of the selected item + selected_item_index = zero_based_index + 1 + + # Return the selected string and its 1-based index + return (selected_item_str, selected_item_index) + +class DenoiseSelector: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "denoise": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), + }} + RETURN_TYPES = ("FLOAT",) + RETURN_NAMES = ("denoise",) + FUNCTION = "select_denoise" + CATEGORY = "MultiModel" + + def select_denoise(self, denoise): + return (denoise,) + + +class DualTextInput: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + # Text field for the first text (visible on the node) + "text_positive": ("STRING", {"multiline": True, "default": ""}), + # Text field for the second text (visible on the node) + "text_negative": ("STRING", {"multiline": True, "default": ""}), + } + } + + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("pos", "neg") + FUNCTION = "process_text" + CATEGORY = "MultiModel" + + def process_text(self, text_positive, text_negative): + # Return values from the fields using correct variable names + return (text_positive, text_negative) + + +NODE_CLASS_MAPPINGS = { + "DenoiseSelector": DenoiseSelector, + "ListSelectorNode": ListSelectorNode, + "DualTextInput": DualTextInput +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DenoiseSelector": "Denoise Selector", + "ListSelectorNode": "Select_from_list_by_index", + "DualTextInput": "pos/neg text" +} diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..5710c80 --- /dev/null +++ b/__init__.py @@ -0,0 +1,12 @@ +from .WF_nodes import NODE_CLASS_MAPPINGS as DENOISE_NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as DENOISE_NODE_DISPLAY_NAME_MAPPINGS +from .ModelParamsNode import NODE_CLASS_MAPPINGS as MODEL_NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as MODEL_NODE_DISPLAY_NAME_MAPPINGS + + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} + +NODE_CLASS_MAPPINGS.update(DENOISE_NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(MODEL_NODE_CLASS_MAPPINGS) + +NODE_DISPLAY_NAME_MAPPINGS.update(DENOISE_NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(MODEL_NODE_DISPLAY_NAME_MAPPINGS) \ No newline at end of file