Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c6065af6d | ||
|
|
8db9411e94 | ||
|
|
18f02d98b1 | ||
|
|
cab499e30a | ||
|
|
71a68650fe | ||
|
|
3bcc1d43a0 | ||
|
|
395869085c | ||
|
|
384284fe0b | ||
|
|
c6d53ecd4d | ||
|
|
44cfac4f0a | ||
|
|
bfaaa6c570 | ||
|
|
d29c41809f | ||
|
|
667dead8c1 | ||
|
|
08cdc7358b | ||
|
|
81485bcf4c | ||
|
|
f6a8c65094 | ||
|
|
37bc51713f | ||
|
|
004a9534e4 | ||
|
|
ca710caaab | ||
|
|
c657cf152a | ||
|
|
9678d685c2 | ||
|
|
51cace6f1c | ||
|
|
c0d54d8a88 | ||
|
|
74b2856499 | ||
|
|
25ae8d522f |
@@ -13,12 +13,17 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
## Nodes
|
||||
### Lora Block Weight - This is a node that provides functionality related to Lora block weight.
|
||||
* This provides similar functionality to [sd-webui-lora-block-weight](https://github.com/hako-mikan/sd-webui-lora-block-weight)
|
||||
* `Lora Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* `LoRA Loader (Block Weight)`: When loading Lora, the block weight vector is applied.
|
||||
* In the block vector, you can use numbers, R, A, a, B, and b.
|
||||
* R is determined sequentially based on a random seed, while A and B represent the values of the A and B parameters, respectively. a and b are half of the values of A and B, respectively.
|
||||
* `XY Input: Lora Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* `XY Input: LoRA Block Weight`: This is a node in the [Efficiency Nodes](https://github.com/LucianoCirino/efficiency-nodes-comfyui)' XY Plot that allows you to use Lora block weight.
|
||||
* You must ensure that X and Y connections are made, and dependencies should be connected to the XY Plot.
|
||||
* Note: To use this feature, update `Efficient Nodes` to a version released after September 3rd.
|
||||
* Make LoRA Block Weight: Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form
|
||||
* Apply LoRA Block Weight: Apply LBW_MODEL to MODEL and CLIP
|
||||
* 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.
|
||||
@@ -30,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.'
|
||||
@@ -44,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.
|
||||
@@ -57,7 +64,9 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* e.g. `prompts/example`
|
||||
* `Load Prompts From File (Inspire)`: It sequentially reads prompts from the specified file. The output it returns is ZIPPED_PROMPT.
|
||||
* Specify the file located under `ComfyUI-Inspire-Pack/prompts/`
|
||||
* e.g. `prompts/example/prompt2.txt`
|
||||
* e.g. `prompts/example/prompt2.txt`
|
||||
* `Load Single Prompt From File (Inspire)`: Loads a single prompt from a file containing multiple prompts by using an index.
|
||||
* The prompts file directory can be specified as `inspire_prompts` in `extra_model_paths.yaml`
|
||||
* `Unzip Prompt (Inspire)`: Separate ZIPPED_PROMPT into `positive`, `negative`, and name components.
|
||||
* `positive` and `negative` represent text prompts, while `name` represents the name of the prompt. When loaded from a file using `Load Prompts From File (Inspire)`, the name corresponds to the file name.
|
||||
* `Zip Prompt (Inspire)`: Create ZIPPED_PROMPT from positive, negative, and name_opt.
|
||||
@@ -138,6 +147,7 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
|
||||
* When `key_opt` is empty, the `ckpt_name` is set as the cache key. The cache key output can be used for deletion purposes with Remove Back End.
|
||||
* This node resolves the issue of reloading checkpoints during workflow switching.
|
||||
* `Stable Cascade Checkpoint Loader (Inspire)`: This node provides a feature that allows you to load the `stage_b` and `stage_c` checkpoints of Stable Cascade at once, and it also provides a backend caching feature, optionally.
|
||||
* `Is Cached (Inspire)`: Returns whether the cache exists.
|
||||
|
||||
### Conditioning - Nodes for conditionings
|
||||
* `Concat Conditionings with Multiplier (Inspire)`: Concatenating an arbitrary number of Conditionings while applying a multiplier for each Conditioning. The multiplier depends on `comfy_PoP`, so [comfy_PoP](https://github.com/picturesonpictures/comfy_PoP) must be installed.
|
||||
@@ -148,8 +158,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:
|
||||
@@ -173,3 +188,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
|
||||
|
||||
+2
-2
@@ -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, 0]
|
||||
version_code = [1, 9]
|
||||
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})")
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
from .libs import common
|
||||
|
||||
import folder_paths
|
||||
import nodes
|
||||
@@ -478,6 +479,74 @@ class StableCascade_CheckpointLoader:
|
||||
return b_model, b_vae, c_model, c_vae, clip_vision, clip, key_b, key_c
|
||||
|
||||
|
||||
class IsCached:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"key": ("STRING", {"multiline": False}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(key, unique_id):
|
||||
return common.is_changed(unique_id, key in cache)
|
||||
|
||||
def doit(self, key, unique_id):
|
||||
return (key in cache,)
|
||||
|
||||
|
||||
# WIP: not properly working, yet
|
||||
class CacheBridge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"value": (any_typ,),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_off": "cached", "label_on": "passthrough"}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ, )
|
||||
RETURN_NAMES = ("value",)
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Backend"
|
||||
|
||||
@staticmethod
|
||||
def IS_CHANGED(value, mode, unique_id):
|
||||
if not mode and unique_id in common.changed_cache:
|
||||
return common.not_changed_value(unique_id)
|
||||
else:
|
||||
return common.changed_value(unique_id)
|
||||
|
||||
def doit(self, value, mode, unique_id):
|
||||
if not mode:
|
||||
# cache mode
|
||||
if unique_id not in common.changed_cache:
|
||||
common.changed_cache[unique_id] = value
|
||||
common.changed_count_cache[unique_id] = 0
|
||||
|
||||
return (common.changed_cache[unique_id],)
|
||||
else:
|
||||
common.changed_cache[unique_id] = value
|
||||
common.changed_count_cache[unique_id] = 0
|
||||
|
||||
return (common.changed_cache[unique_id],)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CacheBackendData //Inspire": CacheBackendData,
|
||||
"CacheBackendDataNumberKey //Inspire": CacheBackendDataNumberKey,
|
||||
@@ -489,7 +558,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"RemoveBackendDataNumberKey //Inspire": RemoveBackendDataNumberKey,
|
||||
"ShowCachedInfo //Inspire": ShowCachedInfo,
|
||||
"CheckpointLoaderSimpleShared //Inspire": CheckpointLoaderSimpleShared,
|
||||
"StableCascade_CheckpointLoader //Inspire": StableCascade_CheckpointLoader
|
||||
"StableCascade_CheckpointLoader //Inspire": StableCascade_CheckpointLoader,
|
||||
"IsCached //Inspire": IsCached,
|
||||
# "CacheBridge //Inspire": CacheBridge,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -503,5 +574,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RemoveBackendDataNumberKey //Inspire": "Remove Backend Data [NumberKey] (Inspire)",
|
||||
"ShowCachedInfo //Inspire": "Show Cached Info (Inspire)",
|
||||
"CheckpointLoaderSimpleShared //Inspire": "Shared Checkpoint Loader (Inspire)",
|
||||
"StableCascade_CheckpointLoader //Inspire": "Stable Cascade Checkpoint Loader (Inspire)"
|
||||
"StableCascade_CheckpointLoader //Inspire": "Stable Cascade Checkpoint Loader (Inspire)",
|
||||
"IsCached //Inspire": "Is Cached (Inspire)",
|
||||
# "CacheBridge //Inspire": "Cache Bridge (Inspire)"
|
||||
}
|
||||
|
||||
@@ -8,19 +8,24 @@ from nodes import MAX_RESOLUTION
|
||||
class ConcatConditioningsWithMultiplier:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
flex_inputs = {}
|
||||
|
||||
stack = inspect.stack()
|
||||
if stack[1].function == 'get_input_data':
|
||||
if stack[1].function == 'get_input_info':
|
||||
# bypass validation
|
||||
for x in range(0, 100):
|
||||
flex_inputs[f"multiplier{x}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
else:
|
||||
flex_inputs["multiplier1"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01})
|
||||
class AllContainer:
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
return "FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": AllContainer()
|
||||
}
|
||||
|
||||
return {
|
||||
"required": {"conditioning1": ("CONDITIONING",), },
|
||||
"optional": flex_inputs
|
||||
"optional": {"multiplier1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), },
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
|
||||
@@ -6,6 +6,7 @@ from enum import Enum
|
||||
from . import prompt_support
|
||||
from aiohttp import web
|
||||
from . import backend_support
|
||||
from .libs import common
|
||||
|
||||
|
||||
max_seed = 2**32 - 1
|
||||
@@ -332,7 +333,7 @@ def populate_wildcards(json_data):
|
||||
|
||||
if 'extra_data' in json_data and 'extra_pnginfo' in json_data['extra_data']:
|
||||
extra_pnginfo = json_data['extra_data']['extra_pnginfo']
|
||||
if 'workflow' in extra_pnginfo and 'nodes' in extra_pnginfo['workflow']:
|
||||
if 'workflow' in extra_pnginfo and extra_pnginfo['workflow'] is not None and 'nodes' in extra_pnginfo['workflow']:
|
||||
for node in extra_pnginfo['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
@@ -354,6 +355,21 @@ def force_reset_useless_params(json_data):
|
||||
return json_data
|
||||
|
||||
|
||||
def clear_unused_node_changed_cache(json_data):
|
||||
prompt = json_data['prompt']
|
||||
|
||||
unused = []
|
||||
for x in common.changed_cache.keys():
|
||||
if x not in prompt:
|
||||
unused.append(x)
|
||||
|
||||
for x in unused:
|
||||
del common.changed_cache[x]
|
||||
del common.changed_count_cache[x]
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
def onprompt(json_data):
|
||||
prompt_support.list_counter_map = {}
|
||||
|
||||
@@ -367,6 +383,7 @@ def onprompt(json_data):
|
||||
populate_wildcards(json_data)
|
||||
|
||||
force_reset_useless_params(json_data)
|
||||
clear_unused_node_changed_cache(json_data)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
@@ -12,3 +12,30 @@ def impact_sampling(*args, **kwargs):
|
||||
raise Exception(f"[ERROR] You need to install 'ComfyUI-Impact-Pack'")
|
||||
|
||||
return nodes.NODE_CLASS_MAPPINGS['RegionalSampler'].separated_sample(*args, **kwargs)
|
||||
|
||||
|
||||
changed_count_cache = {}
|
||||
changed_cache = {}
|
||||
|
||||
|
||||
def changed_value(uid):
|
||||
v = changed_count_cache.get(uid, 0)
|
||||
changed_count_cache[uid] = v + 1
|
||||
return v + 1
|
||||
|
||||
|
||||
def not_changed_value(uid):
|
||||
return changed_count_cache.get(uid, 0)
|
||||
|
||||
|
||||
def is_changed(uid, value):
|
||||
if uid not in changed_cache or changed_cache[uid] != value:
|
||||
res = changed_value(uid)
|
||||
else:
|
||||
res = not_changed_value(uid)
|
||||
|
||||
changed_cache[uid] = value
|
||||
|
||||
print(f"keys: {changed_cache.keys()}")
|
||||
|
||||
return res
|
||||
|
||||
@@ -5,6 +5,7 @@ import torch
|
||||
from PIL import Image, ImageDraw
|
||||
import math
|
||||
import cv2
|
||||
import folder_paths
|
||||
|
||||
|
||||
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'):
|
||||
@@ -336,3 +337,14 @@ def flatten_non_zero_override(masks: torch.Tensor):
|
||||
final_mask[non_zero_mask] = masks[i][non_zero_mask]
|
||||
|
||||
return final_mask
|
||||
|
||||
|
||||
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
|
||||
for full_folder_path in full_folder_paths:
|
||||
folder_paths.add_model_folder_path(folder_name, full_folder_path)
|
||||
if folder_name in folder_paths.folder_names_and_paths:
|
||||
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
|
||||
updated_extensions = current_extensions | extensions
|
||||
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
|
||||
else:
|
||||
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
|
||||
|
||||
+165
-2
@@ -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 @@ class FloatRange:
|
||||
|
||||
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 @@ class FloatRange:
|
||||
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)",
|
||||
}
|
||||
|
||||
+333
-39
@@ -1,5 +1,3 @@
|
||||
import regex
|
||||
|
||||
import folder_paths
|
||||
import comfy.utils
|
||||
import comfy.lora
|
||||
@@ -8,11 +6,20 @@ import torch
|
||||
import numpy as np
|
||||
import nodes
|
||||
import re
|
||||
import json
|
||||
from comfy.cli_args import args
|
||||
from safetensors.torch import safe_open
|
||||
import ast
|
||||
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils
|
||||
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
utils.add_folder_path_and_extensions("lbw_models", [os.path.join(model_path, "lbw_models")], {'.safetensors'})
|
||||
|
||||
|
||||
def is_numeric_string(input_str):
|
||||
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
|
||||
|
||||
@@ -43,6 +50,69 @@ def parse_unet_num(s):
|
||||
return int(s)
|
||||
|
||||
|
||||
class MakeLBW:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
preset = ["Preset"] # 20
|
||||
preset += load_lbw_preset("lbw-preset.txt")
|
||||
preset += load_lbw_preset("lbw-preset.custom.txt")
|
||||
preset = [name for name in preset if not name.startswith('@')]
|
||||
|
||||
lora_names = folder_paths.get_filename_list("loras")
|
||||
lora_dirs = [os.path.dirname(name) for name in lora_names]
|
||||
lora_dirs = ["All"] + list(set(lora_dirs))
|
||||
|
||||
return {"required": {"model": ("MODEL",),
|
||||
"clip": ("CLIP", ),
|
||||
"category_filter": (lora_dirs,),
|
||||
"lora_name": (lora_names, ),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False", "tooltip": "Apply the following weights for each block:\nTrue: 1 - weight\nFalse: weight"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": ""}),
|
||||
"A": ("FLOAT", {"default": 4.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"B": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"preset": (preset,),
|
||||
"block_vector": ("STRING", {"multiline": True, "placeholder": "block weight vectors", "default": "1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1", "pysssss.autocomplete": False}),
|
||||
"bypass": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL", "STRING")
|
||||
RETURN_NAMES = ("lbw_model", "populated_vector")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Instead of directly applying the LoRA Block Weight to the MODEL, it is generated in a separate LBW_MODEL form."
|
||||
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
|
||||
def doit(self, model, clip, lora_name, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lora = None
|
||||
if self.loaded_lora is not None:
|
||||
if self.loaded_lora[0] == lora_path:
|
||||
lora = self.loaded_lora[1]
|
||||
else:
|
||||
temp = self.loaded_lora
|
||||
self.loaded_lora = None
|
||||
del temp
|
||||
|
||||
if lora is None:
|
||||
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
lbw_model = {
|
||||
'blocks': block_weights,
|
||||
'muted': muted_weights
|
||||
}
|
||||
return lbw_model, populated_vector
|
||||
|
||||
|
||||
class LoraLoaderBlockWeight:
|
||||
def __init__(self):
|
||||
self.loaded_lora = None
|
||||
@@ -276,7 +346,7 @@ class LoraLoaderBlockWeight:
|
||||
return ",".join(res)
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
def load_lbw(model, clip, lora, inverse, seed, A, B, block_vector):
|
||||
key_map = comfy.lora.model_lora_keys_unet(model.model)
|
||||
key_map = comfy.lora.model_lora_keys_clip(clip.cond_stage_model, key_map)
|
||||
loaded = comfy.lora.load_lora(lora, key_map)
|
||||
@@ -290,7 +360,6 @@ class LoraLoaderBlockWeight:
|
||||
block_vector = block_vector[0]
|
||||
|
||||
vector = block_vector.split(",")
|
||||
vector_i = 1
|
||||
|
||||
if not LoraLoaderBlockWeight.validate(vector):
|
||||
preset_dict = load_preset_dict()
|
||||
@@ -299,9 +368,6 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
raise ValueError(f"[LoraLoaderBlockWeight] invalid block_vector '{block_vector}'")
|
||||
|
||||
last_k_unet_num = None
|
||||
new_modelpatcher = model.clone()
|
||||
|
||||
# sort: input, middle, output, others
|
||||
input_blocks = []
|
||||
middle_blocks = []
|
||||
@@ -346,6 +412,12 @@ class LoraLoaderBlockWeight:
|
||||
populated_vector_list = []
|
||||
ratios = []
|
||||
ratio = 1.0
|
||||
vector_i = 1
|
||||
|
||||
last_k_unet_num = None
|
||||
|
||||
block_weights = {}
|
||||
muted_weights = []
|
||||
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
if last_k_unet_num != k_unet_num and len(vector) > vector_i:
|
||||
@@ -374,12 +446,9 @@ class LoraLoaderBlockWeight:
|
||||
last_k_unet_num = k_unet_num
|
||||
|
||||
if populated_ratio != 0:
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
|
||||
# prepare base patch
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
|
||||
@@ -392,19 +461,34 @@ class LoraLoaderBlockWeight:
|
||||
|
||||
populated_vector_list.insert(0, LoraLoaderBlockWeight.norm_value(populated_ratio))
|
||||
|
||||
new_clip = clip.clone()
|
||||
for k, v, k_unet in others:
|
||||
if 'text' in k_unet:
|
||||
new_clip.add_patches({k: v}, strength_clip * populated_ratio)
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
|
||||
|
||||
# if inverse:
|
||||
# print(f"\t{k_unet} -> inv({ratio}) ")
|
||||
# else:
|
||||
# print(f"\t{k_unet} -> ({ratio}) ")
|
||||
muted_weights.append(k)
|
||||
|
||||
populated_vector = ','.join(map(str, populated_vector_list))
|
||||
return block_weights, muted_weights, populated_vector
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
block_weights, muted_weights, populated_vector = LoraLoaderBlockWeight.load_lbw(model, clip, lora, inverse, seed, A, B, block_vector)
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k or 'encoder' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip, populated_vector
|
||||
|
||||
def doit(self, model, clip, lora_name, strength_model, strength_clip, inverse, seed, A, B, preset, block_vector, bypass=False, category_filter=None):
|
||||
@@ -429,6 +513,47 @@ class LoraLoaderBlockWeight:
|
||||
return model_lora, clip_lora, populated_vector
|
||||
|
||||
|
||||
class ApplyLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"clip": ("CLIP", ),
|
||||
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
"lbw_model": ("LBW_MODEL",),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Apply LBW_MODEL to MODEL and CLIP"
|
||||
|
||||
@staticmethod
|
||||
def doit(model, clip, strength_model, strength_clip, lbw_model):
|
||||
block_weights = lbw_model['blocks']
|
||||
muted_weights = lbw_model['muted']
|
||||
|
||||
new_modelpatcher = model.clone()
|
||||
new_clip = clip.clone()
|
||||
|
||||
muted_weights = set(muted_weights)
|
||||
|
||||
for k, v in block_weights.items():
|
||||
weights, ratio = v
|
||||
|
||||
if k in muted_weights:
|
||||
pass
|
||||
elif 'text' in k or 'encoder' in k:
|
||||
new_clip.add_patches({k: weights}, strength_clip * ratio)
|
||||
else:
|
||||
new_modelpatcher.add_patches({k: weights}, strength_model * ratio)
|
||||
|
||||
return new_modelpatcher, new_clip
|
||||
|
||||
|
||||
class XY_Capsule_LoraBlockWeight:
|
||||
def __init__(self, x, y, target_vector, label, storage, params):
|
||||
self.x = x
|
||||
@@ -493,6 +618,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
else:
|
||||
image = torch.abs(weighted_image - reference_image)
|
||||
self.storage[(self.another_capsule.x, self.y)] = image
|
||||
|
||||
elif self.y == 3:
|
||||
import matplotlib.cm as cm
|
||||
# heatmap
|
||||
@@ -501,7 +627,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
if image == "fail":
|
||||
image = utils.empty_pil_tensor(8,8)
|
||||
latent = utils.empty_latent()
|
||||
return (image, latent)
|
||||
return image, latent
|
||||
else:
|
||||
image = image.clone()
|
||||
|
||||
@@ -533,7 +659,7 @@ class XY_Capsule_LoraBlockWeight:
|
||||
image = heatmap_alpha * heatmap + (1 - heatmap_alpha) * image
|
||||
|
||||
latent = nodes.VAEEncode().encode(vae, image)[0]
|
||||
return (image, latent)
|
||||
return image, latent
|
||||
|
||||
def getLabel(self):
|
||||
return self.label
|
||||
@@ -696,9 +822,13 @@ class LoraBlockInfo:
|
||||
output_blocks = []
|
||||
output_blocks_map = {}
|
||||
|
||||
text_block_count = set()
|
||||
text_blocks = []
|
||||
text_blocks_map = {}
|
||||
text_block_count1 = set()
|
||||
text_blocks1 = []
|
||||
text_blocks_map1 = {}
|
||||
|
||||
text_block_count2 = set()
|
||||
text_blocks2 = []
|
||||
text_blocks_map2 = {}
|
||||
|
||||
double_block_count = set()
|
||||
double_blocks = []
|
||||
@@ -776,12 +906,23 @@ class LoraBlockInfo:
|
||||
k_unet_num = k_unet[len("er.text_model.encoder.layers."):len("er.text_model.encoder.layers.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
text_block_count.add(k_unet_int)
|
||||
text_blocks.append(k_unet)
|
||||
if k_unet_int in text_blocks_map:
|
||||
text_blocks_map[k_unet_int].append(k_unet)
|
||||
text_block_count1.add(k_unet_int)
|
||||
text_blocks1.append(k_unet)
|
||||
if k_unet_int in text_blocks_map1:
|
||||
text_blocks_map1[k_unet_int].append(k_unet)
|
||||
else:
|
||||
text_blocks_map[k_unet_int] = [k_unet]
|
||||
text_blocks_map1[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("r.encoder.block."):
|
||||
k_unet_num = k_unet[len("r.encoder.block."):len("r.encoder.block.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
text_block_count2.add(k_unet_int)
|
||||
text_blocks2.append(k_unet)
|
||||
if k_unet_int in text_blocks_map2:
|
||||
text_blocks_map2[k_unet_int].append(k_unet)
|
||||
else:
|
||||
text_blocks_map2[k_unet_int] = [k_unet]
|
||||
|
||||
else:
|
||||
others.append(k_unet)
|
||||
@@ -825,10 +966,14 @@ class LoraBlockInfo:
|
||||
for x in single_keys:
|
||||
text += f" SINGLE{x}: {len(single_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Base blocks] ({len(text_block_count) + len(others)}, Subs={len(text_blocks) + len(others)})-------\n"
|
||||
text_keys = sorted(text_blocks_map.keys())
|
||||
for x in text_keys:
|
||||
text += f" TXT_ENC{x}: {len(text_blocks_map[x])}\n"
|
||||
text += f"\n-------[Base blocks] ({len(text_block_count1) + len(text_block_count2) + len(others)}, Subs={len(text_blocks1) + len(text_blocks2) + len(others)})-------\n"
|
||||
text_keys1 = sorted(text_blocks_map1.keys())
|
||||
for x in text_keys1:
|
||||
text += f" TXT_ENC{x}: {len(text_blocks_map1[x])}\n"
|
||||
|
||||
text_keys2 = sorted(text_blocks_map2.keys())
|
||||
for x in text_keys2:
|
||||
text += f" TXT_ENC{x} [B]: {len(text_blocks_map2[x])}\n"
|
||||
|
||||
for x in others:
|
||||
text += f" {x}\n"
|
||||
@@ -845,13 +990,162 @@ class LoraBlockInfo:
|
||||
return {}
|
||||
|
||||
|
||||
class LoadLBW:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
files = folder_paths.get_filename_list('lbw_models')
|
||||
return {"required": {
|
||||
"lbw_model": [sorted(files), ]},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LBW_MODEL",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Load LBW_MODEL from .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def decode_dict(encoded_dict, tensor_dict):
|
||||
original_dict = {}
|
||||
|
||||
def decode_value(value):
|
||||
if isinstance(value, str) and value.startswith('t') and value[1:].isdigit():
|
||||
return tensor_dict[value]
|
||||
return value
|
||||
|
||||
for k, tuple_value in encoded_dict.items():
|
||||
decoded_tuple = tuple(decode_value(v) for v in tuple_value[0][1])
|
||||
key = ast.literal_eval(k) if isinstance(k, str) and (k.startswith('(') or k.startswith('[')) else k
|
||||
original_dict[key] = ((tuple_value[0][0], decoded_tuple), tuple_value[1])
|
||||
|
||||
return original_dict
|
||||
|
||||
@staticmethod
|
||||
def load(file):
|
||||
tensor_dict = comfy.utils.load_torch_file(file)
|
||||
|
||||
with safe_open(file, framework="pt") as f:
|
||||
metadata = f.metadata()
|
||||
|
||||
encoded_dict = json.loads(metadata.get('blocks', '{}'))
|
||||
muted_blocks = ast.literal_eval(metadata.get('muted_blocks', '[]'))
|
||||
|
||||
decoded_dict = LoadLBW.decode_dict(encoded_dict, tensor_dict)
|
||||
|
||||
lbw_model = {
|
||||
'blocks': decoded_dict,
|
||||
'muted': muted_blocks
|
||||
}
|
||||
|
||||
return lbw_model, metadata
|
||||
|
||||
def doit(self, lbw_model):
|
||||
lbw_path = folder_paths.get_full_path("lbw_models", lbw_model)
|
||||
lbw_model, _ = LoadLBW.load(lbw_path)
|
||||
return (lbw_model,)
|
||||
|
||||
|
||||
class SaveLBW:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_folder_paths('lbw_models')[-1]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "lbw_model": ("LBW_MODEL", ),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}) },
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "doit"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "InspirePack/LoraBlockWeight"
|
||||
|
||||
DESCRIPTION = "Save LBW_MODEL as a .lbw.safetensors file"
|
||||
|
||||
@staticmethod
|
||||
def encode_dict(original_dict):
|
||||
tensor_dict = {}
|
||||
encoded_dict = {}
|
||||
counter = 0
|
||||
|
||||
def generate_unique_id():
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return f"t{counter}"
|
||||
|
||||
def encode_value(value):
|
||||
if isinstance(value, torch.Tensor):
|
||||
unique_id = generate_unique_id()
|
||||
tensor_dict[unique_id] = value
|
||||
return unique_id
|
||||
return value
|
||||
|
||||
for k, tuple_value in original_dict.items():
|
||||
encoded_tuple = tuple(encode_value(v) for v in tuple_value[0][1])
|
||||
encoded_dict[str(k)] = (tuple_value[0][0], encoded_tuple), tuple_value[1]
|
||||
|
||||
return encoded_dict, tensor_dict
|
||||
|
||||
@staticmethod
|
||||
def save(lbw_model, file, metadata):
|
||||
metadata['format'] = 'Inspire LBW 1.0'
|
||||
weighted_blocks = lbw_model['blocks']
|
||||
metadata['muted_blocks'] = str(lbw_model['muted'])
|
||||
encoded_dict, tensor_dict = SaveLBW.encode_dict(weighted_blocks)
|
||||
metadata['blocks'] = json.dumps(encoded_dict)
|
||||
|
||||
comfy.utils.save_torch_file(tensor_dict, file, metadata=metadata)
|
||||
|
||||
def doit(self, lbw_model, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir)
|
||||
|
||||
# support save metadata for lbw sharing
|
||||
prompt_info = ""
|
||||
if prompt is not None:
|
||||
prompt_info = json.dumps(prompt)
|
||||
|
||||
metadata = {}
|
||||
if not args.disable_metadata:
|
||||
metadata = {"prompt": prompt_info}
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata[x] = json.dumps(extra_pnginfo[x])
|
||||
|
||||
file = f"{filename}_{counter:05}_.lbw.safetensors"
|
||||
|
||||
results = list()
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": "output"
|
||||
})
|
||||
|
||||
file = os.path.join(full_output_folder, file)
|
||||
|
||||
SaveLBW.save(lbw_model, file, metadata)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": XYInput_LoraBlockWeight,
|
||||
"LoraLoaderBlockWeight //Inspire": LoraLoaderBlockWeight,
|
||||
"LoraBlockInfo //Inspire": LoraBlockInfo,
|
||||
"MakeLBW //Inspire": MakeLBW,
|
||||
"ApplyLBW //Inspire": ApplyLBW,
|
||||
"SaveLBW //Inspire": SaveLBW,
|
||||
"LoadLBW //Inspire": LoadLBW,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: Lora Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "Lora Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "Lora Block Info",
|
||||
"XY Input: Lora Block Weight //Inspire": "XY Input: LoRA Block Weight",
|
||||
"LoraLoaderBlockWeight //Inspire": "LoRA Loader (Block Weight)",
|
||||
"LoraBlockInfo //Inspire": "LoRA Block Info",
|
||||
"MakeLBW //Inspire": "Make LoRA Block Weight",
|
||||
"ApplyLBW //Inspire": "Apply LoRA Block Weight",
|
||||
"SaveLBW //Inspire": "Save LoRA Block Weight",
|
||||
"LoadLBW //Inspire": "Load LoRA Block Weight",
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ model_preset = {
|
||||
"SDXL ViT-H": ("ip-adapter_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus ViT-H": ("ip-adapter-plus_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"SDXL Plus Face ViT-H": ("ip-adapter-plus-face_sdxl_vit-h", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
"Kolors Plus": ("Kolors-IP-Adapter-Plus", "clip-vit-large-patch14-336", None, False),
|
||||
|
||||
# faceid
|
||||
"SD1.5 FaceID": ("ip-adapter-faceid_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sd15_lora", True),
|
||||
@@ -29,6 +30,7 @@ model_preset = {
|
||||
"SDXL FaceID": ("ip-adapter-faceid_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", "ip-adapter-faceid_sdxl_lora", True),
|
||||
"SDXL FaceID Portrait": ("ip-adapter-faceid-portrait_sdxl", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"SDXL FaceID Portrait unnorm": ("ip-adapter-faceid-portrait_sdxl_unnorm", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, True),
|
||||
"Kolors FaceID Plus": ("Kolors-IP-Adapter-FaceID-Plus", "clip-vit-large-patch14-336", None, True),
|
||||
|
||||
# composition
|
||||
"SD1.5 Plus Composition": ("ip-adapter_sd15", "CLIP-ViT-H-14-laion2B-s32B-b79K", None, False),
|
||||
@@ -56,7 +58,6 @@ class IPAdapterModelHelper:
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"preset": (list(model_preset.keys()),),
|
||||
"lora_strength_model": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
"lora_strength_clip": ("FLOAT", {"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01}),
|
||||
@@ -64,6 +65,7 @@ class IPAdapterModelHelper:
|
||||
"cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"}),
|
||||
},
|
||||
"optional": {
|
||||
"clip": ("CLIP",),
|
||||
"insightface_model_name": (['buffalo_l', 'antelopev2'],),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"}
|
||||
@@ -75,14 +77,17 @@ class IPAdapterModelHelper:
|
||||
|
||||
CATEGORY = "InspirePack/models"
|
||||
|
||||
def doit(self, model, clip, preset, lora_strength_model, lora_strength_clip, insightface_provider, cache_mode="none", unique_id=None, insightface_model_name='buffalo_l'):
|
||||
def doit(self, model, preset, lora_strength_model, lora_strength_clip, insightface_provider, clip=None, cache_mode="none", unique_id=None, insightface_model_name='buffalo_l'):
|
||||
if 'IPAdapter' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'IPAdapterModelHelper' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception(f"[ERROR] To use IPAdapterModelHelper, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
is_sdxl_preset = 'SDXL' in preset
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
if clip is not None:
|
||||
is_sdxl_model = isinstance(clip.tokenizer, sdxl_clip.SDXLTokenizer)
|
||||
else:
|
||||
is_sdxl_model = False
|
||||
|
||||
if is_sdxl_preset != is_sdxl_model:
|
||||
server.PromptServer.instance.send_sync("inspire-node-output-label", {"node_id": unique_id, "output_idx": 1, "label": "IPADAPTER (fail)"})
|
||||
|
||||
+154
-42
@@ -12,20 +12,23 @@ import folder_paths
|
||||
import comfy
|
||||
import traceback
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from server import PromptServer
|
||||
from .libs import utils, common
|
||||
from .backend_support import CheckpointLoaderSimpleShared
|
||||
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
utils.add_folder_path_and_extensions("inspire_prompts", [os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "prompts"))], {'.txt'})
|
||||
|
||||
|
||||
prompt_builder_preset = {}
|
||||
|
||||
|
||||
resource_path = os.path.join(os.path.dirname(__file__), "..", "resources")
|
||||
resource_path = os.path.abspath(resource_path)
|
||||
|
||||
prompts_path = os.path.join(os.path.dirname(__file__), "..", "prompts")
|
||||
prompts_path = os.path.abspath(prompts_path)
|
||||
|
||||
|
||||
try:
|
||||
pb_yaml_path = os.path.join(resource_path, 'prompt-builder.yaml')
|
||||
@@ -37,19 +40,28 @@ try:
|
||||
with open(pb_yaml_path, 'r', encoding="utf-8") as f:
|
||||
prompt_builder_preset = yaml.load(f, Loader=yaml.FullLoader)
|
||||
except Exception as e:
|
||||
print(f"[Inspire Pack] Failed to load 'prompt-builder.yaml'")
|
||||
print(f"[Inspire Pack] Failed to load 'prompt-builder.yaml'\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
|
||||
class LoadPromptsFromDir:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
try:
|
||||
prompt_dirs = [d for d in os.listdir(prompts_path) if os.path.isdir(os.path.join(prompts_path, d))]
|
||||
prompt_dirs = []
|
||||
for x in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
for d in os.listdir(x):
|
||||
if os.path.isdir(os.path.join(x, d)):
|
||||
prompt_dirs.append(d)
|
||||
except Exception:
|
||||
prompt_dirs = []
|
||||
|
||||
return {"required": {"prompt_dir": (prompt_dirs,)}}
|
||||
return {"required": {
|
||||
"prompt_dir": (prompt_dirs,)
|
||||
},
|
||||
"optional": {
|
||||
"reload": ("BOOLEAN", { "default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -59,17 +71,56 @@ class LoadPromptsFromDir:
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_dir):
|
||||
global prompts_path
|
||||
prompt_dir = os.path.join(prompts_path, prompt_dir)
|
||||
files = [f for f in os.listdir(prompt_dir) if f.endswith(".txt")]
|
||||
files.sort()
|
||||
def IS_CHANGED(prompt_dir, reload=False):
|
||||
if not reload:
|
||||
return prompt_dir
|
||||
else:
|
||||
candidates = []
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
candidates.append(os.path.join(d, prompt_dir))
|
||||
|
||||
prompt_files = []
|
||||
for x in candidates:
|
||||
for root, dirs, files in os.walk(x):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
prompt_files.append(os.path.join(root, file))
|
||||
|
||||
prompt_files.sort()
|
||||
|
||||
md5 = hashlib.md5()
|
||||
|
||||
for file_name in prompt_files:
|
||||
md5.update(file_name.encode('utf-8'))
|
||||
with open(folder_paths.get_full_path('inspire_prompts', file_name), 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_dir, reload=False):
|
||||
candidates = []
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
candidates.append(os.path.join(d, prompt_dir))
|
||||
|
||||
prompt_files = []
|
||||
for x in candidates:
|
||||
for root, dirs, files in os.walk(x):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
prompt_files.append(os.path.join(root, file))
|
||||
|
||||
prompt_files.sort()
|
||||
|
||||
prompts = []
|
||||
for file_name in files:
|
||||
for file_name in prompt_files:
|
||||
print(f"file_name: {file_name}")
|
||||
try:
|
||||
with open(os.path.join(prompt_dir, file_name), "r", encoding="utf-8") as file:
|
||||
with open(file_name, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
@@ -85,7 +136,7 @@ class LoadPromptsFromDir:
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromDir: invalid prompt format in '{file_name}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}")
|
||||
print(f"[ERROR] LoadPromptsFromDir: an error occurred while processing '{file_name}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -93,20 +144,27 @@ class LoadPromptsFromDir:
|
||||
class LoadPromptsFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
prompt_files = []
|
||||
try:
|
||||
prompt_files = []
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for prompts_path in prompts_paths:
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
return {"required": {"prompt_file": (prompt_files,)},
|
||||
"optional": {"text_data_opt": ("STRING", {"defaultInput": True})}}
|
||||
return {"required": {
|
||||
"prompt_file": (prompt_files,)
|
||||
},
|
||||
"optional": {
|
||||
"text_data_opt": ("STRING", {"defaultInput": True}),
|
||||
"reload": ("BOOLEAN", {"default": False, "label_on": "if file changed", "label_off": "if value changed"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -116,13 +174,54 @@ class LoadPromptsFromFile:
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, text_data_opt=None):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
def IS_CHANGED(prompt_file, text_data_opt=None, reload=False):
|
||||
md5 = hashlib.md5()
|
||||
|
||||
if text_data_opt is not None:
|
||||
md5.update(text_data_opt)
|
||||
return md5.hexdigest()
|
||||
elif not reload:
|
||||
return prompt_file
|
||||
else:
|
||||
matched_path = None
|
||||
for x in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
matched_path = os.path.join(x, prompt_file)
|
||||
if not os.path.exists(matched_path):
|
||||
matched_path = None
|
||||
else:
|
||||
break
|
||||
|
||||
if matched_path is None:
|
||||
return float('NaN')
|
||||
|
||||
with open(matched_path, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
md5.update(chunk)
|
||||
|
||||
return md5.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, text_data_opt=None, reload=False):
|
||||
matched_path = None
|
||||
for d in folder_paths.get_folder_paths('inspire_prompts'):
|
||||
matched_path = os.path.join(d, prompt_file)
|
||||
if os.path.exists(matched_path):
|
||||
break
|
||||
else:
|
||||
matched_path = None
|
||||
|
||||
if matched_path:
|
||||
print(f"[INFO] LoadPromptsFromFile: file found '{prompt_file}'")
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: file not found '{prompt_file}'")
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
if not text_data_opt:
|
||||
with open(prompt_path, "r", encoding="utf-8") as file:
|
||||
with open(matched_path, "r", encoding="utf-8") as file:
|
||||
prompt_data = file.read()
|
||||
else:
|
||||
prompt_data = text_data_opt
|
||||
@@ -131,8 +230,8 @@ class LoadPromptsFromFile:
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
|
||||
for prompt in prompt_list:
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
for p in prompt_list:
|
||||
matches = re.search(pattern, p, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
@@ -142,7 +241,7 @@ class LoadPromptsFromFile:
|
||||
else:
|
||||
print(f"[WARN] LoadPromptsFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
print(f"[ERROR] LoadPromptsFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -150,15 +249,16 @@ class LoadPromptsFromFile:
|
||||
class LoadSinglePromptFromFile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
global prompts_path
|
||||
prompt_files = []
|
||||
try:
|
||||
prompt_files = []
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for prompts_path in prompts_paths:
|
||||
for root, dirs, files in os.walk(prompts_path):
|
||||
for file in files:
|
||||
if file.endswith(".txt"):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, prompts_path)
|
||||
prompt_files.append(rel_path)
|
||||
except Exception:
|
||||
prompt_files = []
|
||||
|
||||
@@ -178,7 +278,19 @@ class LoadSinglePromptFromFile:
|
||||
|
||||
@staticmethod
|
||||
def doit(prompt_file, index, text_data_opt=None):
|
||||
prompt_path = os.path.join(prompts_path, prompt_file)
|
||||
prompt_path = None
|
||||
prompts_paths = folder_paths.get_folder_paths('inspire_prompts')
|
||||
for d in prompts_paths:
|
||||
prompt_path = os.path.join(d, prompt_file)
|
||||
if os.path.exists(prompt_path):
|
||||
break
|
||||
else:
|
||||
prompt_path = None
|
||||
|
||||
if prompt_path:
|
||||
print(f"[INFO] LoadSinglePromptFromFile: file found '{prompt_file}'")
|
||||
else:
|
||||
print(f"[WARN] LoadSinglePromptFromFile: file not found '{prompt_file}'")
|
||||
|
||||
prompts = []
|
||||
try:
|
||||
@@ -205,7 +317,7 @@ class LoadSinglePromptFromFile:
|
||||
else:
|
||||
print(f"[WARN] LoadSinglePromptFromFile: invalid prompt format in '{prompt_file}'")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] LoadSinglePromptFromFile: an error occurred while processing '{prompt_file}': {str(e)}")
|
||||
print(f"[ERROR] LoadSinglePromptFromFile: an error occurred while processing '{prompt_file}': {str(e)}\nNOTE: Only files with UTF-8 encoding are supported.")
|
||||
|
||||
return (prompts, )
|
||||
|
||||
@@ -566,7 +678,7 @@ class SeedExplorer:
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"model": ("model",),
|
||||
"model": ("MODEL",),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import comfy
|
||||
import nodes
|
||||
import torch
|
||||
import re
|
||||
import webcolors
|
||||
|
||||
from . import prompt_support
|
||||
from .libs import utils, common
|
||||
@@ -81,8 +82,9 @@ class RegionalPromptSimple:
|
||||
|
||||
def color_to_mask(color_mask, mask_color):
|
||||
try:
|
||||
if mask_color.startswith("#"):
|
||||
selected = int(mask_color[1:], 16)
|
||||
if mask_color.startswith("#") or mask_color.isalpha():
|
||||
hex = mask_color[1:] if mask_color.startswith("#") else webcolors.name_to_hex(mask_color)[1:]
|
||||
selected = int(hex, 16)
|
||||
else:
|
||||
selected = int(mask_color, 10)
|
||||
except Exception:
|
||||
|
||||
@@ -44,7 +44,7 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
result = [comfy.sample.fix_empty_latent_channels(model, latent_image['samples']).cpu()]
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
|
||||
+21
-4
@@ -110,6 +110,9 @@ class Color_Preprocessor_wrapper:
|
||||
|
||||
|
||||
class InpaintPreprocessor_wrapper:
|
||||
def __init__(self, black_pixel_for_xinsir_cn):
|
||||
self.black_pixel_for_xinsir_cn = black_pixel_for_xinsir_cn
|
||||
|
||||
def apply(self, image, mask=None):
|
||||
if 'InpaintPreprocessor' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node('https://github.com/Fannovel16/comfyui_controlnet_aux',
|
||||
@@ -120,7 +123,16 @@ class InpaintPreprocessor_wrapper:
|
||||
if mask is None:
|
||||
mask = torch.ones((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu").unsqueeze(0)
|
||||
|
||||
return obj.preprocess(image, mask)[0]
|
||||
try:
|
||||
res = obj.preprocess(image, mask, black_pixel_for_xinsir_cn=self.black_pixel_for_xinsir_cn)[0]
|
||||
except Exception as e:
|
||||
if self.black_pixel_for_xinsir_cn:
|
||||
raise e
|
||||
else:
|
||||
res = obj.preprocess(image, mask)[0]
|
||||
print(f"[Inspire Pack] Installed 'ComfyUI's ControlNet Auxiliary Preprocessors.' is outdated.")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class TilePreprocessor_wrapper:
|
||||
@@ -547,14 +559,19 @@ class Color_Preprocessor_Provider_for_SEGS:
|
||||
class InpaintPreprocessor_Provider_for_SEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"black_pixel_for_xinsir_cn": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SEGS_PREPROCESSOR",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/SEGS/ControlNet"
|
||||
|
||||
def doit(self):
|
||||
obj = InpaintPreprocessor_wrapper()
|
||||
def doit(self, black_pixel_for_xinsir_cn=False):
|
||||
obj = InpaintPreprocessor_wrapper(black_pixel_for_xinsir_cn)
|
||||
return (obj, )
|
||||
|
||||
|
||||
|
||||
+91
-87
@@ -4,7 +4,7 @@ app.registerExtension({
|
||||
name: "Comfy.Inspire.LBW",
|
||||
|
||||
nodeCreated(node, app) {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire") {
|
||||
if(node.comfyClass == "LoraLoaderBlockWeight //Inspire" || node.comfyClass == "MakeLBW //Inspire") {
|
||||
// category filter
|
||||
const lora_names_widget = node.widgets[node.widgets.findIndex(obj => obj.name === 'lora_name')];
|
||||
var full_lora_list = lora_names_widget.options.values;
|
||||
@@ -26,21 +26,25 @@ app.registerExtension({
|
||||
// vector selector
|
||||
let preset_i = 9;
|
||||
let vector_i = 10;
|
||||
|
||||
if(node.comfyClass == "MakeLBW //Inspire") {
|
||||
preset_i = 7;
|
||||
vector_i = 8;
|
||||
}
|
||||
|
||||
node._value = "Preset";
|
||||
|
||||
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
|
||||
node.widgets[vector_i].value = node._value.split(':')[1];
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Preset") {
|
||||
node.widgets[vector_i].value = value.split(':')[1];
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node._value = value;
|
||||
if(value != "Preset")
|
||||
node._value = value;
|
||||
},
|
||||
get: () => {
|
||||
return node._value;
|
||||
@@ -71,86 +75,86 @@ app.registerExtension({
|
||||
let preset_i = 9;
|
||||
let vector_i = 10;
|
||||
node._value = "Preset";
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Preset") {
|
||||
if(!value.startsWith('@') && node.widgets[vector_i].value != "")
|
||||
node.widgets[vector_i].value += "\n";
|
||||
if(value.startsWith('@')) {
|
||||
let spec = value.split(':')[1];
|
||||
var n;
|
||||
var sub_n = null;
|
||||
var block = null;
|
||||
|
||||
if(isNaN(spec)) {
|
||||
let sub_spec = spec.split(',');
|
||||
node.widgets[preset_i].callback = (v, canvas, node, pos, e) => {
|
||||
let value = node._value;
|
||||
if(!value.startsWith('@') && node.widgets[vector_i].value != "")
|
||||
node.widgets[vector_i].value += "\n";
|
||||
if(value.startsWith('@')) {
|
||||
let spec = value.split(':')[1];
|
||||
var n;
|
||||
var sub_n = null;
|
||||
var block = null;
|
||||
|
||||
if(sub_spec.length != 3) {
|
||||
node.widgets_values[vector_i] = '!! SPEC ERROR !!';
|
||||
node._value = '';
|
||||
return;
|
||||
}
|
||||
if(isNaN(spec)) {
|
||||
let sub_spec = spec.split(',');
|
||||
|
||||
n = parseInt(sub_spec[0].trim());
|
||||
sub_n = parseInt(sub_spec[1].trim());
|
||||
block = parseInt(sub_spec[2].trim());
|
||||
}
|
||||
else {
|
||||
n = parseInt(spec.trim());
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value = "";
|
||||
if(sub_n == null) {
|
||||
for(let i=1; i<=n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
if(j==i)
|
||||
temp += 'A';
|
||||
else
|
||||
temp += '0';
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(let i=1; i<=sub_n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
|
||||
if(block!=j)
|
||||
temp += '0';
|
||||
else {
|
||||
temp += ' ';
|
||||
for(let k=1; k<=sub_n; k++) {
|
||||
if(k==i)
|
||||
temp += 'A ';
|
||||
else
|
||||
temp += '0 ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${block}.SUB${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.widgets[vector_i].value += `${value}/${value.split(':')[0]}`;
|
||||
}
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
if(sub_spec.length != 3) {
|
||||
node.widgets_values[vector_i] = '!! SPEC ERROR !!';
|
||||
node._value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
node._value = value;
|
||||
n = parseInt(sub_spec[0].trim());
|
||||
sub_n = parseInt(sub_spec[1].trim());
|
||||
block = parseInt(sub_spec[2].trim());
|
||||
}
|
||||
else {
|
||||
n = parseInt(spec.trim());
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value = "";
|
||||
if(sub_n == null) {
|
||||
for(let i=1; i<=n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
if(j==i)
|
||||
temp += 'A';
|
||||
else
|
||||
temp += '0';
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(let i=1; i<=sub_n; i++) {
|
||||
var temp = "";
|
||||
for(let j=1; j<=n; j++) {
|
||||
if(temp!='')
|
||||
temp += ',';
|
||||
|
||||
if(block!=j)
|
||||
temp += '0';
|
||||
else {
|
||||
temp += ' ';
|
||||
for(let k=1; k<=sub_n; k++) {
|
||||
if(k==i)
|
||||
temp += 'A ';
|
||||
else
|
||||
temp += '0 ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.widgets[vector_i].value += `B${block}.SUB${i}:${temp}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.widgets[vector_i].value += `${value}/${value.split(':')[0]}`;
|
||||
}
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[vector_i] = node.widgets[preset_i].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[preset_i], "value", {
|
||||
set: (value) => {
|
||||
if(value != 'Preset')
|
||||
node._value = value;
|
||||
},
|
||||
get: () => {
|
||||
return node._value;
|
||||
|
||||
+75
-75
@@ -42,35 +42,38 @@ app.registerExtension({
|
||||
// lora selector, wildcard selector
|
||||
let combo_id = 5;
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the LoRA to add to the text") {
|
||||
let lora_name = value;
|
||||
if (lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
// lora
|
||||
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
|
||||
let lora_name = node._value;
|
||||
if(lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the LoRA to add to the text")
|
||||
node._value = value;
|
||||
},
|
||||
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
|
||||
// wildcard
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(wildcard_text_widget.value != '')
|
||||
wildcard_text_widget.value += ', '
|
||||
|
||||
wildcard_text_widget.value += node._wildcard_value;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the Wildcard to add to the text") {
|
||||
if(wildcard_text_widget.value != '')
|
||||
wildcard_text_widget.value += ', '
|
||||
|
||||
wildcard_text_widget.value += value;
|
||||
}
|
||||
}
|
||||
},
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
|
||||
@@ -115,47 +118,46 @@ app.registerExtension({
|
||||
// lora selector, wildcard selector
|
||||
let combo_id = 5;
|
||||
|
||||
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
|
||||
let lora_name = node._lora_value;
|
||||
if (lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
if(direction_widget.value) {
|
||||
pos_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
else {
|
||||
neg_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the LoRA to add to the text") {
|
||||
let lora_name = value;
|
||||
if (lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
if(direction_widget.value) {
|
||||
pos_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
else {
|
||||
neg_wildcard_text_widget.value += `<lora:${lora_name}>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value !== "Select the LoRA to add to the text")
|
||||
node._lora_value = value;
|
||||
},
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
});
|
||||
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
let w = null;
|
||||
if(direction_widget.value) {
|
||||
w = pos_wildcard_text_widget;
|
||||
}
|
||||
else {
|
||||
w = neg_wildcard_text_widget;
|
||||
}
|
||||
|
||||
if(w.value != '')
|
||||
w.value += ', '
|
||||
|
||||
w.value += node._wildcard_value;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the Wildcard to add to the text") {
|
||||
let w = null;
|
||||
if(direction_widget.value) {
|
||||
w = pos_wildcard_text_widget;
|
||||
}
|
||||
else {
|
||||
w = neg_wildcard_text_widget;
|
||||
}
|
||||
|
||||
if(w.value != '')
|
||||
w.value += ', '
|
||||
|
||||
w.value += value;
|
||||
}
|
||||
}
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
@@ -205,24 +207,22 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
preset_widget.callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[2].value) {
|
||||
node.widgets[2].value += ', ';
|
||||
}
|
||||
|
||||
const y = node._preset_value.split(':');
|
||||
if(y.length == 2)
|
||||
node.widgets[2].value += y[1].trim();
|
||||
else
|
||||
node.widgets[2].value += node._preset_value.trim();
|
||||
}
|
||||
|
||||
Object.defineProperty(preset_widget, "value", {
|
||||
set: (x) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(node.widgets[2].value) {
|
||||
node.widgets[2].value += ', ';
|
||||
}
|
||||
|
||||
const y = x.split(':');
|
||||
if(y.length == 2)
|
||||
node.widgets[2].value += y[1].trim();
|
||||
else
|
||||
node.widgets[2].value += x.trim();
|
||||
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[2] = node.widgets[2].values;
|
||||
}
|
||||
};
|
||||
set: (value) => {
|
||||
if (value !== "#PRESET")
|
||||
node._preset_value = value;
|
||||
},
|
||||
get: () => { return '#PRESET'; }
|
||||
});
|
||||
|
||||
+2
-2
@@ -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.0"
|
||||
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.9"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["matplotlib", "cachetools"]
|
||||
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
matplotlib
|
||||
cachetools
|
||||
numpy<2
|
||||
numpy<2
|
||||
webcolors
|
||||
opencv-python
|
||||
|
||||
Reference in New Issue
Block a user