C / workflow_api.py
q6's picture
Add grouped custom sampling options
1b72208
Raw History Blame Contribute Delete
43.7 kB
# Imports
import json
import os
import sys
from typing import Sequence, Mapping, Any, Union
ALIGN_MODEL_TYPE = "SDXL"
ALIGN_SCHEDULER = "AlignYourSteps"
GENERATION = 2
ANIMA_CLIP = "2_qwen_3_06b_base.safetensors"
GRID_SIZE = 5
LATENT_SCALE = 8
REGIONAL_FEATHER = .5
DETAILER_GUIDE_SIZE = 768
DETAILER_MAX_SIZE = 1024
DETAILER_THRESHOLD = .5
DETAILER_DILATION = 10
DETAILER_CROP = 3
DETAILER_FEATHER = 5
DETAILER_DROP_SIZE = 10
STYLE_IPADAPTER = "2_ip-adapter-plus_sdxl_vit-h.safetensors"
STYLE_CLIP_VISION = "1_CLIP-ViT-H-fp16.safetensors"
STYLE_WEIGHT_TYPE = "style transfer"
STYLE_EMBEDS_SCALING = "V only"
STYLE_IMAGE_SIZE = 1024
def upscale_size(width, height, scale):
return tuple(
round(value / LATENT_SCALE * scale) * LATENT_SCALE
for value in (width, height)
)
def mask_box(x, y, width, height, image_width, image_height):
x1 = round(x * image_width)
y1 = round(y * image_height)
x2 = round((x + width) * image_width)
y2 = round((y + height) * image_height)
box_width = x2 - x1
box_height = y2 - y1
x_edges = int(x1 > 0) + int(x2 < image_width)
y_edges = int(y1 > 0) + int(y2 < image_height)
feather_x = min(
round(image_width / GRID_SIZE * REGIONAL_FEATHER),
box_width // max(1, x_edges),
)
feather_y = min(
round(image_height / GRID_SIZE * REGIONAL_FEATHER),
box_height // max(1, y_edges),
)
return (
x1,
y1,
box_width,
box_height,
feather_x if x1 else 0,
feather_y if y1 else 0,
feather_x if x2 < image_width else 0,
feather_y if y2 < image_height else 0,
)
def is_anima_model(name):
number, separator, model = (
name.rsplit("/", 1)[-1].casefold().partition("_")
)
return (
bool(separator)
and number.isdigit()
and model.startswith("anima")
and not model.startswith("animag")
)
def get_value_at_index(obj: Union[Sequence, Mapping], index: int) -> Any:
"""Return a sequence or mapping result item by index."""
try:
return obj[index]
except KeyError:
return obj["result"][index]
def get_comfyui_path() -> str:
"""Return the configured ComfyUI path, preferring COMFYUI_PATH when set."""
comfyui_path = os.environ.get("COMFYUI_PATH")
if comfyui_path:
return comfyui_path
return find_path("ComfyUI")
def find_path(name: str, path: str = None) -> str:
"""Recursively search parent folders until the named entry is found."""
if path is None:
path = os.getcwd()
if name in os.listdir(path):
path_name = os.path.join(path, name)
print(f"{name} found: {path_name}")
return path_name
parent_directory = os.path.dirname(path)
if parent_directory == path:
return None
return find_path(name, parent_directory)
def add_comfyui_directory_to_sys_path() -> None:
"""Add the ComfyUI checkout to sys.path."""
comfyui_path = get_comfyui_path()
if comfyui_path is not None and os.path.isdir(comfyui_path):
if comfyui_path in sys.path:
sys.path.remove(comfyui_path)
sys.path.insert(0, comfyui_path)
print(f"'{comfyui_path}' added to sys.path")
def add_extra_model_paths() -> None:
"""Load ComfyUI extra model paths configuration when available."""
try:
from main import load_extra_path_config
except ImportError:
print(
"Could not import load_extra_path_config from main.py. Looking in utils.extra_config instead."
)
from utils.extra_config import load_extra_path_config
extra_model_paths = find_path("extra_model_paths.yaml")
if extra_model_paths is not None:
load_extra_path_config(extra_model_paths)
else:
print("Could not find the extra_model_paths config file.")
def bootstrap_comfyui_runtime() -> None:
"""Mirror the allocator-related ComfyUI startup steps before torch import."""
add_comfyui_directory_to_sys_path()
import comfy.options
comfy.options.enable_args_parsing()
from comfy.cli_args import args
if os.name == "nt":
os.environ["MIMALLOC_PURGE_DELAY"] = "0"
if args.default_device is not None:
default_dev = args.default_device
devices = list(range(32))
devices.remove(default_dev)
devices.insert(0, default_dev)
devices = ",".join(map(str, devices))
os.environ["CUDA_VISIBLE_DEVICES"] = str(devices)
os.environ["HIP_VISIBLE_DEVICES"] = str(devices)
if args.cuda_device is not None:
os.environ["CUDA_VISIBLE_DEVICES"] = str(args.cuda_device)
os.environ["HIP_VISIBLE_DEVICES"] = str(args.cuda_device)
os.environ["ASCEND_RT_VISIBLE_DEVICES"] = str(args.cuda_device)
if args.oneapi_device_selector is not None:
os.environ["ONEAPI_DEVICE_SELECTOR"] = args.oneapi_device_selector
if args.deterministic and "CUBLAS_WORKSPACE_CONFIG" not in os.environ:
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
import cuda_malloc
if "rocm" in cuda_malloc.get_torch_version_noimport():
os.environ["OCL_SET_SVM_SIZE"] = "262144"
def cleanup_comfyui_runtime(unload_models: bool | None = None) -> None:
"""Best-effort cleanup for embedded or repeated generated-script execution."""
import gc
def run_cleanup_hook(name: str, should_run: bool = True) -> None:
if not should_run or not hasattr(model_management, name):
return
cleanup_fn = getattr(model_management, name)
try:
cleanup_fn()
except Exception as exc:
warnings.warn(
f"ComfyUI cleanup hook {name} failed during teardown: {exc}",
RuntimeWarning,
stacklevel=2,
)
should_unload = unload_models
if should_unload is None:
should_unload = os.environ.get(
"COMFYUI_TOPYTHON_UNLOAD_MODELS", ""
).lower() in {
"1",
"true",
"yes",
"on",
}
try:
import comfy.model_management as model_management
except ModuleNotFoundError:
gc.collect()
return
run_cleanup_hook("cleanup_models_gc")
run_cleanup_hook("unload_all_models", should_run=should_unload)
run_cleanup_hook("soft_empty_cache")
gc.collect()
def import_custom_nodes() -> None:
"""Initialize ComfyUI custom nodes in the exporter runtime."""
comfyui_path = get_comfyui_path()
if comfyui_path and comfyui_path not in sys.path:
sys.path.insert(0, comfyui_path)
import asyncio
import execution
from nodes import init_extra_nodes
if comfyui_path in sys.path:
sys.path.remove(comfyui_path)
sys.path.insert(0, comfyui_path)
import server
from app.assets.manager import default_asset_manager
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
server_instance = server.PromptServer(loop, default_asset_manager())
execution.PromptQueue(server_instance)
loop.run_until_complete(init_extra_nodes())
finally:
asyncio.set_event_loop(None)
loop.close()
# Workflow data
def build_workflow() -> dict[str, Any]:
return {
"1": {
"inputs": {"ckpt_name": "52_novaAnimeXL_ilV190.safetensors"},
"class_type": "CheckpointLoaderSimple",
"_meta": {"title": "Loader"},
},
"2": {
"inputs": {"text": ["118", 0], "clip": ["117", 1]},
"class_type": "CLIPTextEncode",
"_meta": {"title": "CLIP Text Encode (Prompt)"},
},
"3": {
"inputs": {
"text": "(censored, mosaic censoring, bar censor:1.1), bad "
"quality, worst quality, worst detail, bad anatomy, "
"extra fingers, extra toes, extra legs, 4 toes, 6 "
"toes, 4 fingers, 6 fingers, malformed fingers, "
"extra limbs, missing fingers, extra arms, censored, "
"deformed, disfigured, text, (multiple views:1.1)",
"clip": ["28", 1],
},
"class_type": "CLIPTextEncode",
"_meta": {"title": "CLIP Text Encode (Prompt)"},
},
"5": {
"inputs": {
"seed": 809278554234612,
"steps": 16,
"cfg": 4,
"sampler_name": "euler_ancestral",
"scheduler": "karras",
"denoise": 1,
"model": ["117", 0],
"positive": ["2", 0],
"negative": ["3", 0],
"latent_image": ["27", 0],
},
"class_type": "KSampler",
"_meta": {"title": "KSampler"},
},
"10": {
"inputs": {
"lora_name": "8_bikabaka.safetensors",
"strength_model": 0.3,
"strength_clip": 0,
"model": ["1", 0],
"clip": ["1", 1],
},
"class_type": "LoraLoader",
"_meta": {"title": "Load LoRA"},
},
"27": {
"inputs": {"width": 1152, "height": 896, "batch_size": 1},
"class_type": "EmptyLatentImage",
"_meta": {"title": "Empty Landscape"},
},
"28": {
"inputs": {
"lora_name": "43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
"strength_model": 0.4,
"strength_clip": 0,
"model": ["38", 0],
"clip": ["38", 1],
},
"class_type": "LoraLoader",
"_meta": {"title": "Load LoRA"},
},
"38": {
"inputs": {
"lora_name": "42_アップスケール_remacri_original.pt",
"strength_model": 0.4,
"strength_clip": 0,
"model": ["10", 0],
"clip": ["10", 1],
},
"class_type": "LoraLoader",
"_meta": {"title": "Load LoRA"},
},
"45": {
"inputs": {"vae_name": "3_sdxlVAE_sdxlVAE.safetensors"},
"class_type": "VAELoader",
"_meta": {"title": "Load VAE"},
},
"56": {
"inputs": {"samples": ["5", 0], "vae": ["45", 0]},
"class_type": "VAEDecode",
"_meta": {"title": "VAE Decode"},
},
"72": {
"inputs": {"images": ["56", 0]},
"class_type": "PreviewImage",
"_meta": {"title": "Preview Image"},
},
"117": {
"inputs": {
"lora_name": "5_add_saturation_XL.safetensors",
"strength_model": -1.4,
"strength_clip": 0,
"model": ["28", 0],
"clip": ["28", 1],
},
"class_type": "LoraLoader",
"_meta": {"title": "Load LoRA (Model and CLIP)"},
},
"118": {
"inputs": {
"string_a": "%prompt%",
"string_b": "",
"delimiter": "",
},
"class_type": "StringConcatenate",
"_meta": {"title": "Concatenate Text"},
},
}
def build_extra_pnginfo() -> dict[str, Any] | None:
return {
"workflow": {
"id": "e69619af-5ceb-4a83-821d-68180291905e",
"revision": 0,
"last_node_id": 121,
"last_link_id": 57,
"nodes": [
{
"id": 27,
"type": "EmptyLatentImage",
"pos": [100, 358],
"size": [270, 106],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [{"name": "LATENT", "type": "LATENT", "links": [24]}],
"title": "Empty Landscape",
"properties": {"Node name for S&R": "EmptyLatentImage"},
"widgets_values": [1152, 896, 1],
},
{
"id": 45,
"type": "VAELoader",
"pos": [100, 594],
"size": [270, 58],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [{"name": "VAE", "type": "VAE", "links": [32]}],
"properties": {"Node name for S&R": "VAELoader"},
"widgets_values": ["3_sdxlVAE_sdxlVAE.safetensors"],
},
{
"id": 56,
"type": "VAEDecode",
"pos": [2948.649165895271, 134.76727061509087],
"size": [140, 46],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{"name": "samples", "type": "LATENT", "link": 31},
{"name": "vae", "type": "VAE", "link": 32},
],
"outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [33]}],
"properties": {"Node name for S&R": "VAEDecode"},
"widgets_values": [],
},
{
"id": 10,
"type": "LoraLoader",
"pos": [600, 130],
"size": [290.43334045410154, 126],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{"name": "model", "type": "MODEL", "link": 25},
{"name": "clip", "type": "CLIP", "link": 26},
],
"outputs": [
{"name": "MODEL", "type": "MODEL", "links": [29]},
{"name": "CLIP", "type": "CLIP", "links": [30]},
],
"title": "Load LoRA",
"properties": {"Node name for S&R": "LoraLoader"},
"widgets_values": ["8_bikabaka.safetensors", 0.3, 0],
},
{
"id": 72,
"type": "PreviewImage",
"pos": [3188.649165895271, 134.76727061509087],
"size": [285.77604360195164, 258],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [{"name": "images", "type": "IMAGE", "link": 33}],
"outputs": [{"name": "images", "type": "IMAGE", "links": None}],
"properties": {"Node name for S&R": "PreviewImage"},
"widgets_values": [],
},
{
"id": 1,
"type": "CheckpointLoaderSimple",
"pos": [100, 130],
"size": [270, 98],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{"name": "MODEL", "type": "MODEL", "links": [25]},
{"name": "CLIP", "type": "CLIP", "links": [26]},
{"name": "VAE", "type": "VAE", "links": None},
],
"title": "Loader",
"properties": {"Node name for S&R": "CheckpointLoaderSimple"},
"widgets_values": ["52_novaAnimeXL_ilV190.safetensors"],
},
{
"id": 2,
"type": "CLIPTextEncode",
"pos": [1880.866680908203, 130],
"size": [400, 200],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{"name": "clip", "type": "CLIP", "link": 49},
{
"name": "text",
"type": "STRING",
"widget": {"name": "text"},
"link": 54,
},
],
"outputs": [
{"name": "CONDITIONING", "type": "CONDITIONING", "links": [39]}
],
"properties": {"Node name for S&R": "CLIPTextEncode"},
"widgets_values": [""],
},
{
"id": 5,
"type": "KSampler",
"pos": [2578.649165895271, 134.76727061509087],
"size": [270, 262],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{"name": "model", "type": "MODEL", "link": 50},
{"name": "positive", "type": "CONDITIONING", "link": 39},
{"name": "negative", "type": "CONDITIONING", "link": 23},
{"name": "latent_image", "type": "LATENT", "link": 24},
],
"outputs": [{"name": "LATENT", "type": "LATENT", "links": [31]}],
"properties": {"Node name for S&R": "KSampler"},
"widgets_values": [
809278554234612,
"randomize",
16,
4,
"euler_ancestral",
"karras",
1,
],
},
{
"id": 38,
"type": "LoraLoader",
"pos": [990, 130],
"size": [290.43334045410154, 126],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{"name": "model", "type": "MODEL", "link": 29},
{"name": "clip", "type": "CLIP", "link": 30},
],
"outputs": [
{"name": "MODEL", "type": "MODEL", "links": [27]},
{"name": "CLIP", "type": "CLIP", "links": [28]},
],
"title": "Load LoRA",
"properties": {"Node name for S&R": "LoraLoader"},
"widgets_values": ["42_アップスケール_remacri_original.pt", 0.4, 0],
},
{
"id": 28,
"type": "LoraLoader",
"pos": [1380, 130],
"size": [290.43334045410154, 126],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{"name": "model", "type": "MODEL", "link": 27},
{"name": "clip", "type": "CLIP", "link": 28},
],
"outputs": [
{"name": "MODEL", "type": "MODEL", "links": [47]},
{"name": "CLIP", "type": "CLIP", "links": [20, 48]},
],
"title": "Load LoRA",
"properties": {"Node name for S&R": "LoraLoader"},
"widgets_values": [
"43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
0.4,
0,
],
},
{
"id": 117,
"type": "LoraLoader",
"pos": [1523.7427746854546, 341.08039710943746],
"size": [290.43334045410154, 126],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{"name": "model", "type": "MODEL", "link": 47},
{"name": "clip", "type": "CLIP", "link": 48},
],
"outputs": [
{"name": "MODEL", "type": "MODEL", "links": [50]},
{"name": "CLIP", "type": "CLIP", "links": [49]},
],
"properties": {"Node name for S&R": "LoraLoader"},
"widgets_values": ["5_add_saturation_XL.safetensors", -1.4, 0],
},
{
"id": 118,
"type": "StringConcatenate",
"pos": [1363.7030337022063, -256.7007293998441],
"size": [400, 200],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [{"name": "STRING", "type": "STRING", "links": [54]}],
"properties": {"Node name for S&R": "StringConcatenate"},
"widgets_values": [
"%prompt%",
"",
"",
],
},
{
"id": 3,
"type": "CLIPTextEncode",
"pos": [1881.5394309031356, 459.32725000506747],
"size": [400, 200],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [{"name": "clip", "type": "CLIP", "link": 20}],
"outputs": [
{"name": "CONDITIONING", "type": "CONDITIONING", "links": [23]}
],
"properties": {"Node name for S&R": "CLIPTextEncode"},
"widgets_values": [
"(censored, mosaic censoring, bar "
"censor:1.1), bad quality, worst "
"quality, worst detail, bad "
"anatomy, extra fingers, extra "
"toes, extra legs, 4 toes, 6 toes, "
"4 fingers, 6 fingers, malformed "
"fingers, extra limbs, missing "
"fingers, extra arms, censored, "
"deformed, disfigured, text, "
"(multiple views:1.1)"
],
},
],
"links": [
[20, 28, 1, 3, 0, "CLIP"],
[23, 3, 0, 5, 2, "CONDITIONING"],
[24, 27, 0, 5, 3, "LATENT"],
[25, 1, 0, 10, 0, "MODEL"],
[26, 1, 1, 10, 1, "CLIP"],
[27, 38, 0, 28, 0, "MODEL"],
[28, 38, 1, 28, 1, "CLIP"],
[29, 10, 0, 38, 0, "MODEL"],
[30, 10, 1, 38, 1, "CLIP"],
[31, 5, 0, 56, 0, "LATENT"],
[32, 45, 0, 56, 1, "VAE"],
[33, 56, 0, 72, 0, "IMAGE"],
[39, 2, 0, 5, 1, "CONDITIONING"],
[47, 28, 0, 117, 0, "MODEL"],
[48, 28, 1, 117, 1, "CLIP"],
[49, 117, 1, 2, 0, "CLIP"],
[50, 117, 0, 5, 0, "MODEL"],
[54, 118, 0, 2, 1, "STRING"],
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.6303940863128564,
"offset": [-602.8886169463092, 486.2591310892753],
},
"frontendVersion": "1.45.20",
},
"version": 0.4,
}
}
def image_metadata(
config,
seeds,
detailer_seeds,
detailer_vaes,
vaes,
regions,
environment_start,
global_strength,
):
api = {}
def add(class_type, inputs):
node_id = str(len(api) + 1)
api[node_id] = {"inputs": inputs, "class_type": class_type}
return node_id
style_images = {}
style_pipeline = None
style_clip_vision = None
def style_config(stage):
if stage == "second" and config.get("second_style_images"):
return (
"second",
config["second_style_images"],
config["second_style_weight"],
config["second_style_end"],
)
images = config.get("style_images")
scope = config.get("style_scope", "generation")
enabled = (
stage == "first"
or stage == "second" and scope in ("generation", "all")
or stage == "detailer" and scope == "all"
)
if images and enabled:
return "first", images, config["style_weight"], config["style_end"]
return None
def apply_style(model, stage):
nonlocal style_pipeline, style_clip_vision
values = style_config(stage)
if values is None:
return model
key, names, weight, end = values
if key not in style_images:
style_image = None
for name in names:
image = [add("LoadImage", {"image": name}), 0]
image = [add("ImageScale", {
"image": image,
"upscale_method": "lanczos",
"width": STYLE_IMAGE_SIZE,
"height": STYLE_IMAGE_SIZE,
"crop": "center",
}), 0]
if style_image is None:
style_image = image
else:
style_image = [add("ImageBatch", {
"image1": style_image,
"image2": image,
}), 0]
style_images[key] = style_image
if style_pipeline is None:
style_pipeline = [add("IPAdapterModelLoader", {
"ipadapter_file": STYLE_IPADAPTER,
}), 0]
style_clip_vision = [add("CLIPVisionLoader", {
"clip_name": STYLE_CLIP_VISION,
}), 0]
return [add("IPAdapterAdvanced", {
"model": model,
"ipadapter": style_pipeline,
"clip_vision": style_clip_vision,
"image": style_images[key],
"weight": weight,
"weight_type": STYLE_WEIGHT_TYPE,
"combine_embeds": "average",
"start_at": 0,
"end_at": end,
"embeds_scaling": STYLE_EMBEDS_SCALING,
}), 0]
def load_chain(model_name, loras):
if is_anima_model(model_name):
model = [add("UNETLoader", {
"unet_name": model_name,
"weight_dtype": "default",
}), 0]
clip = [add("CLIPLoader", {
"clip_name": ANIMA_CLIP,
"type": "stable_diffusion",
"device": "default",
}), 0]
else:
node_id = add(
"CheckpointLoaderSimple",
{"ckpt_name": model_name},
)
model = [node_id, 0]
clip = [node_id, 1]
for lora in loras:
node_id = add("LoraLoader", {
"lora_name": lora["name"],
"strength_model": lora["strength"],
"strength_clip": lora["clip"],
"model": model,
"clip": clip,
})
model = [node_id, 0]
clip = [node_id, 1]
return model, clip
def custom_sampler(sampler_name, model):
prefix, separator, name = sampler_name.partition(":")
if not separator:
return None
if prefix == "ppm-dyn":
return [add("DynSamplerSelect", {
"sampler_name": name,
"eta": 1,
"s_dy_pow": -1,
"s_extra_steps": False,
}), 0]
if prefix == "ppm-cfgpp":
return [add("CFGPPSamplerSelect", {
"sampler_name": name,
"eta": 1,
"s_gamma_start": 0,
"s_gamma_end": 1,
"s_extra_steps": False,
}), 0]
if prefix == "ppm":
return [add("PPMSamplerSelect", {
"sampler_name": name,
"model": model,
"cfg_pp": False,
"s_sigma_diff": 2,
}), 0]
return None
def sample(
model,
seed,
steps,
cfg,
sampler_name,
scheduler,
positive,
negative,
latent,
denoise,
):
sampler = custom_sampler(sampler_name, model)
if scheduler != ALIGN_SCHEDULER and sampler is None:
return [add("KSampler", {
"seed": seed,
"steps": steps,
"cfg": cfg,
"sampler_name": sampler_name,
"scheduler": scheduler,
"denoise": denoise,
"model": model,
"positive": positive,
"negative": negative,
"latent_image": latent,
}), 0]
if scheduler == ALIGN_SCHEDULER:
sigmas = [add("AlignYourStepsScheduler", {
"model_type": ALIGN_MODEL_TYPE,
"steps": steps,
"denoise": denoise,
}), 0]
else:
sigmas = [add("BasicScheduler", {
"model": model,
"scheduler": scheduler,
"steps": steps,
"denoise": denoise,
}), 0]
if sampler is None:
sampler = [add("KSamplerSelect", {
"sampler_name": sampler_name,
}), 0]
return [add("SamplerCustom", {
"model": model,
"add_noise": True,
"noise_seed": seed,
"cfg": cfg,
"positive": positive,
"negative": negative,
"sampler": sampler,
"sigmas": sigmas,
"latent_image": latent,
}), 0]
def encode_positive(model, clip, prompt, image_width, image_height):
mask_width = image_width // LATENT_SCALE
mask_height = image_height // LATENT_SCALE
positive = [add("CLIPTextEncode", {
"text": prompt, "clip": clip,
}), 0]
regional_mode = config.get("regional_mode", "conditioning")
if regions:
positive = [add("ConditioningSetAreaStrength", {
"conditioning": positive,
"strength": global_strength,
}), 0]
if regions and regional_mode == "conditioning":
positive = [add("ConditioningSetTimestepRange", {
"conditioning": positive,
"start": environment_start,
"end": 1,
}), 0]
regional_inputs = {}
for index, (prompt, x, y, width, height, strength) in enumerate(
regions,
1,
):
conditioning = [add("CLIPTextEncode", {
"text": prompt, "clip": clip,
}), 0]
x, y, width, height, left, top, right, bottom = mask_box(
x,
y,
width,
height,
mask_width,
mask_height,
)
mask = [add("SolidMask", {
"value": 1,
"width": width,
"height": height,
}), 0]
if any((left, top, right, bottom)):
mask = [add("FeatherMask", {
"mask": mask,
"left": left,
"top": top,
"right": right,
"bottom": bottom,
}), 0]
background = [add("SolidMask", {
"value": 0,
"width": mask_width,
"height": mask_height,
}), 0]
mask = [add("MaskComposite", {
"destination": background,
"source": mask,
"x": x,
"y": y,
"operation": "add",
}), 0]
if regional_mode == "attention":
conditioning = [add("ConditioningSetAreaStrength", {
"conditioning": conditioning,
"strength": strength,
}), 0]
regional_inputs[f"cond_{index}"] = conditioning
regional_inputs[f"mask_{index}"] = mask
else:
conditioning = [add("ConditioningSetMask", {
"conditioning": conditioning,
"mask": mask,
"strength": strength,
"set_cond_area": "mask bounds",
}), 0]
positive = [add("ConditioningCombine", {
"conditioning_1": positive,
"conditioning_2": conditioning,
}), 0]
if regional_inputs:
base_mask = [add("SolidMask", {
"value": 1,
"width": mask_width,
"height": mask_height,
}), 0]
model = [add("AttentionCouplePPM", {
"model": model,
"base_cond": positive,
"base_mask": base_mask,
**regional_inputs,
}), 0]
return model, positive
first_model = config["model"]
second_model = config["second_model"] or first_model
first_vae, second_vae = vaes
base_model, clip = load_chain(first_model, config["loras"])
model = apply_style(base_model, "first")
model, positive = encode_positive(
model,
clip,
config["prompt"],
config["width"],
config["height"],
)
negative = [add("CLIPTextEncode", {
"text": config["negative"], "clip": clip,
}), 0]
latent = [add("EmptyLatentImage", {
"width": config["width"],
"height": config["height"],
"batch_size": config["batch_size"],
}), 0]
samples = sample(
model,
seeds[0],
config["steps"],
config["cfg"],
config["sampler"],
config["scheduler"],
positive,
negative,
latent,
1,
)
if config["upscale"]:
width, height = upscale_size(
config["width"],
config["height"],
config["upscale_scale"],
)
samples = [add("LatentUpscale", {
"upscale_method": config["upscale_method"],
"width": width,
"height": height,
"crop": "disabled",
"samples": samples,
}), 0]
if is_anima_model(first_model) != is_anima_model(second_model):
source_vae = [add("VAELoader", {"vae_name": first_vae}), 0]
image = [add("VAEDecode", {
"samples": samples,
"vae": source_vae,
}), 0]
target_vae = [add("VAELoader", {"vae_name": second_vae}), 0]
samples = [add("VAEEncode", {
"pixels": image,
"vae": target_vae,
}), 0]
if config["second_model"]:
base_model, clip = load_chain(
config["second_model"], config["second_loras"],
)
model = apply_style(base_model, "second")
model, positive = encode_positive(
model,
clip,
config.get("second_prompt") or config["prompt"],
width,
height,
)
negative = [add("CLIPTextEncode", {
"text": config.get("second_negative") or config["negative"],
"clip": clip,
}), 0]
samples = sample(
model,
seeds[1],
config["second_steps"],
config["second_cfg"],
config["second_sampler"],
config["second_scheduler"],
positive,
negative,
samples,
config["denoise"],
)
vae = second_vae if config["upscale"] else first_vae
vae_node = add("VAELoader", {"vae_name": vae})
image = add("VAEDecode", {"samples": samples, "vae": [vae_node, 0]})
base_clip, base_vae = clip, [vae_node, 0]
final_prompt = config.get("second_prompt") or config["prompt"] \
if config["upscale"] else config["prompt"]
final_negative = config.get("second_negative") or config["negative"] \
if config["upscale"] else config["negative"]
for detailer, seed, detailer_vae in zip(
config["detailers"],
detailer_seeds,
detailer_vaes,
):
if detailer["model"]:
model, clip = load_chain(detailer["model"], [])
vae = [add("VAELoader", {
"vae_name": detailer_vae,
}), 0]
prompt, negative_prompt = config["prompt"], config["negative"]
else:
model, clip, vae = base_model, base_clip, base_vae
prompt, negative_prompt = final_prompt, final_negative
model = apply_style(model, "detailer")
positive = [add("CLIPTextEncode", {
"text": detailer["prompt"] or prompt, "clip": clip,
}), 0]
negative = [add("CLIPTextEncode", {
"text": detailer["negative"] or negative_prompt, "clip": clip,
}), 0]
detector = add("UltralyticsDetectorProvider", {
"model_name": f"bbox/{detailer['detector']}",
})
image = add("FaceDetailer", {
"image": [image, 0],
"model": model,
"clip": clip,
"vae": vae,
"guide_size": DETAILER_GUIDE_SIZE,
"guide_size_for": True,
"max_size": DETAILER_MAX_SIZE,
"seed": seed,
"steps": detailer["steps"],
"cfg": detailer["cfg"],
"sampler_name": detailer["sampler"],
"scheduler": detailer["scheduler"],
"positive": positive,
"negative": negative,
"denoise": detailer["denoise"],
"feather": DETAILER_FEATHER,
"noise_mask": True,
"force_inpaint": True,
"bbox_threshold": DETAILER_THRESHOLD,
"bbox_dilation": DETAILER_DILATION,
"bbox_crop_factor": DETAILER_CROP,
"sam_detection_hint": "none",
"sam_dilation": 0,
"sam_threshold": .93,
"sam_bbox_expansion": 0,
"sam_mask_hint_threshold": .7,
"sam_mask_hint_use_negative": "False",
"drop_size": DETAILER_DROP_SIZE,
"bbox_detector": [detector, 0],
"wildcard": "",
"cycle": 1,
})
if config["upscale"] and config["upscale_model"]:
upscale_model = [add("UpscaleModelLoader", {
"model_name": config["upscale_model"],
}), 0]
image = add("ImageUpscaleWithModel", {
"upscale_model": upscale_model,
"image": [image, 0],
})
add("PreviewImage", {"images": [image, 0]})
return {
"prompt": json.dumps(api, separators=(",", ":")),
"parameters": json.dumps(config, separators=(",", ":")),
}
workflow = build_workflow()
prompt = json.loads(json.dumps(workflow))
extra_pnginfo = build_extra_pnginfo()
# Workflow execution
def main(unload_models: bool | None = None):
bootstrap_comfyui_runtime()
add_extra_model_paths()
import_custom_nodes()
# Node imports
from nodes import (
CLIPTextEncode,
CheckpointLoaderSimple,
EmptyLatentImage,
KSampler,
LoraLoader,
NODE_CLASS_MAPPINGS,
VAEDecode,
VAELoader,
)
import torch
try:
with torch.inference_mode():
checkpointloadersimple = CheckpointLoaderSimple()
checkpointloadersimple_1 = checkpointloadersimple.load_checkpoint(
ckpt_name="52_novaAnimeXL_ilV190.safetensors"
)
stringconcatenate = NODE_CLASS_MAPPINGS["StringConcatenate"]()
stringconcatenate_118 = stringconcatenate.EXECUTE_NORMALIZED(
string_a="%prompt%",
string_b="",
delimiter="",
)
loraloader = LoraLoader()
loraloader_10 = loraloader.load_lora(
lora_name="8_bikabaka.safetensors",
strength_model=0.3,
strength_clip=0,
model=get_value_at_index(checkpointloadersimple_1, 0),
clip=get_value_at_index(checkpointloadersimple_1, 1),
)
loraloader_38 = loraloader.load_lora(
lora_name="42_\u30a2\u30c3\u30d7\u30b9\u30b1\u30fc\u30eb_remacri_original.pt",
strength_model=0.4,
strength_clip=0,
model=get_value_at_index(loraloader_10, 0),
clip=get_value_at_index(loraloader_10, 1),
)
loraloader_28 = loraloader.load_lora(
lora_name="43_5cm-illustriousXL_v01_V1-CAME-000035.safetensors",
strength_model=0.4,
strength_clip=0,
model=get_value_at_index(loraloader_38, 0),
clip=get_value_at_index(loraloader_38, 1),
)
loraloader_117 = loraloader.load_lora(
lora_name="5_add_saturation_XL.safetensors",
strength_model=-1.4,
strength_clip=0,
model=get_value_at_index(loraloader_28, 0),
clip=get_value_at_index(loraloader_28, 1),
)
cliptextencode = CLIPTextEncode()
cliptextencode_2 = cliptextencode.encode(
text=get_value_at_index(stringconcatenate_118, 0),
clip=get_value_at_index(loraloader_117, 1),
)
cliptextencode_3 = cliptextencode.encode(
text="(censored, mosaic censoring, bar censor:1.1), bad quality, worst quality, worst detail, bad anatomy, extra fingers, extra toes, extra legs, 4 toes, 6 toes, 4 fingers, 6 fingers, malformed fingers, extra limbs, missing fingers, extra arms, censored, deformed, disfigured, text, (multiple views:1.1)",
clip=get_value_at_index(loraloader_28, 1),
)
emptylatentimage = EmptyLatentImage()
emptylatentimage_27 = emptylatentimage.generate(
width=1152, height=896, batch_size=1
)
vaeloader = VAELoader()
vaeloader_45 = vaeloader.load_vae(vae_name="3_sdxlVAE_sdxlVAE.safetensors")
ksampler = KSampler()
vaedecode = VAEDecode()
for q in range(1):
node_5_seed = prompt["5"]["inputs"]["seed"] = GENERATION
ksampler_5 = ksampler.sample(
seed=node_5_seed,
steps=16,
cfg=4,
sampler_name="euler_ancestral",
scheduler="karras",
denoise=1,
model=get_value_at_index(loraloader_117, 0),
positive=get_value_at_index(cliptextencode_2, 0),
negative=get_value_at_index(cliptextencode_3, 0),
latent_image=get_value_at_index(emptylatentimage_27, 0),
)
vaedecode_56 = vaedecode.decode(
samples=get_value_at_index(ksampler_5, 0),
vae=get_value_at_index(vaeloader_45, 0),
)
finally:
cleanup_comfyui_runtime(unload_models=unload_models)
# Entrypoint
if __name__ == "__main__":
main()