Compare commits

...
3 Commits
7 changed files with 548 additions and 46 deletions
+6 -2
View File
@@ -13,12 +13,16 @@ 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.
+1 -1
View File
@@ -7,7 +7,7 @@
import importlib
version_code = [0, 86, 2]
version_code = [1, 2]
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})")
+12
View File
@@ -5,6 +5,7 @@ import torch
from PIL import Image, ImageDraw
import math
import cv2
import folder_paths
def apply_variation_noise(latent_image, noise_device, variation_seed, variation_strength, mask=None, variation_method='linear'):
@@ -336,3 +337,14 @@ def flatten_non_zero_override(masks: torch.Tensor):
final_mask[non_zero_mask] = masks[i][non_zero_mask]
return final_mask
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
for full_folder_path in full_folder_paths:
folder_paths.add_model_folder_path(folder_name, full_folder_path)
if folder_name in folder_paths.folder_names_and_paths:
current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
updated_extensions = current_extensions | extensions
folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
else:
folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)
+459 -36
View File
@@ -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,16 +368,6 @@ 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 = []
@@ -204,6 +411,14 @@ class LoraLoaderBlockWeight:
np.random.seed(seed % (2**31))
populated_vector_list = []
ratios = []
ratio = 1.0
vector_i = 1
last_k_unet_num = None
block_weights = {}
muted_weights = []
for k, v, k_unet_num, k_unet in (input_blocks + middle_blocks + output_blocks + double_blocks + single_blocks):
if last_k_unet_num != k_unet_num and len(vector) > vector_i:
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[vector_i].strip())
@@ -220,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
@@ -229,12 +446,9 @@ class LoraLoaderBlockWeight:
last_k_unet_num = k_unet_num
if populated_ratio != 0:
new_modelpatcher.add_patches({k: v}, strength_model * populated_ratio)
# if inverse:
# print(f"\t{k_unet} -> inv({ratio}) ")
# else:
# print(f"\t{k_unet} -> ({ratio}) ")
block_weights[k] = v, populated_ratio
else:
muted_weights.append(k)
# prepare base patch
ratios = LoraLoaderBlockWeight.convert_vector_value(A, B, vector[0].strip())
@@ -243,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:
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
@@ -278,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:
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:
@@ -345,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
@@ -353,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()
@@ -385,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
@@ -697,13 +971,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",
}
+62 -5
View File
@@ -12,6 +12,7 @@ import folder_paths
import comfy
import traceback
import random
import hashlib
from server import PromptServer
from .libs import utils, common
@@ -49,7 +50,13 @@ class LoadPromptsFromDir:
except Exception:
prompt_dirs = []
return {"required": {"prompt_dir": (prompt_dirs,)}}
return {"required": {
"prompt_dir": (prompt_dirs,)
},
"optional": {
"reload": ("BOOLEAN", { "default": False, "label_on": "if file changed", "label_off": "if value changed"}),
}
}
RETURN_TYPES = ("ZIPPED_PROMPT",)
OUTPUT_IS_LIST = (True,)
@@ -59,7 +66,30 @@ class LoadPromptsFromDir:
CATEGORY = "InspirePack/Prompt"
@staticmethod
def doit(prompt_dir):
def IS_CHANGED(prompt_dir, reload=False):
if not reload:
return prompt_dir
else:
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")]
md5 = hashlib.md5()
files.sort()
for file in files:
md5.update(file.encode('utf-8'))
with open(os.path.join(prompt_dir, file), '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):
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")]
@@ -105,8 +135,14 @@ class LoadPromptsFromFile:
except Exception:
prompt_files = []
return {"required": {"prompt_file": (prompt_files,)},
"optional": {"text_data_opt": ("STRING", {"defaultInput": True})}}
return {"required": {
"prompt_file": (prompt_files,)
},
"optional": {
"text_data_opt": ("STRING", {"defaultInput": True}),
"reload": ("BOOLEAN", {"default": False, "label_on": "if file changed", "label_off": "if value changed"}),
}
}
RETURN_TYPES = ("ZIPPED_PROMPT",)
OUTPUT_IS_LIST = (True,)
@@ -116,7 +152,28 @@ class LoadPromptsFromFile:
CATEGORY = "InspirePack/Prompt"
@staticmethod
def doit(prompt_file, text_data_opt=None):
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:
prompt_path = os.path.join(prompts_path, prompt_file)
with open(prompt_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):
prompt_path = os.path.join(prompts_path, prompt_file)
prompts = []
+7 -1
View File
@@ -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,6 +26,12 @@ 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";
Object.defineProperty(node.widgets[preset_i], "value", {
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-inspire-pack"
description = "This extension provides various nodes to support Lora Block Weight and the Impact Pack. Provides many easily applicable regional features and applications for Variation Seed."
version = "0.86.2"
version = "1.2"
license = { file = "LICENSE" }
dependencies = ["matplotlib", "cachetools"]