Compare commits

..
6 Commits
9 changed files with 319 additions and 59 deletions
+10 -1
View File
@@ -63,9 +63,15 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
* Specify the directories located under `ComfyUI-Inspire-Pack/prompts/`
* One prompts file can have multiple prompts separated by `---`.
* e.g. `prompts/example`
* `Load Prompts From File (Inspire)`: It sequentially reads prompts from the specified file. The output it returns is ZIPPED_PROMPT.
* **NOTE**: This node provides advanced option via `Show advanced`
* load_cap, start_index
* `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`
* **NOTE**: This node provides advanced option via `Show advanced`
* load_cap, start_index
* `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.
@@ -147,6 +153,9 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff
* `Shared Checkpoint Loader (Inspire)`: When loading a checkpoint through this loader, it is automatically cached in the backend cache. Additionally, if it is already cached, it retrieves it from the cache instead of loading it anew.
* 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.
* `Shared Diffusion Model Loader (Inspire)`: Similar to the `Shared Checkpoint Loader (Inspire)` but used for loading Diffusion models instead of Checkpoints.
* `Shared Text Encoder Loader (Inspire)`: Similar to the `Shared Checkpoint Loader (Inspire)` but used for loading Text Encoder models instead of Checkpoints.
* This node also functions as a unified node for `CLIPLoader`, `DualCLIPLoader`, and `TripleCLIPLoader`.
* `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.
+1 -1
View File
@@ -7,7 +7,7 @@
import importlib
version_code = [1, 9, 1]
version_code = [1, 13]
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})")
+41 -22
View File
@@ -8,9 +8,13 @@ from .libs import common
class Inspire_RandomNoise:
def __init__(self, seed, mode, incremental_seed_mode, variation_seed, variation_strength, variation_method="linear"):
def __init__(self, seed, mode, incremental_seed_mode, variation_seed, variation_strength, variation_method="linear", internal_seed=None):
device = comfy.model_management.get_torch_device()
self.seed = seed
# HOTFIX: https://github.com/comfyanonymous/ComfyUI/commit/916d1e14a93ef331adef7c0deff2fdcf443b05cf#commitcomment-151914788
# seed value should be different with generated noise
self.seed = internal_seed
self.noise_seed = seed
self.noise_device = "cpu" if mode == "CPU" else device
self.incremental_seed_mode = incremental_seed_mode
self.variation_seed = variation_seed
@@ -20,7 +24,7 @@ class Inspire_RandomNoise:
def generate_noise(self, input_latent):
latent_image = input_latent["samples"]
batch_inds = input_latent["batch_index"] if "batch_index" in input_latent else None
noise = utils.prepare_noise(latent_image, self.seed, batch_inds, self.noise_device, self.incremental_seed_mode,
noise = utils.prepare_noise(latent_image, self.noise_seed, batch_inds, self.noise_device, self.incremental_seed_mode,
variation_seed=self.variation_seed, variation_strength=self.variation_strength, variation_method=self.variation_method)
return noise.cpu()
@@ -29,28 +33,34 @@ class RandomNoise:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
"noise_mode": (["GPU(=A1111)", "CPU"],),
"batch_seed_mode": (["incremental", "comfy", "variation str inc:0.01", "variation str inc:0.05"],),
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
"optional":
{"variation_method": (["linear", "slerp"],), }
{
"variation_method": (["linear", "slerp"],),
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
}
}
RETURN_TYPES = ("NOISE",)
FUNCTION = "get_noise"
CATEGORY = "InspirePack/a1111_compat"
def get_noise(self, noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method="linear"):
return (Inspire_RandomNoise(noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method=variation_method),)
def get_noise(self, noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method="linear", internal_seed=None):
if internal_seed is None:
internal_seed = noise_seed
return (Inspire_RandomNoise(noise_seed, noise_mode, batch_seed_mode, variation_seed, variation_strength, variation_method=variation_method, internal_seed=internal_seed),)
def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
noise_mode="CPU", disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
incremental_seed_mode="comfy", variation_seed=None, variation_strength=None, noise=None, callback=None, variation_method="linear",
scheduler_func=None):
scheduler_func=None, internal_seed=None):
device = comfy.model_management.get_torch_device()
noise_device = "cpu" if noise_mode == "CPU" else device
latent_image = latent["samples"]
@@ -80,9 +90,12 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
start_step = advanced_steps - steps
steps = advanced_steps
if internal_seed is None:
internal_seed = seed
try:
samples = common.impact_sampling(
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
model=model, add_noise=not disable_noise, seed=internal_seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback,
scheduler_func=scheduler_func)
except Exception as e:
@@ -90,7 +103,7 @@ def inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
print(f"[Inspire Pack] Impact Pack is outdated. (Cannot use GITS scheduler.)")
samples = common.impact_sampling(
model=model, add_noise=not disable_noise, seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
model=model, add_noise=not disable_noise, seed=internal_seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler, positive=positive, negative=negative,
latent_image=latent, start_at_step=start_step, end_at_step=last_step, return_with_leftover_noise=not force_full_denoise, noise=noise, callback=callback)
else:
raise e
@@ -103,7 +116,7 @@ class KSampler_inspire:
def INPUT_TYPES(s):
return {"required":
{"model": ("MODEL",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
@@ -121,6 +134,7 @@ class KSampler_inspire:
{
"variation_method": (["linear", "slerp"],),
"scheduler_func_opt": ("SCHEDULER_FUNC",),
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
}
}
@@ -131,10 +145,11 @@ class KSampler_inspire:
@staticmethod
def doit(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
batch_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear", scheduler_func_opt=None):
batch_seed_mode="comfy", variation_seed=None, variation_strength=None, variation_method="linear", scheduler_func_opt=None,
internal_seed=None):
return (inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode,
incremental_seed_mode=batch_seed_mode, variation_seed=variation_seed, variation_strength=variation_strength, variation_method=variation_method,
scheduler_func=scheduler_func_opt)[0], )
scheduler_func=scheduler_func_opt, internal_seed=internal_seed)[0], )
class KSamplerAdvanced_inspire:
@@ -143,7 +158,7 @@ class KSamplerAdvanced_inspire:
return {"required":
{"model": ("MODEL",),
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
@@ -164,6 +179,7 @@ class KSamplerAdvanced_inspire:
"variation_method": (["linear", "slerp"],),
"noise_opt": ("NOISE_IMAGE",),
"scheduler_func_opt": ("SCHEDULER_FUNC",),
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
}
}
@@ -174,7 +190,7 @@ class KSamplerAdvanced_inspire:
@staticmethod
def sample(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,
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None, variation_method="linear", scheduler_func_opt=None):
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, callback=None, variation_method="linear", scheduler_func_opt=None, internal_seed=None):
force_full_denoise = True
if return_with_leftover_noise:
@@ -189,7 +205,7 @@ class KSamplerAdvanced_inspire:
denoise=denoise, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step,
force_full_denoise=force_full_denoise, noise_mode=noise_mode, incremental_seed_mode=batch_seed_mode,
variation_seed=variation_seed, variation_strength=variation_strength, noise=noise_opt, callback=callback, variation_method=variation_method,
scheduler_func=scheduler_func_opt)
scheduler_func=scheduler_func_opt, internal_seed=internal_seed)
def doit(self, *args, **kwargs):
return (self.sample(*args, **kwargs)[0],)
@@ -200,7 +216,7 @@ class KSampler_inspire_pipe:
def INPUT_TYPES(s):
return {"required":
{"basic_pipe": ("BASIC_PIPE",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
@@ -215,6 +231,7 @@ class KSampler_inspire_pipe:
"optional":
{
"scheduler_func_opt": ("SCHEDULER_FUNC",),
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
}
}
@@ -224,10 +241,10 @@ class KSampler_inspire_pipe:
CATEGORY = "InspirePack/a1111_compat"
def sample(self, basic_pipe, seed, steps, cfg, sampler_name, scheduler, latent_image, denoise, noise_mode, batch_seed_mode="comfy",
variation_seed=None, variation_strength=None, scheduler_func_opt=None):
variation_seed=None, variation_strength=None, scheduler_func_opt=None, internal_seed=None):
model, clip, vae, positive, negative = basic_pipe
latent = inspire_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise, noise_mode, incremental_seed_mode=batch_seed_mode,
variation_seed=variation_seed, variation_strength=variation_strength, scheduler_func=scheduler_func_opt)[0]
variation_seed=variation_seed, variation_strength=variation_strength, scheduler_func=scheduler_func_opt, internal_seed=internal_seed)[0]
return latent, vae
@@ -237,7 +254,7 @@ class KSamplerAdvanced_inspire_pipe:
return {"required":
{"basic_pipe": ("BASIC_PIPE",),
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed for the initial noise applied to the latent."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.5, "round": 0.01}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
@@ -255,6 +272,7 @@ class KSamplerAdvanced_inspire_pipe:
{
"noise_opt": ("NOISE_IMAGE",),
"scheduler_func_opt": ("SCHEDULER_FUNC",),
"internal_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "This is the seed used for generating noise in intermediate steps when using ancestral and SDE-based samplers.\nNOTE: If `noise_mode` is in GPU mode and `internal_seed` is the same as `seed`, the generated image may be distorted."}),
}
}
@@ -264,7 +282,7 @@ class KSamplerAdvanced_inspire_pipe:
CATEGORY = "InspirePack/a1111_compat"
def sample(self, basic_pipe, add_noise, noise_seed, steps, cfg, sampler_name, scheduler, latent_image, start_at_step, end_at_step, noise_mode, return_with_leftover_noise,
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, scheduler_func_opt=None):
denoise=1.0, batch_seed_mode="comfy", variation_seed=None, variation_strength=None, noise_opt=None, scheduler_func_opt=None, internal_seed=None):
model, clip, vae, positive, negative = basic_pipe
latent = KSamplerAdvanced_inspire().sample(model=model, add_noise=add_noise, noise_seed=noise_seed,
steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler,
@@ -272,7 +290,8 @@ class KSamplerAdvanced_inspire_pipe:
start_at_step=start_at_step, end_at_step=end_at_step,
noise_mode=noise_mode, return_with_leftover_noise=return_with_leftover_noise,
denoise=denoise, batch_seed_mode=batch_seed_mode, variation_seed=variation_seed,
variation_strength=variation_strength, noise_opt=noise_opt, scheduler_func_opt=scheduler_func_opt)[0]
variation_strength=variation_strength, noise_opt=noise_opt, scheduler_func_opt=scheduler_func_opt,
internal_seed=internal_seed)[0]
return latent, vae
+174
View File
@@ -8,6 +8,8 @@ from server import PromptServer
from .libs.utils import TaggedCache, any_typ
import logging
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
settings_file = os.path.join(root_dir, 'cache_settings.json')
try:
@@ -401,6 +403,174 @@ class CheckpointLoaderSimpleShared(nodes.CheckpointLoaderSimple):
return (None, cache_weak_hash(key))
class LoadDiffusionModelShared(nodes.UNETLoader):
@classmethod
def INPUT_TYPES(s):
return {"required": { "model_name": (folder_paths.get_filename_list("diffusion_models"), {"tooltip": "Diffusion Model Name"}),
"weight_dtype": (["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"],),
"key_opt": ("STRING", {"multiline": False, "placeholder": "If empty, use 'model_name' as the key."}),
"mode": (['Auto', 'Override Cache', 'Read Only'],),
}
}
RETURN_TYPES = ("MODEL", "STRING")
RETURN_NAMES = ("model", "cache key")
FUNCTION = "doit"
CATEGORY = "InspirePack/Backend"
def doit(self, model_name, weight_dtype, key_opt, mode='Auto'):
if mode == 'Read Only':
if key_opt.strip() == '':
raise Exception("[LoadDiffusionModelShared] key_opt cannot be omit if mode is 'Read Only'")
key = key_opt.strip()
elif key_opt.strip() == '':
key = f"{model_name}_{weight_dtype}"
else:
key = key_opt.strip()
if key not in cache or mode == 'Override Cache':
model = self.load_unet(model_name, weight_dtype)[0]
update_cache(key, "diffusion", (False, model))
print(f"[Inspire Pack] LoadDiffusionModelShared: diffusion model '{model_name}' is cached to '{key}'.")
else:
_, (_, model) = cache[key]
print(f"[Inspire Pack] LoadDiffusionModelShared: Cached diffusion model '{key}' is loaded. (Loading skip)")
return model, key
@staticmethod
def IS_CHANGED(model_name, weight_dtype, key_opt, mode='Auto'):
if mode == 'Read Only':
if key_opt.strip() == '':
raise Exception("[LoadDiffusionModelShared] key_opt cannot be omit if mode is 'Read Only'")
key = key_opt.strip()
elif key_opt.strip() == '':
key = f"{model_name}_{weight_dtype}"
else:
key = key_opt.strip()
if mode == 'Read Only':
return None, cache_weak_hash(key)
elif mode == 'Override Cache':
return model_name, key
return None, cache_weak_hash(key)
class LoadTextEncoderShared:
@classmethod
def INPUT_TYPES(s):
return {"required": { "model_name1": (folder_paths.get_filename_list("text_encoders"), ),
"model_name2": (["None"] + folder_paths.get_filename_list("text_encoders"), ),
"model_name3": (["None"] + folder_paths.get_filename_list("text_encoders"), ),
"type": (["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos", "sdxl", "flux", "hunyuan_video"], ),
"key_opt": ("STRING", {"multiline": False, "placeholder": "If empty, use 'model_name' as the key."}),
"mode": (['Auto', 'Override Cache', 'Read Only'],),
},
"optional": { "device": (["default", "cpu"], {"advanced": True}), }
}
RETURN_TYPES = ("CLIP", "STRING")
RETURN_NAMES = ("clip", "cache key")
FUNCTION = "doit"
CATEGORY = "InspirePack/Backend"
DESCRIPTION = \
("[Recipes single]\n"
"stable_diffusion: clip-l\n"
"stable_cascade: clip-g\n"
"sd3: t5 / clip-g / clip-l\n"
"stable_audio: t5\n"
"mochi: t5\n"
"cosmos: old t5 xxl\n\n"
"[Recipes dual]\n"
"sdxl: clip-l, clip-g\n"
"sd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\n"
"flux: clip-l, t5\n\n"
"[Recipes triple]\n"
"sd3: clip-l, clip-g, t5")
def doit(self, model_name1, model_name2, model_name3, type, key_opt, mode='Auto', device="default"):
if mode == 'Read Only':
if key_opt.strip() == '':
raise Exception("[LoadTextEncoderShared] key_opt cannot be omit if mode is 'Read Only'")
key = key_opt.strip()
elif key_opt.strip() == '':
key = model_name1
if model_name2 is not None:
key += f"_{model_name2}"
if model_name3 is not None:
key += f"_{model_name3}"
key += f"_{type}_{device}"
else:
key = key_opt.strip()
if key not in cache or mode == 'Override Cache':
if model_name2 != "None" and model_name3 != "None": # triple text encoder
if len({model_name1, model_name2, model_name3}) < 3:
logging.error("[LoadTextEncoderShared] The same model has been selected multiple times.")
raise ValueError("The same model has been selected multiple times.")
if type not in ["sd3"]:
logging.error("[LoadTextEncoderShared] Currently, the triple text encoder is only supported in `sd3`.")
raise ValueError("Currently, the triple text encoder is only supported in `sd3`.")
res = nodes.NODE_CLASS_MAPPINGS["TripleCLIPLoader"]().load_clip(model_name1, model_name2, model_name3)[0]
elif model_name2 != "None" or model_name3 != "None": # dual text encoder
second_model = model_name2 if model_name2 != "None" else model_name3
if model_name1 == second_model:
logging.error("[LoadTextEncoderShared] You have selected the same model for both.")
raise ValueError("[LoadTextEncoderShared] You have selected the same model for both.")
if type not in ["sdxl", "sd3", "flux", "hunyuan_video"]:
logging.error("[LoadTextEncoderShared] Currently, the triple text encoder is only supported in `sdxl, sd3, flux, hunyuan_video`.")
raise ValueError("Currently, the triple text encoder is only supported in `sdxl, sd3, flux, hunyuan_video`.")
res = nodes.NODE_CLASS_MAPPINGS["DualCLIPLoader"]().load_clip(model_name1, second_model, type=type, device=device)[0]
else: # single text encoder
if type not in ["stable_diffusion", "stable_cascade", "sd3", "stable_audio", "mochi", "ltxv", "pixart", "cosmos"]:
logging.error("[LoadTextEncoderShared] Currently, the single text encoder is only supported in `stable_diffusion, stable_cascade, sd3, stable_audio, mochi, ltxv, pixart, cosmos`.")
raise ValueError("Currently, the single text encoder is only supported in `stable_diffusion, stable_cascade, sd3, stable_audio, mochi, ltxv, pixart, cosmos`.")
res = nodes.NODE_CLASS_MAPPINGS["CLIPLoader"]().load_clip(model_name1, type=type, device=device)[0]
update_cache(key, "diffusion", (False, res))
print(f"[Inspire Pack] LoadTextEncoderShared: text encoder model set is cached to '{key}'.")
else:
_, (_, res) = cache[key]
print(f"[Inspire Pack] LoadTextEncoderShared: Cached text encoder model set '{key}' is loaded. (Loading skip)")
return res, key
@staticmethod
def IS_CHANGED(model_name1, model_name2, model_name3, type, key_opt, mode='Auto', device="default"):
if mode == 'Read Only':
if key_opt.strip() == '':
raise Exception("[LoadTextEncoderShared] key_opt cannot be omit if mode is 'Read Only'")
key = key_opt.strip()
elif key_opt.strip() == '':
key = model_name1
if model_name2 is not None:
key += f"_{model_name2}"
if model_name3 is not None:
key += f"_{model_name3}"
key += f"_{type}_{device}"
else:
key = key_opt.strip()
if mode == 'Read Only':
return None, cache_weak_hash(key)
elif mode == 'Override Cache':
return f"{model_name1}_{model_name2}_{model_name3}_{type}_{device}", key
return None, cache_weak_hash(key)
class StableCascade_CheckpointLoader:
@classmethod
def INPUT_TYPES(s):
@@ -558,6 +728,8 @@ NODE_CLASS_MAPPINGS = {
"RemoveBackendDataNumberKey //Inspire": RemoveBackendDataNumberKey,
"ShowCachedInfo //Inspire": ShowCachedInfo,
"CheckpointLoaderSimpleShared //Inspire": CheckpointLoaderSimpleShared,
"LoadDiffusionModelShared //Inspire": LoadDiffusionModelShared,
"LoadTextEncoderShared //Inspire": LoadTextEncoderShared,
"StableCascade_CheckpointLoader //Inspire": StableCascade_CheckpointLoader,
"IsCached //Inspire": IsCached,
# "CacheBridge //Inspire": CacheBridge,
@@ -574,6 +746,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"RemoveBackendDataNumberKey //Inspire": "Remove Backend Data [NumberKey] (Inspire)",
"ShowCachedInfo //Inspire": "Show Cached Info (Inspire)",
"CheckpointLoaderSimpleShared //Inspire": "Shared Checkpoint Loader (Inspire)",
"LoadDiffusionModelShared //Inspire": "Shared Diffusion Model Loader (Inspire)",
"LoadTextEncoderShared //Inspire": "Shared Text Encoder Loader (Inspire)",
"StableCascade_CheckpointLoader //Inspire": "Stable Cascade Checkpoint Loader (Inspire)",
"IsCached //Inspire": "Is Cached (Inspire)",
# "CacheBridge //Inspire": "Cache Bridge (Inspire)"
+2 -2
View File
@@ -18,7 +18,7 @@ class LoadImagesFromDirBatch:
},
"optional": {
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
"start_index": ("INT", {"default": 0, "min": -1, "step": 1}),
"start_index": ("INT", {"default": 0, "min": -1, "max": 0xffffffffffffffff, "step": 1}),
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
}
}
@@ -121,7 +121,7 @@ class LoadImagesFromDirList:
},
"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": 0, "max": 0xffffffffffffffff, "step": 1}),
"load_always": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
}
}
+22 -6
View File
@@ -272,7 +272,17 @@ def populate_wildcards(json_data):
for k, v in prompt.items():
if 'class_type' in v and v['class_type'] == 'WildcardEncode //Inspire':
inputs = v['inputs']
if inputs['mode'] and isinstance(inputs['populated_text'], str):
# legacy adapter
if isinstance(inputs['mode'], bool):
if inputs['mode']:
new_mode = 'populate'
else:
new_mode = 'fixed'
inputs['mode'] = new_mode
if inputs['mode'] == 'populate' and isinstance(inputs['populated_text'], str):
if isinstance(inputs['seed'], list):
try:
input_node = prompt[inputs['seed'][0]]
@@ -293,14 +303,17 @@ def populate_wildcards(json_data):
input_seed = int(inputs['seed'])
inputs['populated_text'] = wildcard_process(text=inputs['wildcard_text'], seed=input_seed)
inputs['mode'] = False
inputs['mode'] = 'reproduce'
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "populated_text", "type": "text", "data": inputs['populated_text']})
updated_widget_values[k] = inputs['populated_text']
if inputs['mode'] == 'reproduce':
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "mode", "type": "text", "value": 'populate'})
elif 'class_type' in v and v['class_type'] == 'MakeBasicPipe //Inspire':
inputs = v['inputs']
if inputs['wildcard_mode'] and (isinstance(inputs['positive_populated_text'], str) or isinstance(inputs['negative_populated_text'], str)):
if inputs['wildcard_mode'] == 'populate' and (isinstance(inputs['positive_populated_text'], str) or isinstance(inputs['negative_populated_text'], str)):
if isinstance(inputs['seed'], list):
try:
input_node = prompt[inputs['seed'][0]]
@@ -328,9 +341,12 @@ def populate_wildcards(json_data):
inputs['negative_populated_text'] = wildcard_process(text=inputs['negative_wildcard_text'], seed=input_seed)
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "negative_populated_text", "type": "text", "data": inputs['negative_populated_text']})
inputs['wildcard_mode'] = False
inputs['wildcard_mode'] = 'reproduce'
mbp_updated_widget_values[k] = inputs['positive_populated_text'], inputs['negative_populated_text']
if inputs['wildcard_mode'] == 'reproduce':
server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": "wildcard_mode", "type": "text", "value": 'populate'})
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 extra_pnginfo['workflow'] is not None and 'nodes' in extra_pnginfo['workflow']:
@@ -338,11 +354,11 @@ def populate_wildcards(json_data):
key = str(node['id'])
if key in updated_widget_values:
node['widgets_values'][3] = updated_widget_values[key]
node['widgets_values'][4] = False
node['widgets_values'][4] = 'reproduce'
if key in mbp_updated_widget_values:
node['widgets_values'][7] = mbp_updated_widget_values[key][0]
node['widgets_values'][8] = mbp_updated_widget_values[key][1]
node['widgets_values'][5] = False
node['widgets_values'][5] = 'reproduce'
def force_reset_useless_params(json_data):
+49 -19
View File
@@ -60,21 +60,23 @@ class LoadPromptsFromDir:
},
"optional": {
"reload": ("BOOLEAN", { "default": False, "label_on": "if file changed", "label_off": "if value changed"}),
"load_cap": ("INT", {"default": 0, "min": 0, "step": 1, "advanced": True, "tooltip": "The amount of prompts to load at once:\n0: Load all\n1 or higher: Load a specified number"}),
"start_index": ("INT", {"default": 0, "min": -1, "step": 1, "max": 0xffffffffffffffff, "advanced": True, "tooltip": "Starting index for loading prompts:\n-1: The last prompt\n0 or higher: Load from the specified index"}),
}
}
RETURN_TYPES = ("ZIPPED_PROMPT", "INT")
RETURN_NAMES = ("zipped_prompt", "count")
OUTPUT_IS_LIST = (True,)
RETURN_TYPES = ("ZIPPED_PROMPT", "INT", "INT")
RETURN_NAMES = ("zipped_prompt", "count", "remaining_count")
OUTPUT_IS_LIST = (True, False, False)
FUNCTION = "doit"
CATEGORY = "InspirePack/Prompt"
@staticmethod
def IS_CHANGED(prompt_dir, reload=False):
def IS_CHANGED(prompt_dir, reload=False, load_cap=0, start_index=-1):
if not reload:
return prompt_dir
return prompt_dir, load_cap, start_index
else:
candidates = []
for d in folder_paths.get_folder_paths('inspire_prompts'):
@@ -100,10 +102,10 @@ class LoadPromptsFromDir:
break
md5.update(chunk)
return md5.hexdigest()
return md5.hexdigest(), load_cap, start_index
@staticmethod
def doit(prompt_dir, reload=False):
def doit(prompt_dir, reload=False, load_cap=0, start_index=-1):
candidates = []
for d in folder_paths.get_folder_paths('inspire_prompts'):
candidates.append(os.path.join(d, prompt_dir))
@@ -140,7 +142,15 @@ class LoadPromptsFromDir:
except Exception as 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, len(prompts),)
# slicing [start_index ~ start_index + load_cap]
total_prompts = len(prompts)
prompts = prompts[start_index:]
remaining_count = False
if load_cap > 0:
remaining_count = max(0, len(prompts) - load_cap)
prompts = prompts[:load_cap]
return prompts, total_prompts, remaining_count
class LoadPromptsFromFile:
@@ -165,26 +175,28 @@ class LoadPromptsFromFile:
"optional": {
"text_data_opt": ("STRING", {"defaultInput": True}),
"reload": ("BOOLEAN", {"default": False, "label_on": "if file changed", "label_off": "if value changed"}),
"load_cap": ("INT", {"default": 0, "min": 0, "step": 1, "advanced": True, "tooltip": "The amount of prompts to load at once:\n0: Load all\n1 or higher: Load a specified number"}),
"start_index": ("INT", {"default": 0, "min": -1, "max": 0xffffffffffffffff, "step": 1, "advanced": True, "tooltip": "Starting index for loading prompts:\n-1: The last prompt\n0 or higher: Load from the specified index"}),
}
}
RETURN_TYPES = ("ZIPPED_PROMPT", "INT")
RETURN_NAMES = ("zipped_prompt", "count")
OUTPUT_IS_LIST = (True,)
RETURN_TYPES = ("ZIPPED_PROMPT", "INT", "INT")
RETURN_NAMES = ("zipped_prompt", "count", "remaining_count")
OUTPUT_IS_LIST = (True, False, False)
FUNCTION = "doit"
CATEGORY = "InspirePack/Prompt"
@staticmethod
def IS_CHANGED(prompt_file, text_data_opt=None, reload=False):
def IS_CHANGED(prompt_file, text_data_opt=None, reload=False, load_cap=0, start_index=-1):
md5 = hashlib.md5()
if text_data_opt is not None:
md5.update(text_data_opt)
return md5.hexdigest()
return md5.hexdigest(), load_cap, start_index
elif not reload:
return prompt_file
return prompt_file, load_cap, start_index
else:
matched_path = None
for x in folder_paths.get_folder_paths('inspire_prompts'):
@@ -204,10 +216,10 @@ class LoadPromptsFromFile:
break
md5.update(chunk)
return md5.hexdigest()
return md5.hexdigest(), load_cap, start_index
@staticmethod
def doit(prompt_file, text_data_opt=None, reload=False):
def doit(prompt_file, text_data_opt=None, reload=False, load_cap=0, start_index=-1):
matched_path = None
for d in folder_paths.get_folder_paths('inspire_prompts'):
matched_path = os.path.join(d, prompt_file)
@@ -247,7 +259,15 @@ class LoadPromptsFromFile:
except Exception as 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, len(prompts),)
# slicing [start_index ~ start_index + load_cap]
total_prompts = len(prompts)
prompts = prompts[start_index:]
remaining_count = 0
if load_cap > 0:
remaining_count = max(0, len(prompts) - load_cap)
prompts = prompts[:load_cap]
return prompts, total_prompts, remaining_count
class LoadSinglePromptFromFile:
@@ -557,7 +577,13 @@ class WildcardEncodeInspire:
"weight_interpretation": (["comfy", "A1111", "compel", "comfy++", "down_weight"], {'default': 'comfy++'}),
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Wildcard Prompt (User Input)'}),
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Prompt (Will be generated automatically)'}),
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
"mode": (["populate", "fixed", "reproduce"], {"default": "populate", "tooltip":
"populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
"fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode.\n"
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"), ),
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
@@ -598,7 +624,11 @@ class MakeBasicPipe:
"Add selection to": ("BOOLEAN", {"default": True, "label_on": "Positive", "label_off": "Negative"}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
"wildcard_mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
"wildcard_mode": (["populate", "fixed", "reproduce"], {"default": "populate", "tooltip":
"populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
"fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode.\n"
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
}),
"positive_populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Positive Prompt (Will be generated automatically)'}),
"negative_populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, 'placeholder': 'Populated Negative Prompt (Will be generated automatically)'}),
+19 -7
View File
@@ -95,14 +95,20 @@ app.registerExtension({
// mode combo
Object.defineProperty(mode_widget, "value", {
set: (value) => {
node._mode_value = value == true || value == "Populate";
populated_text_widget.inputEl.disabled = value == true || value == "Populate";
if(value == true)
node._mode_value = "populate";
else if(value == false)
node._mode_value = "fixed";
else
node._mode_value = value; // combo value
populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
},
get: () => {
if(node._mode_value != undefined)
return node._mode_value;
else
return true;
return 'populate';
}
});
}
@@ -180,15 +186,21 @@ app.registerExtension({
// mode combo
Object.defineProperty(mode_widget, "value", {
set: (value) => {
pos_populated_text_widget.inputEl.disabled = node._mode_value;
neg_populated_text_widget.inputEl.disabled = node._mode_value;
node._mode_value = value;
if(value == true)
node._mode_value = "populate";
else if(value == false)
node._mode_value = "fixed";
else
node._mode_value = value; // combo value
pos_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
neg_populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
},
get: () => {
if(node._mode_value != undefined)
return node._mode_value;
else
return true;
return 'populate';
}
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-inspire-pack"
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"
version = "1.13"
license = { file = "LICENSE" }
dependencies = ["matplotlib", "cachetools"]