Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcbed07560 | ||
|
|
685c5c0d00 | ||
|
|
697f12fbdb | ||
|
|
e8817bc348 | ||
|
|
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 | ||
|
|
50d42c30b5 | ||
|
|
89e24b7529 | ||
|
|
6db6ca54c0 | ||
|
|
69b7c486a9 | ||
|
|
b09b295009 | ||
|
|
a15d3362d8 | ||
|
|
019713040c | ||
|
|
bc075b1a4f | ||
|
|
452d8cfc00 | ||
|
|
17716ddb52 | ||
|
|
56f4f01f02 | ||
|
|
f64c98714b | ||
|
|
30974e69c4 | ||
|
|
ec29936d16 | ||
|
|
0bd92419de | ||
|
|
ec6831fd68 | ||
|
|
c876e477e8 | ||
|
|
0de0e4232c | ||
|
|
61ba155cf1 | ||
|
|
09ae2eaa9b | ||
|
|
eab0b95df5 | ||
|
|
badc1c0cb4 |
@@ -2,6 +2,7 @@
|
||||
This repository offers various extension nodes for ComfyUI. Nodes here have different characteristics compared to those in the ComfyUI Impact Pack. The Impact Pack has become too large now...
|
||||
|
||||
## Notice:
|
||||
* v1.9.1 To avoid confusion with the `NOISE` type in core, the type name has been changed to `NOISE_IMAGE`.
|
||||
* V0.73 The Variation Seed feature is added to Regional Prompt nodes, and it is only compatible with versions Impact Pack V5.10 and above.
|
||||
* V0.69 incompatible with the outdated **ComfyUI IPAdapter Plus**. (A version dated March 24th or later is required.)
|
||||
* V0.64 add sigma_factor to RegionalPrompt... nodes required Impact Pack V4.76 or later.
|
||||
@@ -13,12 +14,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 +36,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 +51,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 +65,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 +148,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 +159,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 +189,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 = [0, 82, 6]
|
||||
version_code = [1, 9, 1]
|
||||
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})")
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class KSamplerAdvanced_inspire:
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"noise_opt": ("NOISE",),
|
||||
"noise_opt": ("NOISE_IMAGE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,7 @@ class KSamplerAdvanced_inspire_pipe:
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"noise_opt": ("NOISE",),
|
||||
"noise_opt": ("NOISE_IMAGE",),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",)
|
||||
|
||||
@@ -18,7 +18,7 @@ class LoadImagesFromDirBatch:
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": -1, "step": 1}),
|
||||
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
@@ -94,10 +94,10 @@ class LoadImagesFromDirBatch:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
|
||||
for mask2 in masks[1:]:
|
||||
for mask2 in masks:
|
||||
if has_non_empty_mask:
|
||||
if image1.shape[1:3] != mask2.shape:
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[2], image1.shape[1]), mode='bilinear', align_corners=False)
|
||||
mask2 = torch.nn.functional.interpolate(mask2.unsqueeze(0).unsqueeze(0), size=(image1.shape[1], image1.shape[2]), mode='bilinear', align_corners=False)
|
||||
mask2 = mask2.squeeze(0)
|
||||
else:
|
||||
mask2 = mask2.unsqueeze(0)
|
||||
@@ -126,8 +126,9 @@ class LoadImagesFromDirList:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "STRING")
|
||||
RETURN_NAMES = ("IMAGE", "MASK", "FILE PATH")
|
||||
OUTPUT_IS_LIST = (True, True, True)
|
||||
|
||||
FUNCTION = "load_images"
|
||||
|
||||
@@ -159,6 +160,7 @@ class LoadImagesFromDirList:
|
||||
|
||||
images = []
|
||||
masks = []
|
||||
file_paths = []
|
||||
|
||||
limit_images = False
|
||||
if image_load_cap > 0:
|
||||
@@ -184,9 +186,10 @@ class LoadImagesFromDirList:
|
||||
|
||||
images.append(image)
|
||||
masks.append(mask)
|
||||
file_paths.append(str(image_path))
|
||||
image_count += 1
|
||||
|
||||
return images, masks
|
||||
return (images, masks, file_paths)
|
||||
|
||||
|
||||
class LoadImageInspire:
|
||||
|
||||
@@ -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'):
|
||||
@@ -56,6 +57,8 @@ def slerp(val, low, high):
|
||||
|
||||
|
||||
def mix_noise(from_noise, to_noise, strength, variation_method):
|
||||
to_noise = to_noise.to(from_noise.device)
|
||||
|
||||
if variation_method == 'slerp':
|
||||
mixed_noise = slerp(strength, from_noise, to_noise)
|
||||
else:
|
||||
@@ -334,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)
|
||||
|
||||
+174
-3
@@ -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,12 +18,17 @@ 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:
|
||||
if start == stop or step == 0:
|
||||
return ([start], )
|
||||
|
||||
reverse = False
|
||||
if start > stop:
|
||||
reverse = True
|
||||
start, stop = stop, start
|
||||
|
||||
res = []
|
||||
x = start
|
||||
last = x
|
||||
@@ -36,13 +44,176 @@ class FloatRange:
|
||||
|
||||
res.append(stop)
|
||||
|
||||
if reverse:
|
||||
res.reverse()
|
||||
|
||||
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)",
|
||||
}
|
||||
|
||||
+574
-63
@@ -6,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
|
||||
|
||||
@@ -34,6 +43,76 @@ def load_lbw_preset(filename):
|
||||
return []
|
||||
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
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
|
||||
@@ -55,8 +134,8 @@ class LoraLoaderBlockWeight:
|
||||
"lora_name": (lora_names, ),
|
||||
"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}),
|
||||
"inverse": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"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,),
|
||||
@@ -130,11 +209,150 @@ class LoraLoaderBlockWeight:
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector):
|
||||
def block_spec_parser(loaded, spec):
|
||||
if not spec.startswith("%"):
|
||||
return spec
|
||||
else:
|
||||
items = [x.strip() for x in spec[1:].split(',')]
|
||||
|
||||
input_blocks_set = set()
|
||||
middle_blocks_set= set()
|
||||
output_blocks_set = set()
|
||||
double_blocks_set = set()
|
||||
single_blocks_set = set()
|
||||
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
k_unet_num = k_unet[len("input_blocks."):len("input_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
input_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("middle_block."):
|
||||
k_unet_num = k_unet[len("middle_block."):len("middle_block.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
middle_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
output_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
double_blocks_set.add(k_unet_int)
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
single_blocks_set.add(k_unet_int)
|
||||
|
||||
pat1 = re.compile(r"(default|base)=([0-9.]+)")
|
||||
pat2 = re.compile(r"(in|out|mid|double|single)([0-9]+)-([0-9]+)=([0-9.]+)")
|
||||
pat3 = re.compile(r"(in|out|mid|double|single)([0-9]+)=([0-9.]+)")
|
||||
pat4 = re.compile(r"(in|out|mid|double|single)=([0-9.]+)")
|
||||
|
||||
base_spec = None
|
||||
default_spec = 1.0
|
||||
|
||||
for item in items:
|
||||
match = pat1.match(item)
|
||||
if match:
|
||||
if match[1] == 'base':
|
||||
base_spec = match[2]
|
||||
continue
|
||||
|
||||
if match[1] == 'default':
|
||||
default_spec = match[2]
|
||||
continue
|
||||
|
||||
if base_spec is None:
|
||||
base_spec = default_spec
|
||||
|
||||
input_blocks = [default_spec] * len(input_blocks_set)
|
||||
middle_blocks = [default_spec] * len(middle_blocks_set)
|
||||
output_blocks = [default_spec] * len(output_blocks_set)
|
||||
double_blocks = [default_spec] * len(double_blocks_set)
|
||||
single_blocks = [default_spec] * len(single_blocks_set)
|
||||
|
||||
for item in items:
|
||||
match = pat2.match(item)
|
||||
if match:
|
||||
for x in range(int(match[2])-1, int(match[3])):
|
||||
value = float(match[4])
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat3.match(item)
|
||||
if match:
|
||||
value = float(match[3])
|
||||
x = int(match[2]) - 1
|
||||
|
||||
if x < 0:
|
||||
continue
|
||||
|
||||
if match[1] == 'in' and len(input_blocks) > x:
|
||||
input_blocks[x] = value
|
||||
elif match[1] == 'out' and len(output_blocks) > x:
|
||||
output_blocks[x] = value
|
||||
elif match[1] == 'mid' and len(middle_blocks) > x:
|
||||
middle_blocks[x] = value
|
||||
elif match[1] == 'double' and len(double_blocks) > x:
|
||||
double_blocks[x] = value
|
||||
elif match[1] == 'single' and len(single_blocks) > x:
|
||||
single_blocks[x] = value
|
||||
|
||||
continue
|
||||
|
||||
match = pat4.match(item)
|
||||
if match:
|
||||
value = float(match[2])
|
||||
|
||||
if match[1] == 'in':
|
||||
input_blocks = [value] * len(input_blocks)
|
||||
elif match[1] == 'out':
|
||||
output_blocks = [value] * len(output_blocks)
|
||||
elif match[1] == 'mid':
|
||||
middle_blocks = [value] * len(middle_blocks)
|
||||
elif match[1] == 'double':
|
||||
double_blocks = [value] * len(double_blocks)
|
||||
elif match[1] == 'single':
|
||||
single_blocks = [value] * len(single_blocks)
|
||||
|
||||
continue
|
||||
|
||||
# concat specs
|
||||
res = [str(base_spec)]
|
||||
for x in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
|
||||
res.append(str(x))
|
||||
|
||||
return ",".join(res)
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
|
||||
block_vector = LoraLoaderBlockWeight.block_spec_parser(loaded, block_vector)
|
||||
|
||||
block_vector = block_vector.split(":")
|
||||
if len(block_vector) > 1:
|
||||
block_vector = block_vector[1]
|
||||
@@ -142,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()
|
||||
@@ -151,22 +368,19 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
raise ValueError(f"[LoraLoaderBlockWeight] invalid block_vector '{block_vector}'")
|
||||
|
||||
last_k_unet_num = None
|
||||
new_modelpatcher = model.clone()
|
||||
populated_ratio = strength_model
|
||||
|
||||
def parse_unet_num(s):
|
||||
if s[1] == '.':
|
||||
return int(s[0])
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
# sort: input, middle, output, others
|
||||
input_blocks = []
|
||||
middle_blocks = []
|
||||
output_blocks = []
|
||||
double_blocks = []
|
||||
single_blocks = []
|
||||
others = []
|
||||
for k, v in loaded.items():
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -178,18 +392,34 @@ class LoraLoaderBlockWeight:
|
||||
elif k_unet.startswith("output_blocks."):
|
||||
k_unet_num = k_unet[len("output_blocks."):len("output_blocks.")+2]
|
||||
output_blocks.append((k, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.")+2]
|
||||
double_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.")+2]
|
||||
single_blocks.append((key, v, parse_unet_num(k_unet_num), k_unet))
|
||||
else:
|
||||
others.append((k, v, k_unet))
|
||||
|
||||
input_blocks = sorted(input_blocks, key=lambda x: x[2])
|
||||
middle_blocks = sorted(middle_blocks, key=lambda x: x[2])
|
||||
output_blocks = sorted(output_blocks, key=lambda x: x[2])
|
||||
double_blocks = sorted(double_blocks, key=lambda x: x[2])
|
||||
single_blocks = sorted(single_blocks, key=lambda x: x[2])
|
||||
|
||||
# prepare patch
|
||||
np.random.seed(seed % (2**31))
|
||||
populated_vector_list = []
|
||||
ratios = []
|
||||
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks):
|
||||
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:
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[vector_i].strip())
|
||||
ratio = ratios.pop(0)
|
||||
@@ -205,6 +435,8 @@ class LoraLoaderBlockWeight:
|
||||
else:
|
||||
if len(ratios) > 0:
|
||||
ratio = ratios.pop(0)
|
||||
else:
|
||||
pass # use last used ratio if no more user specified ratio is given
|
||||
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
@@ -213,11 +445,10 @@ class LoraLoaderBlockWeight:
|
||||
|
||||
last_k_unet_num = k_unet_num
|
||||
|
||||
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}) ")
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
|
||||
# prepare base patch
|
||||
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
|
||||
@@ -226,25 +457,43 @@ class LoraLoaderBlockWeight:
|
||||
if inverse:
|
||||
populated_ratio = 1 - ratio
|
||||
else:
|
||||
populated_ratio = 1
|
||||
populated_ratio = ratio
|
||||
|
||||
populated_vector_list.insert(0, LoraLoaderBlockWeight.norm_value(populated_ratio))
|
||||
|
||||
for k, v, k_unet in others:
|
||||
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}) ")
|
||||
if populated_ratio != 0:
|
||||
block_weights[k] = v, populated_ratio
|
||||
else:
|
||||
muted_weights.append(k)
|
||||
|
||||
new_clip = clip.clone()
|
||||
new_clip.add_patches(loaded, strength_clip)
|
||||
populated_vector = ','.join(map(str, populated_vector_list))
|
||||
return (new_modelpatcher, new_clip, populated_vector)
|
||||
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):
|
||||
if strength_model == 0 and strength_clip == 0 or bypass:
|
||||
return (model, clip, "")
|
||||
return model, clip, ""
|
||||
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lora = None
|
||||
@@ -261,7 +510,48 @@ class LoraLoaderBlockWeight:
|
||||
self.loaded_lora = (lora_path, lora)
|
||||
|
||||
model_lora, clip_lora, populated_vector = LoraLoaderBlockWeight.load_lora_for_models(model, clip, lora, strength_model, strength_clip, inverse, seed, A, B, block_vector)
|
||||
return (model_lora, clip_lora, populated_vector)
|
||||
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:
|
||||
@@ -328,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
|
||||
@@ -336,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()
|
||||
|
||||
@@ -368,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
|
||||
@@ -485,7 +776,7 @@ class XYInput_LoraBlockWeight:
|
||||
XY_Capsule_LoraBlockWeight(0, 2, '', 'diff', storage, common_params),
|
||||
XY_Capsule_LoraBlockWeight(0, 3, '', 'heatmap', storage, common_params)]
|
||||
|
||||
return ((xy_type, x_values), (xy_type, y_values), )
|
||||
return (xy_type, x_values), (xy_type, y_values),
|
||||
|
||||
|
||||
class LoraBlockInfo:
|
||||
@@ -531,12 +822,29 @@ 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 = []
|
||||
double_blocks_map = {}
|
||||
|
||||
single_block_count = set()
|
||||
single_blocks = []
|
||||
single_blocks_map = {}
|
||||
|
||||
others = []
|
||||
for k, v in loaded.items():
|
||||
for key, v in loaded.items():
|
||||
if isinstance(key, tuple):
|
||||
k = key[0]
|
||||
else:
|
||||
k = key
|
||||
|
||||
k_unet = k[len("diffusion_model."):]
|
||||
|
||||
if k_unet.startswith("input_blocks."):
|
||||
@@ -572,16 +880,49 @@ class LoraBlockInfo:
|
||||
else:
|
||||
output_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("double_blocks."):
|
||||
k_unet_num = k_unet[len("double_blocks."):len("double_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
double_block_count.add(k_unet_int)
|
||||
double_blocks.append(k_unet)
|
||||
if k_unet_int in double_blocks_map:
|
||||
double_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
double_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("single_blocks."):
|
||||
k_unet_num = k_unet[len("single_blocks."):len("single_blocks.") + 2]
|
||||
k_unet_int = parse_unet_num(k_unet_num)
|
||||
|
||||
single_block_count.add(k_unet_int)
|
||||
single_blocks.append(k_unet)
|
||||
if k_unet_int in single_blocks_map:
|
||||
single_blocks_map[k_unet_int].append(k_unet)
|
||||
else:
|
||||
single_blocks_map[k_unet_int] = [k_unet]
|
||||
|
||||
elif k_unet.startswith("er.text_model.encoder.layers."):
|
||||
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)
|
||||
@@ -591,27 +932,48 @@ class LoraBlockInfo:
|
||||
input_blocks = sorted(input_blocks)
|
||||
middle_blocks = sorted(middle_blocks)
|
||||
output_blocks = sorted(output_blocks)
|
||||
double_blocks = sorted(double_blocks)
|
||||
single_blocks = sorted(single_blocks)
|
||||
others = sorted(others)
|
||||
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
if len(input_block_count) > 0:
|
||||
text += f"\n-------[Input blocks] ({len(input_block_count)}, Subs={len(input_blocks)})-------\n"
|
||||
input_keys = sorted(input_blocks_map.keys())
|
||||
for x in input_keys:
|
||||
text += f" IN{x}: {len(input_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
if len(middle_block_count) > 0:
|
||||
text += f"\n-------[Middle blocks] ({len(middle_block_count)}, Subs={len(middle_blocks)})-------\n"
|
||||
middle_keys = sorted(middle_blocks_map.keys())
|
||||
for x in middle_keys:
|
||||
text += f" MID{x}: {len(middle_blocks_map[x])}\n"
|
||||
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_blocks_map[x])}\n"
|
||||
if len(output_block_count) > 0:
|
||||
text += f"\n-------[Output blocks] ({len(output_block_count)}, Subs={len(output_blocks)})-------\n"
|
||||
output_keys = sorted(output_blocks_map.keys())
|
||||
for x in output_keys:
|
||||
text += f" OUT{x}: {len(output_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"
|
||||
if len(double_block_count) > 0:
|
||||
text += f"\n-------[Double blocks(MMDiT)] ({len(double_block_count)}, Subs={len(double_blocks)})-------\n"
|
||||
double_keys = sorted(double_blocks_map.keys())
|
||||
for x in double_keys:
|
||||
text += f" DOUBLE{x}: {len(double_blocks_map[x])}\n"
|
||||
|
||||
if len(single_block_count) > 0:
|
||||
text += f"\n-------[Single blocks(DiT)] ({len(single_block_count)}, Subs={len(single_blocks)})-------\n"
|
||||
single_keys = sorted(single_blocks_map.keys())
|
||||
for x in single_keys:
|
||||
text += f" SINGLE{x}: {len(single_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"
|
||||
@@ -628,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",
|
||||
}
|
||||
|
||||
+12
-4
@@ -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,13 +58,16 @@ 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}),
|
||||
"insightface_provider": (["CPU", "CUDA", "ROCM"], ),
|
||||
"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"}
|
||||
}
|
||||
|
||||
@@ -72,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):
|
||||
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)"})
|
||||
@@ -157,7 +165,7 @@ class IPAdapterModelHelper:
|
||||
if cache_mode in ["insightface only", "all"]:
|
||||
icache_key = 'insightface-' + insightface_provider
|
||||
if icache_key not in backend_support.cache:
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(insightface_provider)[0]))
|
||||
backend_support.update_cache(icache_key, "insightface", (False, insight_face_loader(provider=insightface_provider, model_name=insightface_model_name)[0]))
|
||||
_, (_, insightface) = backend_support.cache[icache_key]
|
||||
else:
|
||||
insightface = insight_face_loader(insightface_provider)[0]
|
||||
|
||||
+179
-62
@@ -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,21 +40,31 @@ 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",)
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT", "INT")
|
||||
RETURN_NAMES = ("zipped_prompt", "count")
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
FUNCTION = "doit"
|
||||
@@ -59,56 +72,104 @@ 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)
|
||||
|
||||
for prompt in prompt_list:
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, file_name)
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else file_name
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
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, )
|
||||
return (prompts, len(prompts),)
|
||||
|
||||
|
||||
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",)
|
||||
RETURN_TYPES = ("ZIPPED_PROMPT", "INT")
|
||||
RETURN_NAMES = ("zipped_prompt", "count")
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
FUNCTION = "doit"
|
||||
@@ -116,49 +177,92 @@ 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
|
||||
|
||||
prompt_list = re.split(r'\n\s*-+\s*\n', prompt_data)
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
|
||||
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()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else prompt_file
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
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, )
|
||||
return (prompts, len(prompts),)
|
||||
|
||||
|
||||
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 +282,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:
|
||||
@@ -194,18 +310,19 @@ class LoadSinglePromptFromFile:
|
||||
except Exception:
|
||||
prompt = prompt_list[-1]
|
||||
|
||||
pattern = r"positive:(.*?)(?:\n*|$)negative:(.*)"
|
||||
pattern = r"^(?:(?:positive:(?P<positive>.*?)|negative:(?P<negative>.*?)|name:(?P<name>.*?))\n*)+$"
|
||||
matches = re.search(pattern, prompt, re.DOTALL)
|
||||
|
||||
if matches:
|
||||
positive_text = matches.group(1).strip()
|
||||
negative_text = matches.group(2).strip()
|
||||
result_tuple = (positive_text, negative_text, prompt_file)
|
||||
positive_text = matches.group('positive').strip()
|
||||
negative_text = matches.group('negative').strip()
|
||||
name_text = matches.group('name').strip() if matches.group('name') else prompt_file
|
||||
result_tuple = (positive_text, negative_text, name_text)
|
||||
prompts.append(result_tuple)
|
||||
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,11 +683,11 @@ class SeedExplorer:
|
||||
"optional":
|
||||
{
|
||||
"variation_method": (["linear", "slerp"],),
|
||||
"model": ("model",),
|
||||
"model": ("MODEL",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
@@ -645,15 +762,15 @@ class CompositeNoise:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"destination": ("NOISE",),
|
||||
"source": ("NOISE",),
|
||||
"destination": ("NOISE_IMAGE",),
|
||||
"source": ("NOISE_IMAGE",),
|
||||
"mode": (["center", "left-top", "right-top", "left-bottom", "right-bottom", "xy"], ),
|
||||
"x": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"y": ("INT", {"default": 0, "min": 0, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Prompt"
|
||||
|
||||
@@ -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:
|
||||
@@ -458,7 +460,7 @@ class RegionalSeedExplorerMask:
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
|
||||
"noise": ("NOISE",),
|
||||
"noise": ("NOISE_IMAGE",),
|
||||
"seed_prompt": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": False}),
|
||||
"enable_additional": ("BOOLEAN", {"default": True, "label_on": "true", "label_off": "false"}),
|
||||
"additional_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
@@ -469,7 +471,7 @@ class RegionalSeedExplorerMask:
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
RETURN_TYPES = ("NOISE_IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
@@ -515,7 +517,7 @@ class RegionalSeedExplorerColorMask:
|
||||
"color_mask": ("IMAGE",),
|
||||
"mask_color": ("STRING", {"multiline": False, "default": "#FFFFFF"}),
|
||||
|
||||
"noise": ("NOISE",),
|
||||
"noise": ("NOISE_IMAGE",),
|
||||
"seed_prompt": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": False}),
|
||||
"enable_additional": ("BOOLEAN", {"default": True, "label_on": "true", "label_off": "false"}),
|
||||
"additional_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
@@ -526,7 +528,7 @@ class RegionalSeedExplorerColorMask:
|
||||
{"variation_method": (["linear", "slerp"],), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE", "MASK")
|
||||
RETURN_TYPES = ("NOISE_IMAGE", "MASK")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "InspirePack/Regional"
|
||||
|
||||
+33
-13
@@ -24,6 +24,7 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
"noise_mode": (["GPU(=A1111)", "CPU"],),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
@@ -36,27 +37,29 @@ class KSampler_progress(a1111_compat.KSampler_inspire):
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
@staticmethod
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, interval, omit_start_latent, scheduler_func_opt=None):
|
||||
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
|
||||
interval, omit_start_latent, omit_final_latent, scheduler_func_opt=None):
|
||||
adv_steps = int(steps / denoise)
|
||||
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
|
||||
result = []
|
||||
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:
|
||||
return
|
||||
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.to(model_management.intermediate_device())
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, True, seed, adv_steps, cfg, sampler_name, scheduler, positive, negative, latent_image, (adv_steps-steps),
|
||||
adv_steps, noise_mode, False, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
result = {'samples': result}
|
||||
@@ -86,6 +89,7 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}),
|
||||
"interval": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"omit_start_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
"omit_final_latent": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
|
||||
},
|
||||
"optional": {
|
||||
"prev_progress_latent_opt": ("LATENT",),
|
||||
@@ -100,25 +104,28 @@ class KSamplerAdvanced_progress(a1111_compat.KSamplerAdvanced_inspire):
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("latent", "progress_latent")
|
||||
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, return_with_leftover_noise, interval, omit_start_latent, prev_progress_latent_opt=None, scheduler_func_opt=None):
|
||||
def doit(self, model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
start_at_step, end_at_step, noise_mode, return_with_leftover_noise, interval, omit_start_latent, omit_final_latent,
|
||||
prev_progress_latent_opt=None, scheduler_func_opt=None):
|
||||
|
||||
if omit_start_latent:
|
||||
result = []
|
||||
else:
|
||||
result = [latent_image['samples']]
|
||||
|
||||
result = []
|
||||
|
||||
def progress_callback(step, x0, x, total_steps):
|
||||
if (total_steps-1) != step and step % interval != 0:
|
||||
return
|
||||
|
||||
x = model.model.process_latent_out(x)
|
||||
x = x.to(model_management.intermediate_device())
|
||||
x = x.cpu()
|
||||
result.append(x)
|
||||
|
||||
latent_image, noise = a1111_compat.KSamplerAdvanced_inspire.sample(model, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, start_at_step, end_at_step,
|
||||
noise_mode, False, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
noise_mode, return_with_leftover_noise, callback=progress_callback, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if not omit_final_latent:
|
||||
result.append(latent_image['samples'].cpu())
|
||||
|
||||
if len(result) > 0:
|
||||
result = torch.cat(result)
|
||||
@@ -165,6 +172,15 @@ def logarithmic_interpolation(from_cfg, to_cfg, i, steps):
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
def cosine_interpolation(from_cfg, to_cfg, i, steps):
|
||||
if (i == 0) or (i == steps-1):
|
||||
return from_cfg
|
||||
|
||||
t = (1.0 + math.cos(math.pi*2*(i/steps))) / 2
|
||||
|
||||
return from_cfg + (to_cfg - from_cfg) * t
|
||||
|
||||
|
||||
class Guider_scheduled(CFGGuider):
|
||||
def __init__(self, model_patcher, sigmas, from_cfg, to_cfg, schedule):
|
||||
super().__init__(model_patcher)
|
||||
@@ -194,6 +210,8 @@ class Guider_scheduled(CFGGuider):
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
@@ -245,6 +263,8 @@ class Guider_PerpNeg_scheduled(Guider_PerpNeg):
|
||||
self.cfg_sigmas[k] = exponential_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'log':
|
||||
self.cfg_sigmas[k] = logarithmic_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
elif self.schedule == 'cos':
|
||||
self.cfg_sigmas[k] = cosine_interpolation(self.from_cfg, self.to_cfg, i, steps), i
|
||||
else:
|
||||
self.cfg_sigmas[k] = self.from_cfg + delta * i / steps, i
|
||||
|
||||
@@ -276,7 +296,7 @@ class ScheduledCFGGuider:
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp"], {'default': 'log'})
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +323,7 @@ class ScheduledPerpNegCFGGuider:
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"from_cfg": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"to_cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
|
||||
"schedule": (["linear", "log", "exp"], {'default': 'log'})
|
||||
"schedule": (["linear", "log", "exp", "cos"], {'default': 'log'})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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'; }
|
||||
});
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
[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 = "0.82.6"
|
||||
license = "LICENSE"
|
||||
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.1"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["matplotlib", "cachetools"]
|
||||
|
||||
[project.urls]
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
matplotlib
|
||||
cachetools
|
||||
numpy<2
|
||||
numpy<2
|
||||
webcolors
|
||||
opencv-python
|
||||
|
||||
@@ -29,6 +29,16 @@ SDXL-LyC-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
SDXL-LyC-INALL:1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-MIDALL:1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0
|
||||
SDXL-LyC-OUTALL:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-DBL-FRONT7:1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-DBL-MID6:1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0
|
||||
FLUX-DBL-TAIL6:1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1
|
||||
FLUX-SINGLE-ALL:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
FLUX-SINGLE-1to10:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-11to20:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-21to30:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0
|
||||
FLUX-SINGLE-31to37:1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
|
||||
FLUX-ALL:1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
@SD-FULL-TEST:17
|
||||
@SD-BLOCK1-TEST:17,12,1
|
||||
@SD-BLOCK2-TEST:17,12,2
|
||||
@@ -49,4 +59,64 @@ SDXL-LyC-OUTALL:1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1
|
||||
@SD-BLOCK17-TEST:17,12,17
|
||||
@SD-LyC-FULL-TEST:27
|
||||
@SDXL-FULL-TEST:12
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
@SDXL-LyC-FULL-TEST:21
|
||||
@FLUX-DBL-FULL:19
|
||||
@FLUX-DBL-SGL-FULL:58
|
||||
@FLUX-DBL0-TEST:19,14,2
|
||||
@FLUX-DBL1-TEST:19,14,3
|
||||
@FLUX-DBL2-TEST:19,14,4
|
||||
@FLUX-DBL3-TEST:19,14,5
|
||||
@FLUX-DBL4-TEST:19,14,6
|
||||
@FLUX-DBL5-TEST:19,14,7
|
||||
@FLUX-DBL6-TEST:19,14,8
|
||||
@FLUX-DBL7-TEST:19,14,9
|
||||
@FLUX-DBL8-TEST:19,14,10
|
||||
@FLUX-DBL9-TEST:19,14,11
|
||||
@FLUX-DBL10-TEST:19,14,12
|
||||
@FLUX-DBL11-TEST:19,14,13
|
||||
@FLUX-DBL12-TEST:19,14,14
|
||||
@FLUX-DBL13-TEST:19,14,15
|
||||
@FLUX-DBL14-TEST:19,14,16
|
||||
@FLUX-DBL15-TEST:19,14,17
|
||||
@FLUX-DBL16-TEST:19,14,18
|
||||
@FLUX-DBL17-TEST:19,14,19
|
||||
@FLUX-DBL18-TEST:19,14,20
|
||||
@FLUX-SGL0-TEST:58,6,21
|
||||
@FLUX-SGL1-TEST:58,6,22
|
||||
@FLUX-SGL2-TEST:58,6,23
|
||||
@FLUX-SGL3-TEST:58,6,24
|
||||
@FLUX-SGL4-TEST:58,6,25
|
||||
@FLUX-SGL5-TEST:58,6,26
|
||||
@FLUX-SGL6-TEST:58,6,27
|
||||
@FLUX-SGL7-TEST:58,6,28
|
||||
@FLUX-SGL8-TEST:58,6,29
|
||||
@FLUX-SGL9-TEST:58,6,30
|
||||
@FLUX-SGL10-TEST:58,6,31
|
||||
@FLUX-SGL11-TEST:58,6,32
|
||||
@FLUX-SGL12-TEST:58,6,33
|
||||
@FLUX-SGL13-TEST:58,6,34
|
||||
@FLUX-SGL14-TEST:58,6,35
|
||||
@FLUX-SGL15-TEST:58,6,36
|
||||
@FLUX-SGL16-TEST:58,6,37
|
||||
@FLUX-SGL17-TEST:58,6,38
|
||||
@FLUX-SGL18-TEST:58,6,39
|
||||
@FLUX-SGL19-TEST:58,6,40
|
||||
@FLUX-SGL20-TEST:58,6,41
|
||||
@FLUX-SGL21-TEST:58,6,42
|
||||
@FLUX-SGL22-TEST:58,6,43
|
||||
@FLUX-SGL23-TEST:58,6,44
|
||||
@FLUX-SGL24-TEST:58,6,45
|
||||
@FLUX-SGL25-TEST:58,6,46
|
||||
@FLUX-SGL26-TEST:58,6,47
|
||||
@FLUX-SGL27-TEST:58,6,48
|
||||
@FLUX-SGL28-TEST:58,6,49
|
||||
@FLUX-SGL29-TEST:58,6,50
|
||||
@FLUX-SGL30-TEST:58,6,51
|
||||
@FLUX-SGL31-TEST:58,6,52
|
||||
@FLUX-SGL32-TEST:58,6,53
|
||||
@FLUX-SGL33-TEST:58,6,54
|
||||
@FLUX-SGL34-TEST:58,6,55
|
||||
@FLUX-SGL35-TEST:58,6,56
|
||||
@FLUX-SGL36-TEST:58,6,57
|
||||
@FLUX-SGL37-TEST:58,6,58
|
||||
@FLUX-SGL38-TEST:58,6,59
|
||||
Reference in New Issue
Block a user