import math import json from ..components.tree import TREE_DASHBOARD from ..components.tree import TREE_DEPRECATED from ..components.tree import PRIMERE_ROOT from server import PromptServer import comfy.samplers import folder_paths import nodes import torch import torch.nn.functional as F from .modules.latent_noise import PowerLawNoise import random import os import tomli from .modules.adv_encode import advanced_encode from nodes import MAX_RESOLUTION from ..components import utility from pathlib import Path import re from ..components import hypernetwork from ..components import clipping from ..components import models as model_loaders from ..components import nf4_helper from ..components import sana_utils import comfy.sd import comfy.model_detection import comfy.utils import comfy_extras.nodes_model_advanced as nodes_model_advanced import comfy_extras.nodes_upscale_model as nodes_upscale_model import comfy_extras.nodes_cfg as nodes_cfg import comfy_extras.nodes_qwen as nodes_qwen from comfy import model_management from ..components.gguf import nodes as gguf_nodes import comfy_extras.nodes_flux as nodes_flux import comfy_extras.nodes_sd3 as nodes_sd3 from .modules import long_clip from .modules import networkhandler from .Networks import PrimereLORA from .Networks import PrimereEmbedding from .Networks import PrimereHypernetwork from .Networks import PrimereLYCORIS from diffusers import (UNet2DConditionModel, EulerDiscreteScheduler) from ..components.kolors.pipelines.pipeline_stable_diffusion_xl_chatglm_256 import StableDiffusionXLPipeline from ..components.kolors.models.tokenization_chatglm import ChatGLMTokenizer from ..components.kolors.models.modeling_chatglm import ChatGLMModel import gc from ComfyUI_ExtraModels.HunYuanDiT.conf import hydit_conf from ComfyUI_ExtraModels.HunYuanDiT.loader import load_hydit from ComfyUI_ExtraModels.utils.dtype import string_to_dtype from ComfyUI_ExtraModels.HunYuanDiT.tenc import load_clip, load_t5 from ComfyUI_ExtraModels.PixArt.conf import pixart_conf from ComfyUI_ExtraModels.PixArt.loader import load_pixart from ComfyUI_ExtraModels.Sana.conf import sana_conf, sana_res from ComfyUI_ExtraModels.Sana.loader import load_sana from ComfyUI_ExtraModels.VAE.conf import vae_conf from ComfyUI_ExtraModels.VAE.loader import EXVAE import numpy as np from PIL import Image, ImageFilter import difflib import datetime from ..components import llm_enhancer import pyrallis from ..components.sana.diffusion.model.builder import build_model from ..components.sana.pipeline.sana_pipeline import SanaPipeline from ..components.sana.diffusion.model.dc_ae.efficientvit.ae_model_zoo import create_dc_ae_model_cfg from ..components.sana.diffusion.model.dc_ae.efficientvit.models.efficientvit.dc_ae import DCAE from ..components.sana.diffusion.utils.config import SanaConfig from ..components.sana.diffusion.model.utils import prepare_prompt_ar from transformers import AutoTokenizer, T5Tokenizer, T5EncoderModel, AutoModelForCausalLM, BitsAndBytesConfig from ..components.sana.diffusion.data.datasets.utils import ASPECT_RATIO_512_TEST, ASPECT_RATIO_1024_TEST, ASPECT_RATIO_2048_TEST import node_helpers from comfy_api.latest import ComfyExtension, io class PrimereSamplersSteps: CATEGORY = TREE_DASHBOARD RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, "INT", "FLOAT") RETURN_NAMES = ("SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG") FUNCTION = "get_sampler_step" kolors_schedulers = ["EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepScheduler_SDE_karras", "UniPCMultistepScheduler", "DEISMultistepScheduler"] sana_schedulers = ['flow_dpm-solver'] @classmethod def INPUT_TYPES(cls): return { "required": { "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), "scheduler_name": (cls.sana_schedulers + cls.kolors_schedulers + comfy.samplers.KSampler.SCHEDULERS,), "steps": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}), "cfg": ("FLOAT", {"default": 7, "min": 0.1, "max": 100, "step": 0.01}), } } def get_sampler_step(self, sampler_name, scheduler_name, steps=12, cfg=7): return sampler_name, scheduler_name, steps, round(cfg, 2) class PrimereVAE: RETURN_TYPES = ("VAE_NAME",) RETURN_NAMES = ("VAE_NAME",) FUNCTION = "load_vae_list" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(s): return { "required": { "vae_model": (folder_paths.get_filename_list("vae"),) }, } def load_vae_list(self, vae_model): return vae_model, class PrimereCKPT: RETURN_TYPES = ("CHECKPOINT_NAME", "STRING", ['None'] + folder_paths.get_filename_list("checkpoints"),) RETURN_NAMES = ("MODEL_NAME", "MODEL_VERSION", "MODEL") FUNCTION = "load_ckpt_list" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(s): return { "required": { "base_model": (folder_paths.get_filename_list("checkpoints"),), }, } def load_ckpt_list(self, base_model): model_version = utility.getModelType(base_model, 'checkpoints') return (base_model, model_version, base_model) class PrimereVAELoader: RETURN_TYPES = ("VAE",) RETURN_NAMES = ("VAE",) FUNCTION = "load_primere_vae" CATEGORY = TREE_DEPRECATED @classmethod def INPUT_TYPES(s): return { "required": { "vae_name": ("VAE_NAME",), "baked_vae": ("VAE",) }, } def load_primere_vae(self, vae_name, baked_vae, ): if (vae_name == 'Baked VAE'): return (baked_vae,) if (vae_name == 'External VAE'): vae_name = folder_paths.get_filename_list("vae")[0] return utility.vae_loader_class.load_vae(vae_name)[0] class PrimereModelConceptSelector: RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, "INT", "FLOAT", "OVERRIDE_STEPS", "STRING", "CLIP_SELECTION", "VAE_SELECTION", "VAE_NAME", "FLOAT", "STRING", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "STRING", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "FLOAT", "STRING", "STRING", "FLUX_HYPER_LORA", "STRING", "INT", "FLOAT", "FLUX_TURBO_LORA", "STRING", "INT", "FLOAT", "FLUX_SRPO_LORA", "FLUX_SRPO_SVDQ_LORA", "STRING", "INT", "FLOAT", "FLUX_NUNCHAKU_LORA", "STRING", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "SD3_HYPER_LORA", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "FLOAT", "STRING", "STRING", "STRING", "FLOAT", "INT", "INT", "FLOAT", "BOOLEAN", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "QWEN_GEN_LIGHTNING_LORA", "FLOAT", "QWEN_GEN_LORA_PRECISION", "INT", "FLOAT", "STRING", "STRING", "STRING", "QWEN_EDIT_LIGHTNING_LORA", "FLOAT", "QWEN_EDIT_LORA_PRECISION", "INT", "FLOAT", "STRING", "STRING", "STRING", "STRING", "STRING", ) RETURN_NAMES = ("SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG", "OVERRIDE_STEPS", "MODEL_CONCEPT", "CLIP_SELECTION", "VAE_SELECTION", "VAE_NAME", "STRENGTH_LCM_LORA_MODEL", "LIGHTNING_SELECTOR", "LIGHTNING_MODEL_STEP", "STRENGTH_LIGHTNING_LORA_MODEL", "CASCADE_STAGE_A", "CASCADE_STAGE_B", "CASCADE_STAGE_C", "CASCADE_CLIP", "HYPER-SD_SELECTOR", "HYPER-SD_MODEL_STEP", "STRENGTH_HYPERSD_LORA_MODEL", "FLUX_SELECTOR", "FLUX_DIFFUSION_MODEL", "FLUX_WEIGHT_TYPE", "FLUX_GGUF_MODEL", "FLUX_CLIP_T5XXL", "FLUX_CLIP_L", "FLUX_CLIP_GUIDANCE", "FLUX_VAE", "FLUX_SAMPLER", "USE_FLUX_HYPER_LORA", "FLUX_HYPER_LORA_TYPE", "FLUX_HYPER_LORA_STEP", "FLUX_HYPER_LORA_STRENGTH", "USE_FLUX_TURBO_LORA", "FLUX_TURBO_LORA_TYPE", "FLUX_TURBO_LORA_STEP", "FLUX_TURBO_LORA_STRENGTH", "USE_FLUX_SRPO_LORA", "USE_FLUX_SRPO_SVDQ_LORA", "FLUX_SRPO_LORA_TYPE", "FLUX_SRPO_LORA_RANK", "FLUX_SRPO_LORA_STRENGTH", "USE_FLUX_NUNCHAKU_LORA", "FLUX_NUNCHAKU_LORA_TYPE", "FLUX_NUNCHAKU_LORA_RANK", "FLUX_NUNCHAKU_LORA_STRENGTH", "HUNYUAN_CLIP_T5XXL", "HUNYUAN_CLIP_L", "HUNYUAN_VAE", "SD3_CLIP_G", "SD3_CLIP_L", "SD3_CLIP_T5XXL", "SD3_UNET_VAE", "USE_SD3_HYPER_LORA", "SD3_HYPER_LORA_STEP", "SD3_HYPER_LORA_STRENGTH", "KOLORS_PRECISION", "PIXART_MODEL_TYPE", "PIXART_T5_ENCODER", "PIXART_VAE", "PIXART_DENOISE", "PIXART_REFINER_MODEL", "PIXART_REFINER_SAMPLER", "PIXART_REFINER_SCHEDULER", "PIXART_REFINER_CFG", "PIXART_REFINER_STEPS", "PIXART_REFINER_START", "PIXART_REFINER_DENOISE", "PIXART_REFINER_IGNORE_PROMPT", "SANA_MODEL", "SANA_ENCODER", "SANA_VAE", "SANA_WEIGHT_DTYPE", "SANA_PRECISION", "QWEN_GEN_MODEL", "QWEN_GEN_CLIP", "QWEN_GEN_VAE", "USE_QWEN_GEN_LIGHTNING_LORA", "QWEN_GEN_LIGHTNING_LORA_VERSION", "QWEN_GEN_LIGHTNING_PRECISION", "QWEN_GEN_LIGHTNING_LORA_STEP", "QWEN_GEN_LIGHTNING_LORA_STRENGTH", "QWEN_EDIT_MODEL", "QWEN_EDIT_CLIP", "QWEN_EDIT_VAE", "USE_QWEN_EDIT_LIGHTNING_LORA", "QWEN_EDIT_LIGHTNING_LORA_VERSION", "QWEN_EDIT_LIGHTNING_PRECISION", "QWEN_EDIT_LIGHTNING_LORA_STEP", "QWEN_EDIT_LIGHTNING_LORA_STRENGTH", "AURAFLOW_CLIP", "AURAFLOW_VAE", "ZIMAGE_MODEL", "ZIMAGE_CLIP", "ZIMAGE_VAE", ) FUNCTION = "select_model_concept" CATEGORY = TREE_DEPRECATED UNETLIST = folder_paths.get_filename_list("unet") DIFFUSIONLIST = folder_paths.get_filename_list("diffusion_models") TEXT_ENCODERS = folder_paths.get_filename_list("text_encoders") GGUFLIST = folder_paths.get_filename_list("unet_gguf") VAELIST = folder_paths.get_filename_list("vae") CLIPLIST = folder_paths.get_filename_list("clip") CLIPLIST += folder_paths.get_filename_list("clip_gguf") MODELLIST = folder_paths.get_filename_list("checkpoints") T5_DIR = os.path.join(folder_paths.models_dir, 't5') if os.path.isdir(T5_DIR): folder_paths.add_model_folder_path("p_t5", T5_DIR) T5models = folder_paths.get_filename_list("p_t5") T5List = folder_paths.filter_files_extensions(T5models, ['.bin', '.safetensors']) CLIPLIST += T5List TENC_DIR = os.path.join(folder_paths.models_dir, 'text_encoders') LLM_PRIMERE_ROOT = os.path.join(PRIMERE_ROOT, 'Nodes', 'Downloads', 'LLM') TEXT_ENCODERS_PATHS = llm_enhancer.getValidLLMPaths(TENC_DIR) TEXT_ENCODERS_PATHS += llm_enhancer.getValidLLMPaths(LLM_PRIMERE_ROOT) CONCEPT_LIST = utility.SUPPORTED_MODELS[0:26] SAMPLER_INPUTS = { 'model_version': ("STRING", {"forceInput": True, "default": "SD1"}), 'model_name': ("CHECKPOINT_NAME", {"forceInput": True}) } for concept in CONCEPT_LIST: concept_string = concept.lower() SAMPLER_INPUTS[concept_string + '_sampler_name'] = (comfy.samplers.KSampler.SAMPLERS, {"forceInput": True, "default": "euler"}) SAMPLER_INPUTS[concept_string + '_scheduler_name'] = (comfy.samplers.KSampler.SCHEDULERS, {"forceInput": True, "default": "normal"}) SAMPLER_INPUTS[concept_string + '_steps'] = ('INT', {"forceInput": True, "default": 12}) SAMPLER_INPUTS[concept_string + '_cfg_scale'] = ('FLOAT', {"forceInput": True, "default": 7}) INPUT_DICT = { "required": { "default_sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler"}), "default_scheduler_name": (comfy.samplers.KSampler.SCHEDULERS, {"default": "normal"}), "default_cfg_scale": ("FLOAT", {"default": 7, "min": 0.1, "max": 100, "step": 0.01}), "default_steps": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}), "override_steps": ("BOOLEAN", {"default": False, "label_off": "Set by sampler settings", "label_on": "Set by model filename"}), "sd_vae": (["None"] + VAELIST,), "sdxl_vae": (["None"] + VAELIST,), "model_concept": (["Auto"] + CONCEPT_LIST, {"default": "Auto"}), "clip_selection": ("BOOLEAN", {"default": True, "label_on": "Use baked if exist", "label_off": "Always use custom"}), "vae_selection": ("BOOLEAN", {"default": True, "label_on": "Use baked if exist", "label_off": "Always use custom"}), "strength_lcm_lora_model": ("FLOAT", {"default": 1.000, "min": -20.000, "max": 20.000, "step": 0.001}), "lightning_selector": (["UNET", "LORA", "SAFETENSOR"], {"default": "LORA"}), "lightning_model_step": ([1, 2, 4, 8], {"default": 8}), "lightning_sampler": ("BOOLEAN", {"default": False, "label_on": "Set by model", "label_off": "Custom (external)"}), "strength_lightning_lora_model": ("FLOAT", {"default": 1.000, "min": -20.000, "max": 20.000, "step": 0.001}), "hypersd_selector": (["UNET", "LORA"], {"default": "LORA"}), "hypersd_model_step": ([1, 2, 4, 8, 12], {"default": 8}), "hypersd_sampler": ("BOOLEAN", {"default": False, "label_on": "Set by model", "label_off": "Custom (external)"}), "strength_hypersd_lora_model": ("FLOAT", {"default": 1.000, "min": -20.000, "max": 20.000, "step": 0.001}), "cascade_stage_a": (["None"] + VAELIST,), "cascade_stage_b": (["None"] + UNETLIST,), "cascade_stage_c": (["None"] + UNETLIST,), "cascade_clip": (["None"] + CLIPLIST,), # "playground_sigma_max": ("FLOAT", {"default": 120.0, "min": 0.0, "max": 1000.0, "step": 0.001, "round": False}), # "playground_sigma_min": ("FLOAT", {"default": 0.002, "min": 0.0, "max": 1000.0, "step": 0.001, "round": False}), "flux_selector": (["DIFFUSION", "GGUF", "SAFETENSOR"], {"default": "DIFFUSION"}), "flux_diffusion": (["None"] + DIFFUSIONLIST,), "flux_weight_dtype": (["default", "fp8_e4m3fn", "fp8_e5m2"],), "flux_gguf": (["None"] + GGUFLIST,), "flux_clip_t5xxl": (["None"] + CLIPLIST,), "flux_clip_l": (["None"] + CLIPLIST,), "flux_clip_guidance": ('FLOAT', {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}), "flux_vae": (["None"] + VAELIST,), "flux_sampler": (["custom_advanced", "ksampler"], {"default": "ksampler"}), "use_flux_hyper_lora": ("BOOLEAN", {"default": False, "label_on": "Use hyper Lora", "label_off": "Ignore hyper Lora"}), "flux_hyper_lora_type": (["FLUX.1-dev", "FLUX.1-dev-fp16"], {"default": "FLUX.1-dev"}), "flux_hyper_lora_step": ([8, 16], {"default": 16}), "flux_hyper_lora_strength": ("FLOAT", {"default": 0.125, "min": -20.000, "max": 20.000, "step": 0.001}), "use_flux_turbo_lora": ("BOOLEAN", {"default": False, "label_on": "Use turbo Lora", "label_off": "Ignore turbo Lora"}), "flux_turbo_lora_type": (["TurboAlpha", "TurboRender"], {"default": "TurboAlpha"}), "flux_turbo_lora_step": ([4, 6, 8, 10, 12], {"default": 8}), "flux_turbo_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "use_flux_srpo_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO Lora", "label_off": "Ignore SRPO Lora"}), "use_flux_srpo_svdq_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO-NUNCHAKU Lora", "label_off": "Ignore SRPO-NUNCHAKU Lora"}), "flux_srpo_lora_type": (["R&Q", "RockerBOO", "oficial", "adaptive"], {"default": "oficial"}), "flux_srpo_lora_rank": ([8, 16, 32, 64, 128, 256], {"default": 8}), "flux_srpo_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "use_flux_nunchaku_lora": ("BOOLEAN", {"default": False, "label_on": "Use nunchaku Lora", "label_off": "Ignore nunchaku Lora"}), "flux_nunchaku_lora_type": (["kontext_deblur", "kontext_face_detailer", "anything_extracted"], {"default": "anything_extracted"}), "flux_nunchaku_lora_rank": ([64, 256], {"default": 64}), "flux_nunchaku_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "zimage_model": (["None"] + DIFFUSIONLIST,), "zimage_clip": (["None"] + TEXT_ENCODERS + CLIPLIST,), "zimage_vae": (["None"] + VAELIST,), "hunyuan_clip_t5xxl": (["None"] + CLIPLIST,), "hunyuan_clip_l": (["None"] + CLIPLIST,), "hunyuan_vae": (["None"] + VAELIST,), "sd3_clip_g": (["None"] + CLIPLIST,), "sd3_clip_l": (["None"] + CLIPLIST,), "sd3_clip_t5xxl": (["None"] + CLIPLIST,), "sd3_unet_vae": (["None"] + VAELIST,), "use_sd3_hyper_lora": ("BOOLEAN", {"default": False, "label_on": "Use hyper Lora", "label_off": "Ignore Lora"}), "sd3_hyper_lora_step": ([4, 8, 16], {"default": 8}), "sd3_hyper_lora_strength": ("FLOAT", {"default": 0.125, "min": -20.000, "max": 20.000, "step": 0.001}), "kolors_precision": (['fp16', 'quant8', 'quant4'], {"default": "quant8"}), "pixart_model_type": (["Auto"] + list(pixart_conf.keys()), {"default": "Auto"}), "pixart_T5_encoder": (["None"] + CLIPLIST,), "pixart_vae": (["None"] + VAELIST,), "pixart_denoise": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), "pixart_refiner_model": (["None"] + MODELLIST,), "pixart_refiner_sampler": (comfy.samplers.KSampler.SAMPLERS, {"default": "dpmpp_2m"}), "pixart_refiner_scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "normal"}), "pixart_refiner_cfg": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 100, "step": 0.01}), "pixart_refiner_steps": ("INT", {"default": 22, "min": 10, "max": 30, "step": 1}), "pixart_refiner_start": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}), "pixart_refiner_denoise": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), "pixart_refiner_ignore_prompt": ("BOOLEAN", {"default": False, "label_on": "Send prompt to refiner", "label_off": "Ignore prompt"}), "sana_model": (["None"] + DIFFUSIONLIST + MODELLIST,), "sana_encoder": (["None"] + TEXT_ENCODERS_PATHS,), "sana_vae": (["None"] + VAELIST,), "sana_weight_dtype": (["Auto", "fp16", "bf16", "fp32"], {"default": "fp16"}), "sana_precision": (['fp32', 'fp16', 'quant8', 'quant4'], {"default": "fp16"}), "qwen_gen_model": (["None"] + DIFFUSIONLIST,), "qwen_gen_clip": (["None"] + CLIPLIST,), "qwen_gen_vae": (["None"] + VAELIST,), "use_qwen_gen_lightning_lora": ("BOOLEAN", {"default": False, "label_on": "Use lightning Lora", "label_off": "Ignore Lora"}), "qwen_gen_lightning_lora_version": ([1.0, 1.1, 2.0], {"default": 2.0}), "qwen_gen_lightning_precision": ("BOOLEAN", {"default": True, "label_on": "FP32", "label_off": "BF16"}), "qwen_gen_lightning_lora_step": ([4, 8], {"default": 8}), "qwen_gen_lightning_lora_strength": ("FLOAT", {"default": 1.00, "min": -20.00, "max": 20.00, "step": 0.01}), "qwen_edit_model": (["None"] + DIFFUSIONLIST,), "qwen_edit_clip": (["None"] + CLIPLIST,), "qwen_edit_vae": (["None"] + VAELIST,), "use_qwen_edit_lightning_lora": ("BOOLEAN", {"default": False, "label_on": "Use lightning Lora", "label_off": "Ignore Lora"}), "qwen_edit_lightning_lora_version": ([1.0], {"default": 1.0}), "qwen_edit_lightning_precision": ("BOOLEAN", {"default": True, "label_on": "FP32", "label_off": "BF16"}), "qwen_edit_lightning_lora_step": ([4, 8], {"default": 8}), "qwen_edit_lightning_lora_strength": ("FLOAT", {"default": 1.00, "min": -20.00, "max": 20.00, "step": 0.01}), "auraflow_clip": (["None"] + CLIPLIST,), "auraflow_vae": (["None"] + VAELIST,), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT" }, "optional": SAMPLER_INPUTS } @classmethod def INPUT_TYPES(cls): return cls.INPUT_DICT def select_model_concept(self, cascade_stage_a, cascade_stage_b, cascade_stage_c, cascade_clip, flux_diffusion, flux_weight_dtype, flux_gguf, flux_clip_t5xxl, flux_clip_l, flux_vae, qwen_gen_model, qwen_gen_clip, qwen_gen_vae, hunyuan_clip_t5xxl, hunyuan_clip_l, hunyuan_vae, sd3_clip_g, sd3_clip_l, sd3_clip_t5xxl, sd3_unet_vae, qwen_edit_model, qwen_edit_clip, qwen_edit_vae, auraflow_clip, auraflow_vae, zimage_model, zimage_clip, zimage_vae, prompt, override_steps=False, use_sd3_hyper_lora=False, sd3_hyper_lora_step=8, sd3_hyper_lora_strength=0.125, use_qwen_gen_lightning_lora=False, qwen_gen_lightning_lora_version=1.1, qwen_gen_lightning_precision=True, qwen_gen_lightning_lora_step=8, qwen_gen_lightning_lora_strength=1.00, use_qwen_edit_lightning_lora=False, qwen_edit_lightning_lora_version=1.1, qwen_edit_lightning_precision=True, qwen_edit_lightning_lora_step=8, qwen_edit_lightning_lora_strength=1.00, kolors_precision='quant8', pixart_model_type="Auto", pixart_T5_encoder='None', pixart_vae='None', pixart_denoise=0.9, pixart_refiner_model='None', pixart_refiner_sampler='dpmpp_2m', pixart_refiner_scheduler='Normal', pixart_refiner_cfg=2.0, pixart_refiner_steps=22, pixart_refiner_start=12, pixart_refiner_denoise=0.9, pixart_refiner_ignore_prompt=False, sana_model="None", sana_encoder="None", sana_vae="None", sana_weight_dtype="Auto", sana_precision="fp16", model_version=None, model_name=None, default_sampler_name='euler', default_scheduler_name='normal', default_cfg_scale=7, default_steps=12, sd_vae="None", sdxl_vae="None", model_concept='Auto', clip_selection=True, vae_selection=True, strength_lcm_lora_model=1, lightning_selector="LORA", lightning_model_step=8, lightning_sampler=False, strength_lightning_lora_model=1, hypersd_selector="LORA", hypersd_model_step=8, hypersd_sampler=False, strength_hypersd_lora_model=1, flux_sampler='ksampler', flux_selector="DIFFUSION", flux_clip_guidance=3.5, use_flux_hyper_lora=False, flux_hyper_lora_type='FLUX.1-dev', flux_hyper_lora_step=16, flux_hyper_lora_strength=0.125, use_flux_turbo_lora=False, flux_turbo_lora_type="TurboAlpha", flux_turbo_lora_step=8, flux_turbo_lora_strength=1, use_flux_srpo_lora=False, use_flux_srpo_svdq_lora=False, flux_srpo_lora_type='oficial', flux_srpo_lora_rank=8, flux_srpo_lora_strength=1, use_flux_nunchaku_lora=False, flux_nunchaku_lora_type='anything_extracted', flux_nunchaku_lora_rank=64, flux_nunchaku_lora_strength=1, **kwargs ): sampler_name = default_sampler_name scheduler_name = default_scheduler_name steps = default_steps cfg_scale = default_cfg_scale if model_concept == 'Auto' and model_version == 'Lightning': lightning_selector = 'CUSTOM' if model_concept == 'Auto' and model_version == 'Hyper': hypersd_selector = 'LORA' if model_concept == 'Auto' and model_version is not None: model_concept = model_version if model_concept == 'QwenEdit': WORKFLOWDATA = kwargs['extra_pnginfo']['workflow']['nodes'] editmodel_images = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereMultiImage', 'process_list', prompt) if editmodel_images != True: model_concept = 'QwenGen' vae = 'Baked' if vae_selection == True: vae = 'Baked' else: if model_version == "SD1" and sd_vae != "None": vae = sd_vae if (model_version == "SDXL" or model_version == "KwaiKolors") and sdxl_vae != "None": vae = sdxl_vae sampler_name_input = model_concept.lower() + '_sampler_name' scheduler_name_input = model_concept.lower() + '_scheduler_name' steps_input = model_concept.lower() + '_steps' cfg_scale_input = model_concept.lower() + '_cfg_scale' if sampler_name_input in kwargs: sampler_name = kwargs[sampler_name_input] if scheduler_name_input in kwargs: scheduler_name = kwargs[scheduler_name_input] if steps_input in kwargs: steps = kwargs[steps_input] if cfg_scale_input in kwargs: cfg_scale = kwargs[cfg_scale_input] if pixart_model_type == 'Auto' and model_name is not None: cutoff_list = list(np.around(np.arange(0.1, 1.01, 0.01).tolist(), 2))[::-1] pixartTypes = list(pixart_conf.keys()) is_found = [] trycut = 0 pixart_model_name = Path(model_name).stem for trycut in cutoff_list: is_found = difflib.get_close_matches(pixart_model_name, pixartTypes, cutoff=trycut) if len(is_found) > 0: break if trycut <= 0.35: pixart_model_type = 'PixArtMS_Sigma_XL_2' else: pixart_model_type = is_found[0] match model_concept: case 'Lightning': if lightning_sampler == True: sampler_name = 'euler' scheduler_name = 'sgm_uniform' steps = lightning_model_step cfg_scale = 1 case 'Hyper': if hypersd_sampler == True and hypersd_selector == 'UNET': sampler_name = 'lcm' steps = 1 cfg_scale = 1 elif hypersd_sampler == True and hypersd_selector != 'UNET': sampler_name = 'euler' scheduler_name = 'sgm_uniform' steps = hypersd_model_step cfg_scale = 1 if model_concept != 'LCM': strength_lcm_lora_model = None if model_concept != 'Lightning': lightning_selector = None lightning_model_step = None strength_lightning_lora_model = None if model_concept != 'Hyper': hypersd_selector = None hypersd_model_step = None strength_hypersd_lora_model = None if model_concept != 'StableCascade': cascade_stage_a = None cascade_stage_b = None cascade_stage_c = None cascade_clip = None if model_concept != 'Flux': flux_selector = None flux_diffusion = None flux_weight_dtype = None flux_gguf = None flux_clip_t5xxl = None flux_clip_l = None flux_clip_guidance = None flux_vae = None flux_sampler = None use_flux_hyper_lora = None flux_hyper_lora_type = None flux_hyper_lora_step = None flux_hyper_lora_strength = None use_flux_turbo_lora = None flux_turbo_lora_type = None flux_turbo_lora_step = None flux_turbo_lora_strength = None use_flux_srpo_lora = None use_flux_srpo_svdq_lora = None flux_srpo_lora_type = None flux_srpo_lora_rank = None flux_srpo_lora_strength = None use_flux_nunchaku_lora = None flux_nunchaku_lora_type = None flux_nunchaku_lora_rank = None flux_nunchaku_lora_strength = None if model_concept != 'Hunyuan': hunyuan_clip_t5xxl = None hunyuan_clip_l = None hunyuan_vae = None if model_concept != 'Z-Image': zimage_model = None zimage_clip = None zimage_vae = None if model_concept != 'AuraFlow': auraflow_clip = None auraflow_vae = None if model_concept != 'KwaiKolors': kolors_precision = None if model_concept != 'SD3': sd3_clip_g = None sd3_clip_l = None sd3_clip_t5xxl = None sd3_unet_vae = None use_sd3_hyper_lora = None sd3_hyper_lora_step = None sd3_hyper_lora_strength = None if model_concept != 'QwenGen': qwen_gen_model = None qwen_gen_clip = None qwen_gen_vae = None use_qwen_gen_lightning_lora = None qwen_gen_lightning_lora_step = None qwen_gen_lightning_lora_strength = None qwen_gen_lightning_lora_version = None qwen_gen_lightning_precision = None if model_concept != 'QwenEdit': qwen_edit_model = None qwen_edit_clip = None qwen_edit_vae = None use_qwen_edit_lightning_lora = None qwen_edit_lightning_lora_step = None qwen_edit_lightning_lora_strength = None qwen_edit_lightning_lora_version = None qwen_edit_lightning_precision = None if model_concept != 'PixartSigma': pixart_model_type = None pixart_T5_encoder = None pixart_vae = None pixart_denoise = None pixart_refiner_model = None pixart_refiner_sampler = None pixart_refiner_scheduler = None pixart_refiner_cfg = None pixart_refiner_steps = None pixart_refiner_start = None pixart_refiner_denoise = None pixart_refiner_ignore_prompt = None if model_concept != 'SANA512' and model_concept != 'SANA1024': sana_model = None sana_encoder = None sana_vae = None sana_weight_dtype = None sana_precision = None if model_name is not None and override_steps == True: is_steps = re.findall(r"(?i)(\d+)Step", model_name) if len(is_steps) > 0: steps = int(is_steps[0]) use_flux_hyper_lora = False use_sd3_hyper_lora = False use_flux_turbo_lora = False use_qwen_gen_lightning_lora = False use_qwen_edit_lightning_lora = False elif 'lightning' in model_name: steps = 5 use_flux_hyper_lora = False use_sd3_hyper_lora = False use_flux_turbo_lora = False use_qwen_gen_lightning_lora = False use_qwen_edit_lightning_lora = False cfg_scale = 1.1 if model_concept == 'Flux' and use_flux_hyper_lora == True: steps = flux_hyper_lora_step if model_concept == 'Flux' and use_flux_turbo_lora == True: steps = flux_turbo_lora_step if model_concept == 'QwenGen' and use_qwen_gen_lightning_lora == True: steps = qwen_gen_lightning_lora_step + 1 cfg_scale = 1.1 if model_concept == 'QwenEdit' and use_qwen_edit_lightning_lora == True: steps = qwen_edit_lightning_lora_step + 1 cfg_scale = 1.1 if model_concept == 'SD3' and use_sd3_hyper_lora == True: fullpathFile = folder_paths.get_full_path('checkpoints', model_name) is_link = os.path.islink(str(fullpathFile)) if is_link == True: File_link = Path(str(fullpathFile)).resolve() model_ext = os.path.splitext(File_link)[1].lower() if model_ext != '.gguf': steps = sd3_hyper_lora_step else: steps = sd3_hyper_lora_step if '_distill' in model_name.lower() and 'Qwen' in model_concept: cfg_scale = 1 return (sampler_name, scheduler_name, steps, round(cfg_scale, 2), override_steps, model_concept, clip_selection, vae_selection, vae, strength_lcm_lora_model, lightning_selector, lightning_model_step, strength_lightning_lora_model, cascade_stage_a, cascade_stage_b, cascade_stage_c, cascade_clip, hypersd_selector, hypersd_model_step, strength_hypersd_lora_model, flux_selector, flux_diffusion, flux_weight_dtype, flux_gguf, flux_clip_t5xxl, flux_clip_l, flux_clip_guidance, flux_vae, flux_sampler, use_flux_hyper_lora, flux_hyper_lora_type, flux_hyper_lora_step, flux_hyper_lora_strength, use_flux_turbo_lora, flux_turbo_lora_type, flux_turbo_lora_step, flux_turbo_lora_strength, use_flux_srpo_lora, use_flux_srpo_svdq_lora, flux_srpo_lora_type, flux_srpo_lora_rank, flux_srpo_lora_strength, use_flux_nunchaku_lora, flux_nunchaku_lora_type, flux_nunchaku_lora_rank, flux_nunchaku_lora_strength, hunyuan_clip_t5xxl, hunyuan_clip_l, hunyuan_vae, sd3_clip_g, sd3_clip_l, sd3_clip_t5xxl, sd3_unet_vae, use_sd3_hyper_lora, sd3_hyper_lora_step, sd3_hyper_lora_strength, kolors_precision, pixart_model_type, pixart_T5_encoder, pixart_vae, pixart_denoise, pixart_refiner_model, pixart_refiner_sampler, pixart_refiner_scheduler, pixart_refiner_cfg, pixart_refiner_steps, pixart_refiner_start, pixart_refiner_denoise, pixart_refiner_ignore_prompt, sana_model, sana_encoder, sana_vae, sana_weight_dtype, sana_precision, qwen_gen_model, qwen_gen_clip, qwen_gen_vae, use_qwen_gen_lightning_lora, qwen_gen_lightning_lora_version, qwen_gen_lightning_precision, qwen_gen_lightning_lora_step, qwen_gen_lightning_lora_strength, qwen_edit_model, qwen_edit_clip, qwen_edit_vae, use_qwen_edit_lightning_lora, qwen_edit_lightning_lora_version, qwen_edit_lightning_precision, qwen_edit_lightning_lora_step, qwen_edit_lightning_lora_strength, auraflow_clip, auraflow_vae, zimage_model, zimage_clip, zimage_vae ) class PrimereModelControl: def __init__(self): pass CATEGORY = TREE_DASHBOARD RETURN_TYPES = ("TUPLE", comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, "INT", "FLOAT", "STRING") RETURN_NAMES = ("CONTROL_DATA", "SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG", "MODEL_CONCEPT") FUNCTION = "get_primeremodelcontrol" OUTPUT_NODE = True kolors_schedulers = ["EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "DPMSolverMultistepScheduler", "DPMSolverMultistepScheduler_SDE_karras", "UniPCMultistepScheduler", "DEISMultistepScheduler"] sana_schedulers = ['flow_dpm-solver'] UNETLIST = PrimereModelConceptSelector.UNETLIST DIFFUSIONLIST = PrimereModelConceptSelector.DIFFUSIONLIST TEXT_ENCODERS = PrimereModelConceptSelector.TEXT_ENCODERS GGUFLIST = PrimereModelConceptSelector.GGUFLIST VAELIST = PrimereModelConceptSelector.VAELIST CLIPLIST = PrimereModelConceptSelector.CLIPLIST MODELLIST = PrimereModelConceptSelector.MODELLIST TEXT_ENCODERS_PATHS = PrimereModelConceptSelector.TEXT_ENCODERS_PATHS CONCEPT_LIST = PrimereModelConceptSelector.CONCEPT_LIST CUSTOMLORA_DIR = os.path.join(PRIMERE_ROOT, 'Nodes', 'Downloads') folder_paths.add_model_folder_path("customlora", CUSTOMLORA_DIR) CustomLoras = folder_paths.get_filename_list("customlora") CustomLorasList = folder_paths.filter_files_extensions(CustomLoras, ['.safetensors']) LCM_LORAS = [n for n in CustomLorasList if "lcm" in n.lower()] SPEED_LORAS = [n for n in CustomLorasList if any(s in n.lower() for s in ("lightning", "hyper", "turbo"))] SRPO_LORAS = [n for n in CustomLorasList if "srpo" in n.lower() and "svdq" not in n.lower()] SRPO_SVDQ_LORAS = [n for n in CustomLorasList if "srpo" in n.lower() and "svdq" in n.lower()] NUNCHAKU_LORAS = [n for n in CustomLorasList if "nunchaku" in n.lower()] REFINER_MODELS = [n for n in PrimereModelConceptSelector.MODELLIST if "refiner" in os.path.basename(n).lower() or "refiner" in os.path.dirname(n).lower()] SECTION_TITLES = [ {"before": "concepts", "name": "modelctrl_target", "title": "๐Ÿงญ Model Target & Concept", "color": "#5C3D34", "text_color": "#EAF1F8", "label": "Select active concept/model context and auto-binding behavior for setting profiles."}, {"after": "models", "name": "modelctrl_sampling_core", "title": "๐ŸŽ› Core Sampling Controls", "color": "#6A4A2A", "text_color": "#EAF1F8", "label": "Set sampler, scheduler, step strategy, CFG and override behavior for baseline generation."}, {"after": "rescale_cfg", "name": "modelctrl_vae_clip", "title": "๐Ÿงฉ VAE & CLIP Selection", "color": "#6A4A2A", "text_color": "#EAF1F8", "label": "Choose VAE/CLIP source preference and final CLIP layer behavior."}, {"after": "last_layer", "name": "modelctrl_text_encoders", "title": "๐Ÿ“ Text Encoder Routing", "color": "#3E5C4B", "text_color": "#EAF1F8", "label": "Assign encoder slots for multi-encoder pipelines (T5/CLIP/UNET-backed text paths)."}, {"after": "encoder_3", "name": "modelctrl_attention", "title": "๐Ÿง  Attention Modulation", "color": "#3E5C4B", "text_color": "#EAF1F8", "label": "Preset or custom attention scaling for self/cross attention blocks and expander shaping."}, {"after": "attn_expander", "name": "modelctrl_sampler_backend", "title": "โš™ Backend Sampling Model", "color": "#405985", "text_color": "#EAF1F8", "label": "Configure sampler backend mode, EDM/discrete options, sigma bounds and flux shift parameters."}, {"after": "beta_beta", "name": "modelctrl_guidance_precision", "title": "๐Ÿ“ Guidance & Precision", "color": "#405985", "text_color": "#EAF1F8", "label": "Tune model guidance and runtime precision/weight dtype for speed, memory and quality balance."}, {"after": "precision", "name": "modelctrl_speed_loras", "title": "โšก Acceleration LoRAs", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "LCM and speed-focused LoRA controls for faster inference and step/cfg auto-alignment."}, {"after": "speed_lora_steps_offset", "name": "modelctrl_special_loras", "title": "๐Ÿงช Specialized LoRAs", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Optional SRPO, SRPO-SVDQ and Nunchaku LoRA blocks for specialized behavior."}, {"after": "nunchaku_lora_strength", "name": "modelctrl_refiner", "title": "โœจ Refiner Stage", "color": "#554267", "text_color": "#EAF1F8", "label": "Enable second-stage refiner pass and tune sampler, denoise, prompt handoff and step window."}, ] @classmethod def INPUT_TYPES(cls): return { "required": { # "model_concept": ("STRING", {"default": None, "forceInput": True}), # "model_name": ("CHECKPOINT_NAME", {"default": None, "forceInput": True}), "concepts": (["Auto"] + cls.CONCEPT_LIST,), "models": (["Auto"] + cls.MODELLIST,), "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), "scheduler_name": (cls.sana_schedulers + cls.kolors_schedulers + comfy.samplers.KSampler.SCHEDULERS,), "steps": ("INT", {"default": 12, "min": 1, "max": 1000, "step": 1}), "override_steps": ("BOOLEAN", {"default": False, "label_off": "Set by sampler settings", "label_on": "Set by model filename"}), "cfg": ("FLOAT", {"default": 7, "min": 0.1, "max": 100, "step": 0.01}), "rescale_cfg": ("FLOAT", {"default": 1, "min": 0.0, "max": 1.0, "step": 0.01}), "vae": (cls.VAELIST,), "vae_selection": ("BOOLEAN", {"default": True, "label_on": "Use baked if exist", "label_off": "Always use custom"}), "clip_selection": ("BOOLEAN", {"default": True, "label_on": "Use baked if exist", "label_off": "Always use custom"}), "last_layer": ("INT", {"default": 0, "min": -24, "max": 0, "step": 1}), "encoder_1": (list(dict.fromkeys(["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.UNETLIST + cls.TEXT_ENCODERS_PATHS)),), "encoder_2": (list(dict.fromkeys(["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.UNETLIST + cls.TEXT_ENCODERS_PATHS)),), "encoder_3": (list(dict.fromkeys(["None"] + cls.TEXT_ENCODERS + cls.CLIPLIST + cls.UNETLIST + cls.TEXT_ENCODERS_PATHS)),), "attn_preset": (["Custom"] + list(clipping.ATTN_PRESETS.keys()), {"default": "Off"}), "attn_query": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_key": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_value": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_output": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_cross_query": ("FLOAT", {"default": 1.0, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_cross_key": ("FLOAT", {"default": 1.0, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_cross_value": ("FLOAT", {"default": 1.0, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_cross_output": ("FLOAT", {"default": 1.0, "min": 0.80, "max": 1.20, "step": 0.01}), "attn_expander": ("FLOAT", {"default": 1.00, "min": 0.10, "max": 3.00, "step": 0.01}), "sampler": (["custom_advanced", "ksampler"], {"default": "ksampler"}), "align_your_steps": ("BOOLEAN", {"default": False, "label_on": "Use AlignYourSteps", "label_off": "Ignore AlignYourSteps"}), "model_sampling": ("FLOAT", {"default": 2.5, "min": 0.0, "max": 10.0, "step": 0.01}), "edm_sampling": (["edm_playground_v2.5", "v_prediction", "edm", "eps", "cosmos_rflow"], {"default": "edm_playground_v2.5"}), "discrete_sampling": (["default", "eps", "v_prediction", "x0"], {"default": "default"}), "discrete_zsnr": ("BOOLEAN", {"default": False, "label_on": "Zero SNR", "label_off": "No Zero SNR"}), "sigma_max": ("FLOAT", {"default": 120, "min": 1, "max": 200, "step": 0.001}), "sigma_min": ("FLOAT", {"default": 1, "min": 0.001, "max": 100, "step": 0.001}), "flux_max_shift": ("FLOAT", {"default": 1.15, "min": 0.0, "max": 100.0, "step": 0.01}), "flux_base_shift": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step": 0.01}), "beta_alpha": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 50.0, "step": 0.01}), "beta_beta": ("FLOAT", {"default": 0.6, "min": 0.0, "max": 50.0, "step": 0.01}), "guidance": ('FLOAT', {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.1}), "weight_dtype": (["None"] + ["Auto", "default", "fp16", "bf16", "fp32", "fp8_e4m3fn", "fp8_e5m2"], {"default": "default"}), "precision": (["None"] + ['fp32', 'fp16', 'quant8', 'quant4'], {"default": "fp16"}), "lcm_lora": ("BOOLEAN", {"default": False, "label_on": "LCM lora ON", "label_off": "LCM lora OFF"}), "lcm_lora_strength": ("FLOAT", {"default": 1.000, "min": -20.000, "max": 20.000, "step": 0.001}), "speed_lora": ("BOOLEAN", {"default": False, "label_on": "Speed lora ON", "label_off": "Speed lora OFF"}), "speed_lora_name": (cls.SPEED_LORAS,), "speed_lora_strength": ("FLOAT", {"default": 1.00, "min": -20.00, "max": 20.00, "step": 0.01}), "speed_lora_cfg": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100, "step": 0.01}), "speed_lora_steps_offset": ("INT", {"default": 0, "min": -5, "max": 5, "step": 1}), "srpo_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO Lora", "label_off": "Ignore SRPO Lora"}), "srpo_lora_name": (cls.SRPO_LORAS,), "srpo_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "srpo_svdq_lora": ("BOOLEAN", {"default": False, "label_on": "Use SRPO SVDQ Lora", "label_off": "Ignore SRPO SVDQ Lora"}), "srpo_svdq_lora_name": (cls.SRPO_SVDQ_LORAS,), "srpo_svdq_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "nunchaku_lora": ("BOOLEAN", {"default": False, "label_on": "Use nunchaku Lora", "label_off": "Ignore nunchaku Lora"}), "nunchaku_lora_name": (cls.NUNCHAKU_LORAS,), "nunchaku_lora_strength": ("FLOAT", {"default": 1, "min": -20.000, "max": 20.000, "step": 0.001}), "refiner": ("BOOLEAN", {"default": False, "label_on": "Refiner ON", "label_off": "Refiner OFF"}), "refiner_model": (cls.REFINER_MODELS,), "refiner_sampler": (comfy.samplers.KSampler.SAMPLERS, {"default": "dpmpp_2m"}), "refiner_scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "normal"}), "refiner_cfg": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 100, "step": 0.01}), "refiner_steps": ("INT", {"default": 20, "min": 2, "max": 50, "step": 1}), "refiner_start": ("INT", {"default": 10, "min": 1, "max": 1000, "step": 1}), "refiner_denoise": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), "refiner_sampling_denoise": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}), "refiner_ignore_prompt": ("BOOLEAN", {"default": True, "label_on": "Ignore prompt", "label_off": "Send prompt to refiner"}) }, "optional": { "model_concept": ("STRING", {"default": None, "forceInput": True}), "model_name": ("CHECKPOINT_NAME", {"default": None, "forceInput": True}), }, } def get_primeremodelcontrol(self, **kwargs): model_concept = kwargs.pop('model_concept', None) model_name = kwargs.pop('model_name', None) concepts = kwargs.pop('concepts', 'Auto') models = kwargs.pop('models', 'Auto') sampler_name = kwargs.pop('sampler_name', comfy.samplers.KSampler.SAMPLERS[0]) scheduler_name = kwargs.pop('scheduler_name', comfy.samplers.KSampler.SCHEDULERS[0]) steps = kwargs.pop('steps', 12) cfg = kwargs.pop('cfg', 7.0) active_concept = model_concept if concepts == "Auto" and model_name is not None else concepts saved = {} if concepts == "Auto" and models == "Auto": raw_model = model_name model_key = os.path.splitext(os.path.basename(raw_model))[0] if raw_model else None json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'model_concept.json') concept_data = utility.json2tuple(json_path) if model_key and concept_data and model_key in concept_data: lookup_key = model_key else: lookup_key = active_concept model_key = None active_display = model_key if model_key else active_concept if not concept_data or lookup_key not in concept_data: PromptServer.instance.send_sync("primere.concept_setting", {"status": "missing", "concept": active_concept}) else: saved = concept_data[lookup_key] sampler_name = saved.get('sampler_name', sampler_name) scheduler_name = saved.get('scheduler_name', scheduler_name) steps = saved.get('steps', steps) cfg = saved.get('cfg', cfg) for k, v in saved.items(): if k in kwargs: kwargs[k] = v else: active_display = active_concept if kwargs.get('override_steps') == True and model_name is not None: found = re.findall(r"(?i)(\d+)step", model_name.lower()) if found: steps = int(found[0]) if concepts == "Auto" and models == "Auto": if kwargs.get('speed_lora') == True and 'speed_lora' not in saved: kwargs['speed_lora'] = False if kwargs.get('lcm_lora') == True and 'lcm_lora' not in saved: kwargs['lcm_lora'] = False if kwargs.get('srpo_lora') == True and 'srpo_lora' not in saved: kwargs['srpo_lora'] = False if kwargs.get('srpo_svdq_lora') == True and 'srpo_svdq_lora' not in saved: kwargs['srpo_svdq_lora'] = False if kwargs.get('nunchaku_lora') == True and 'nunchaku_lora' not in saved: kwargs['nunchaku_lora'] = False if kwargs.get('refiner') == True and 'refiner' not in saved: kwargs['refiner'] = False if kwargs.get('speed_lora') == True: speed_lora_name_val = kwargs.get('speed_lora_name', '') if speed_lora_name_val: found = re.findall(r"(?i)(\d+)step", speed_lora_name_val.lower()) if found: steps = int(found[0]) offset = int(kwargs.get('speed_lora_steps_offset', 0)) if offset: steps = max(1, steps + offset) speed_lora_cfg_val = kwargs.get('speed_lora_cfg') if speed_lora_cfg_val is not None: cfg = float(speed_lora_cfg_val) attn_preset = kwargs.pop('attn_preset', 'Off') attn_query = kwargs.pop('attn_query', 1.0) attn_key = kwargs.pop('attn_key', 1.0) attn_value = kwargs.pop('attn_value', 1.0) attn_output = kwargs.pop('attn_output', 1.0) attn_cross_query = kwargs.pop('attn_cross_query', 1.0) attn_cross_key = kwargs.pop('attn_cross_key', 1.0) attn_cross_value = kwargs.pop('attn_cross_value', 1.0) attn_cross_output = kwargs.pop('attn_cross_output', 1.0) attn_expander = kwargs.pop('attn_expander', 1.0) if attn_preset == 'Auto': attn_preset = clipping.detect_attn_preset(model_name) attn_q, attn_k, attn_v, attn_out, cross_q, cross_k, cross_v, cross_out = clipping.ATTN_PRESETS.get(attn_preset, (1.0,)*8) elif attn_preset == 'Custom': attn_q, attn_k, attn_v, attn_out = attn_query, attn_key, attn_value, attn_output cross_q, cross_k, cross_v, cross_out = attn_cross_query, attn_cross_key, attn_cross_value, attn_cross_output else: attn_q, attn_k, attn_v, attn_out, cross_q, cross_k, cross_v, cross_out = clipping.ATTN_PRESETS.get(attn_preset, (1.0,)*8) if attn_expander != 1.0: attn_q, attn_k, attn_v, attn_out, cross_q, cross_k, cross_v, cross_out = ( round(1.0 + (v - 1.0) * attn_expander, 2) for v in (attn_q, attn_k, attn_v, attn_out, cross_q, cross_k, cross_v, cross_out) ) suppressed = [k + "_" for k, v in kwargs.items() if v == "None" or v is False] kwargs = {k: v for k, v in kwargs.items() if v != "None" and not any(k.startswith(p) for p in suppressed)} kwargs['encoders'] = [kwargs[k] for k in ('encoder_1', 'encoder_2', 'encoder_3') if kwargs.get(k) not in (None, 'None')] kwargs['model'] = model_name # kwargs['vae'] = kwargs.get('vae', None) kwargs['model_name'] = model_name kwargs['model_concept'] = active_concept kwargs['model_version'] = active_concept kwargs['sampler'] = sampler_name kwargs['scheduler'] = scheduler_name kwargs['sampler_name'] = sampler_name kwargs['scheduler_name'] = scheduler_name kwargs['steps'] = steps kwargs['cfg'] = round(cfg, 2) kwargs['clip_attn_q'] = attn_q kwargs['clip_attn_k'] = attn_k kwargs['clip_attn_v'] = attn_v kwargs['clip_attn_out'] = attn_out kwargs['attn_cross_q'] = cross_q kwargs['attn_cross_k'] = cross_k kwargs['attn_cross_v'] = cross_v kwargs['attn_cross_out'] = cross_out kwargs['concept_data'] = {} kwargs['concept_data']['vae_name'] = kwargs.get('vae', None) kwargs['concept_data']['vae_selection'] = kwargs.get('vae_selection', True) kwargs['concept_data']['clip_selection'] = kwargs.get('clip_selection', True) return {"ui": {"active_concept": [active_display]}, "result": (kwargs, sampler_name, scheduler_name, steps, round(cfg, 2), active_concept,)} class PrimereConceptDataTuple: RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS, "INT", "FLOAT", "TUPLE",) RETURN_NAMES = ("SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG", "DATA",) FUNCTION = "load_concept_collector" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): return { "required": { "data": ("TUPLE", {"forceInput": True}), }, } def load_concept_collector(self, data): sampler_name = data.get("sampler_name", comfy.samplers.KSampler.SAMPLERS[0]) scheduler_name = data.get("scheduler_name", comfy.samplers.KSampler.SCHEDULERS[0]) steps = data.get("steps", 20) cfg = data.get("cfg", 7.0) return (sampler_name, scheduler_name, steps, cfg, data,) class PrimereCKPTLoader: RETURN_TYPES = ("MODEL", "CLIP", "VAE", "STRING",) RETURN_NAMES = ("MODEL", "CLIP", "VAE", "MODEL_VERSION") FUNCTION = "load_primere_ckpt" CATEGORY = TREE_DASHBOARD def __init__(self): self.loaded_lora = None @classmethod def INPUT_TYPES(cls): return { "required": { "ckpt_name": ("CHECKPOINT_NAME",), "use_yaml": ("BOOLEAN", {"default": False}), }, "optional": { # "model_concept": ("STRING", {"forceInput": True}), "control_data": ("TUPLE", {"default": None, "forceInput": True}), "loaded_model": ('MODEL', {"forceInput": True, "default": None}), "loaded_clip": ('CLIP', {"forceInput": True, "default": None}), "loaded_vae": ('VAE', {"forceInput": True, "default": None}), }, } def load_primere_ckpt(self, ckpt_name, use_yaml, control_data=None, loaded_model=None, loaded_clip=None, loaded_vae=None): model_concept = control_data['model_concept'] try: comfy.model_management.soft_empty_cache() comfy.model_management.cleanup_models(True) comfy.model_management.unload_all_models() except Exception: print('No need to clear cache...') modelname_only = Path(ckpt_name).stem MODEL_VERSION_ORIGINAL = utility.get_value_from_cache('model_version', modelname_only) if MODEL_VERSION_ORIGINAL is None: MODEL_VERSION_ORIGINAL = utility.getModelType(ckpt_name, 'checkpoints') utility.add_value_to_cache('model_version', ckpt_name, MODEL_VERSION_ORIGINAL) path = Path(ckpt_name) ModelName = path.stem ModelConfigPath = path.parent.joinpath(ModelName + '.yaml') ModelConfigFullPath = Path(folder_paths.models_dir).joinpath('checkpoints').joinpath(ModelConfigPath) LOADED_CHECKPOINT = [] if loaded_model is not None and loaded_clip is not None and loaded_vae is not None: LOADED_CHECKPOINT.insert(0, loaded_model) LOADED_CHECKPOINT.insert(1, loaded_clip) LOADED_CHECKPOINT.insert(2, loaded_vae) OUTPUT_MODEL = LOADED_CHECKPOINT[0] match model_concept: case 'SD1' | 'SD2' | 'SDXL' | 'Illustrious' | 'Turbo' | 'Pony': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_sd_model(self, ckpt_name, use_yaml, ModelConfigFullPath, control_data) case 'SD3': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_sd3_model(self, ckpt_name, control_data) case 'StableCascade': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_stable_cascade_model(self, ckpt_name, control_data) case 'Z-Image': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_zimage_model(self, ckpt_name, control_data) case 'Flux': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_flux_model(self, ckpt_name, control_data) case 'LCM': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_lcm_model(self, ckpt_name, control_data) case 'Hyper' | 'Lightning': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_lightning_hyper_model(self, ckpt_name, control_data) case 'Playground': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_playground_model(self, ckpt_name, use_yaml, ModelConfigFullPath, control_data) case 'PixartSigma': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_pixart_model(self, ckpt_name, control_data) case 'AuraFlow': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_auraflow_model(self, ckpt_name, control_data) case 'SANA1024' | 'SANA512': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_sana_model(self, ckpt_name, control_data) case 'KwaiKolors': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_kolors_model(self, ckpt_name, control_data) case 'Hunyuan': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_hunyuan_model(self, ckpt_name, control_data) case 'QwenGen' | 'QwenEdit': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_qwen_model(self, ckpt_name, control_data) case 'Chroma': OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = model_loaders.load_chroma_model(self, ckpt_name, control_data) return (OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE, MODEL_VERSION_ORIGINAL) class AnyType(str): def __ne__(self, __value: object) -> bool: return False class PrimerePromptSwitch: any_typ = AnyType("*") RETURN_TYPES = (any_typ, any_typ, "INT", "TUPLE") RETURN_NAMES = ("PROMPT+", "PROMPT-", "SELECTED_INDEX", "PREFERRED") FUNCTION = "promptswitch" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): return { "required": { "select": ("INT", {"default": 1, "min": 1, "max": 20, "step": 1}), }, } def promptswitch(self, *args, **kwargs): selected_index = int(kwargs['select']) input_namep = f"prompt_pos_{selected_index}" input_namen = f"prompt_neg_{selected_index}" input_preferred = f"preferred_{selected_index}" if input_namep in kwargs: return (kwargs[input_namep], kwargs[input_namen], selected_index, kwargs[input_preferred]) else: print(f"PrimerePromptSwitch: invalid select index (ignored)") return (None, None, selected_index, None) class PrimereSeed: RETURN_TYPES = ("INT",) RETURN_NAMES = ("SEED",) FUNCTION = "seed" CATEGORY = TREE_DEPRECATED @classmethod def INPUT_TYPES(cls): return { "required": { "seed": ("INT", {"default": -1, "min": -1, "max": utility.MAX_SEED}), } } def seed(self, seed=-1): return (seed,) class PrimereFastSeed: RETURN_TYPES = ("INT",) RETURN_NAMES = ("SEED",) FUNCTION = "primere_fastseed" OUTPUT_NODE = True CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): return { "required": { "seed_setup": (['Random', 'Increase', 'Decrease', 'UseLast', 'UseCustom'], {"default": 'Random'}), "custom_seed": ("INT", {"default": 42, "min": 0, "max": utility.MAX_SEED, "step": 1}), } } @classmethod def IS_CHANGED(self, **kwargs): if kwargs['seed_setup'] == 'UseCustom' and seed != kwargs['custom_seed']: return float("NaN") if kwargs['seed_setup'] != 'UseLast' and kwargs['seed_setup'] != 'UseCustom': return float("NaN") def primere_fastseed(self, seed_setup, custom_seed): global seed random.seed(datetime.datetime.now().timestamp()) match seed_setup: case "Random": seed = random.randint(1000, utility.MAX_SEED) case "Increase": if 'seed' in globals(): seed = seed + 1 else: seed = random.randint(1000, utility.MAX_SEED) case "Decrease": if 'seed' in globals(): seed = seed - 1 else: seed = random.randint(1000, utility.MAX_SEED) case "UseLast": if 'seed' in globals(): seed = seed else: seed = random.randint(1000, utility.MAX_SEED) case "UseCustom": seed = custom_seed return {"ui": {"text": [f'Last seed: [{seed}]']}, "result": (seed,)} class PrimereFractalLatent: RETURN_TYPES = ("LATENT", "IMAGE", "TUPLE") RETURN_NAMES = ("LATENTS", "PREVIEWS", "CONTROL_DATA") FUNCTION = "primere_latent_noise" CATEGORY = TREE_DASHBOARD pln = PowerLawNoise('cpu') INPUT_DICT = { "required": { "width": ("INT", {"default": 512, "max": 8192, "min": 64, "forceInput": True}), "height": ("INT", {"default": 512, "max": 8192, "min": 64, "forceInput": True}), "rand_noise_type": ("BOOLEAN", {"default": False}), "noise_type": (pln.get_noise_types(),), "rand_alpha_exponent": ("BOOLEAN", {"default": True}), "alpha_exponent": ("FLOAT", {"default": 1.0, "max": 12.0, "min": -12.0, "step": 0.001}), "alpha_exp_rand_min": ("FLOAT", {"default": 0.5, "max": 12.0, "min": -12.0, "step": 0.001}), "alpha_exp_rand_max": ("FLOAT", {"default": 1.5, "max": 12.0, "min": -12.0, "step": 0.001}), "rand_modulator": ("BOOLEAN", {"default": True}), "modulator": ("FLOAT", {"default": 1.0, "max": 2.0, "min": 0.1, "step": 0.01}), "modulator_rand_min": ("FLOAT", {"default": 0.8, "max": 2.0, "min": 0.1, "step": 0.01}), "modulator_rand_max": ("FLOAT", {"default": 1.4, "max": 2.0, "min": 0.1, "step": 0.01}), "noise_seed": ("INT", {"default": 0, "min": -1, "max": utility.MAX_SEED, "forceInput": True}), "rand_device": ("BOOLEAN", {"default": False}), "device": (["cpu", "cuda"],), "expand_random_limits": ("BOOLEAN", {"default": False, "label_on": "ON", "label_off": "OFF"}), }, "optional": { "optional_vae": ("VAE",), "control_data": ("TUPLE", {"default": None}), } } @classmethod def INPUT_TYPES(cls): return cls.INPUT_DICT @classmethod def IS_CHANGED(self, **kwargs): if kwargs['expand_random_limits'] == True or kwargs['rand_noise_type'] == True or kwargs['rand_device'] == True or kwargs['rand_alpha_exponent'] == True or kwargs['rand_modulator'] == True: return float('NaN') def primere_latent_noise(self, width, height, rand_noise_type, noise_type, rand_alpha_exponent, alpha_exponent, alpha_exp_rand_min, alpha_exp_rand_max, rand_modulator, modulator, modulator_rand_min, modulator_rand_max, noise_seed, rand_device, device, optional_vae=None, control_data=None, expand_random_limits=False): if control_data is not None and len(control_data) > 0 and 'exif_status' in control_data and control_data['exif_status'] == 'SUCCEED': if 'latent_data' in control_data and len(control_data['latent_data']) > 0 and 'setup_states' in control_data and 'latent_setup' in control_data['setup_states']: if control_data['setup_states']['latent_setup'] == True: expand_random_limits = False rand_device = False rand_alpha_exponent = False rand_modulator = False rand_noise_type = False noise_type = control_data['latent_data']['noise_type'] device = control_data['latent_data']['device'] alpha_exponent = control_data['latent_data']['alpha_exponent'] modulator = control_data['latent_data']['modulator'] if expand_random_limits == True: rand_device = True rand_alpha_exponent = True rand_modulator = True rand_noise_type = True alpha_exp_rand_min = -12.00 alpha_exp_rand_max = 7.00 modulator_rand_min = 0.10 modulator_rand_max = 2.00 if rand_noise_type == True: pln = PowerLawNoise(device) noise_type = random.choice(pln.get_noise_types()) if rand_device == True: device = random.choice(["cpu", "cuda"]) if expand_random_limits == True and (noise_type == 'white' or noise_type == 'violet'): alpha_exp_rand_min = 0.00 power_law = PowerLawNoise(device=device) if rand_alpha_exponent == True: alpha_exponent = round(random.uniform(alpha_exp_rand_min, alpha_exp_rand_max), 3) if rand_modulator == True: modulator = round(random.uniform(modulator_rand_min, modulator_rand_max), 2) tensors = power_law(1, width, height, scale=1, alpha=alpha_exponent, modulator=modulator, noise_type=noise_type, seed=noise_seed) alpha_channel = torch.ones((1, height, width, 1), dtype=tensors.dtype, device="cpu") tensors = torch.cat((tensors, alpha_channel), dim=3) if optional_vae is None: latents = tensors.permute(0, 3, 1, 2) latents = F.interpolate(latents, size=((height // 8), (width // 8)), mode='nearest-exact') return {'samples': latents}, tensors, control_data encoder = nodes.VAEEncode() latents = [] for tensor in tensors: tensor = tensor.unsqueeze(0) try: latents.append(encoder.encode(optional_vae, tensor)[0]['samples']) except Exception: latents = tensors.permute(0, 3, 1, 2) latents = F.interpolate(latents, size=((height // 8), (width // 8)), mode='nearest-exact') return {'samples': latents}, tensors, control_data latents = torch.cat(latents) if control_data is not None: control_data['latent_data'] = {} control_data['latent_data']['noise_type'] = noise_type control_data['latent_data']['alpha_exponent'] = alpha_exponent control_data['latent_data']['modulator'] = modulator control_data['latent_data']['device'] = device return {'samples': latents}, tensors, control_data CLIP_SAVE_KEYS = frozenset({ 'negative_strength', 'adv_encode', 'token_normalization', 'weight_interpretation', 'use_int_style', 'int_style_pos', 'int_style_pos_strength', 'int_style_neg', 'int_style_neg_strength', 'enhanced_prompt_usage', 'enhanced_prompt_strength', 'opt_pos_strength', 'opt_neg_strength', 'style_handling', 'style_position', 'style_swap', 'style_pos_strength', 'style_neg_strength', 'l_strength', 'width', 'height' }) class PrimereCLIP: RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "STRING", "STRING", "STRING", "STRING", "STRING", "TUPLE") RETURN_NAMES = ("COND+", "COND-", "PROMPT+", "PROMPT-", "T5XXL_PROMPT", "PROMPT L+", "PROMPT L-", "CONTROL_DATA") FUNCTION = "clip_encode" CATEGORY = TREE_DASHBOARD @staticmethod def get_default_neg(toml_path: str): with open(toml_path, "rb") as f: style_def_neg = tomli.load(f) return style_def_neg @classmethod def INPUT_TYPES(cls): DEF_TOML_DIR = os.path.join(PRIMERE_ROOT, 'Toml') cls.default_neg = cls.get_default_neg(os.path.join(DEF_TOML_DIR, "default_neg.toml")) cls.default_pos = cls.get_default_neg(os.path.join(DEF_TOML_DIR, "default_pos.toml")) CLIPLIST = folder_paths.get_filename_list("clip") CLIPLIST += folder_paths.get_filename_list("clip_gguf") T5_DIR = os.path.join(folder_paths.models_dir, 't5') if os.path.isdir(T5_DIR): folder_paths.add_model_folder_path("p_t5", T5_DIR) T5models = folder_paths.get_filename_list("p_t5") T5List = folder_paths.filter_files_extensions(T5models, ['.bin', '.safetensors']) CLIPLIST += T5List cls.CLIPLIST = CLIPLIST return { "required": { "clip": ("CLIP", {"forceInput": True}), "positive_prompt": ("STRING", {"forceInput": True}), "negative_prompt": ("STRING", {"forceInput": True}), "negative_strength": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 10.0, "step": 0.01}), "use_int_style": ("BOOLEAN", {"default": False}), "int_style_pos": (['None'] + sorted(list(cls.default_pos.keys())),), "int_style_pos_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "int_style_neg": (['None'] + sorted(list(cls.default_neg.keys())),), "int_style_neg_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "adv_encode": ("BOOLEAN", {"default": False}), "token_normalization": (["none", "mean", "length", "length+mean"], {"default": "mean"}), "weight_interpretation": (["comfy", "A1111", "compel", "comfy++", "down_weight"], {"default": "comfy++"}), }, "optional": { "enhanced_prompt": ("STRING", {"forceInput": True}), "enhanced_prompt_usage": (['None', 'Add', 'Replace', 'T5-XXL'], {"default": "T5-XXL"}), "enhanced_prompt_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "edit_image_list": ("IMAGE", {"forceInput": True}, {"default": None}), "edit_vae": ("VAE", {"forceInput": True}, {"default": None}), "model_keywords": ("MODEL_KEYWORD", {"forceInput": True}), "lora_keywords": ("MODEL_KEYWORD", {"forceInput": True}), "lycoris_keywords": ("MODEL_KEYWORD", {"forceInput": True}), "embedding_pos": ("EMBEDDING", {"forceInput": True}), "embedding_neg": ("EMBEDDING", {"forceInput": True}), "opt_pos_prompt": ("STRING", {"forceInput": True}), "opt_pos_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "opt_neg_prompt": ("STRING", {"forceInput": True}), "opt_neg_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "style_handling": ("BOOLEAN", {"default": False, "label_on": "Style-prompt separation", "label_off": "Style-prompt merge"}), "style_position": ("BOOLEAN", {"default": False, "label_on": "Style to front of prompt", "label_off": "Style to end of prompt"}), "style_swap": ("BOOLEAN", {"default": False, "label_on": "Style to default clip - prompt to T5/L", "label_off": "Style to T5/L - prompt to default clip"}), "style_pos_prompt": ("STRING", {"forceInput": True}), "style_pos_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "style_neg_prompt": ("STRING", {"forceInput": True}), "style_neg_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "positive_l": ("STRING", {"forceInput": True}), "negative_l": ("STRING", {"forceInput": True}), "l_strength": ("FLOAT", {"default": 1, "min": 0.0, "max": 10.0, "step": 0.01}), "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION, "forceInput": True}), "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION, "forceInput": True}), "control_data": ("TUPLE", {"default": None, "forceInput": True}), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT" } } def clip_encode(self, clip, extra_pnginfo, prompt, control_data=None, **kwargs): if control_data and control_data.get('exif_status') == 'SUCCEED': if 'prompt_encoder' in control_data and control_data.get('setup_states', {}).get('clip_encoder_setup') == True: kwargs.update(control_data['prompt_encoder']) negative_strength = kwargs.get('negative_strength', 1.2) use_int_style = kwargs.get('use_int_style', False) int_style_pos = kwargs.get('int_style_pos', 'None') int_style_pos_strength = kwargs.get('int_style_pos_strength', 1) int_style_neg = kwargs.get('int_style_neg', 'None') int_style_neg_strength = kwargs.get('int_style_neg_strength', 1) adv_encode = kwargs.get('adv_encode', False) token_normalization = kwargs.get('token_normalization', 'mean') weight_interpretation = kwargs.get('weight_interpretation', 'comfy++') enhanced_prompt_usage = kwargs.get('enhanced_prompt_usage', 'T5-XXL') enhanced_prompt_strength = kwargs.get('enhanced_prompt_strength', 1) style_handling = kwargs.get('style_handling', False) style_position = kwargs.get('style_position', False) style_swap = kwargs.get('style_swap', False) opt_pos_strength = kwargs.get('opt_pos_strength', 1) opt_neg_strength = kwargs.get('opt_neg_strength', 1) style_pos_strength = kwargs.get('style_pos_strength', 1) style_neg_strength = kwargs.get('style_neg_strength', 1) l_strength = kwargs.get('l_strength', 1) positive_prompt = kwargs.get('positive_prompt', '') negative_prompt = kwargs.get('negative_prompt', '') enhanced_prompt = kwargs.get('enhanced_prompt', '') model_keywords = kwargs.get('model_keywords', None) lora_keywords = kwargs.get('lora_keywords', None) lycoris_keywords = kwargs.get('lycoris_keywords', None) embedding_pos = kwargs.get('embedding_pos', None) embedding_neg = kwargs.get('embedding_neg', None) opt_pos_prompt = kwargs.get('opt_pos_prompt', '') opt_neg_prompt = kwargs.get('opt_neg_prompt', '') style_pos_prompt = kwargs.get('style_pos_prompt', '') style_neg_prompt = kwargs.get('style_neg_prompt', '') positive_l = kwargs.get('positive_l', '') negative_l = kwargs.get('negative_l', '') width = kwargs.get('width', 1024) height = kwargs.get('height', 1024) edit_image_list = kwargs.get('edit_image_list', None) edit_vae = kwargs.get('edit_vae', None) if control_data is not None: _lv = locals() control_data['prompt_encoder'] = {k: _lv[k] for k in CLIP_SAVE_KEYS} model_concept = control_data.get('model_concept', 'SD1') advanced_default = ['StableCascade', 'Chroma', 'KwaiKolors', 'Flux', "Z-Image", 'Pony', 'SD1', 'SD2', 'SD3', 'Lightning', 'Hunyuan', 'QwenGen', 'QwenEdit', 'AuraFlow'] if model_concept in advanced_default: adv_encode = False positive_text, negative_text, t5xxl_prompt, positive_l, negative_l = clipping.build_prompt_context( model_concept, positive_prompt, negative_prompt, enhanced_prompt, enhanced_prompt_usage, enhanced_prompt_strength, style_pos_prompt, style_neg_prompt, style_handling, style_swap, style_position, style_pos_strength, style_neg_strength, opt_pos_prompt, opt_neg_prompt, opt_pos_strength, opt_neg_strength, negative_strength, int_style_pos, int_style_neg, int_style_pos_strength, int_style_neg_strength, use_int_style, self.default_pos, self.default_neg, l_strength, positive_l, negative_l, model_keywords, lora_keywords, lycoris_keywords, embedding_pos, embedding_neg, ) clip = clipping.apply_clip_attention_multiply(clip, control_data) match model_concept: case 'SD3': return clipping.encode_sd3(clip, positive_text, negative_text, t5xxl_prompt, control_data) case 'StableCascade': return clipping.encode_stable_cascade(clip, positive_text, negative_text, control_data) case 'Flux': return clipping.encode_flux(clip, positive_text, negative_text, t5xxl_prompt, control_data) case 'PixartSigma': return clipping.encode_pixart_sigma(clip, positive_text, negative_text, control_data) case 'SANA1024' | 'SANA512': return clipping.encode_sana(clip, positive_text, negative_text, t5xxl_prompt, control_data) case 'KwaiKolors': return clipping.encode_kolors(clip, positive_text, negative_text, t5xxl_prompt, control_data) case 'Hunyuan': return clipping.encode_hunyuan(self, clip, positive_text, negative_text, t5xxl_prompt, control_data) case 'QwenEdit': return clipping.encode_qwen_edit(self, clip, positive_text, negative_text, t5xxl_prompt, edit_vae, edit_image_list, control_data) # case 'Chroma': # return clipping.encode_chroma(clip, positive_text, negative_text, control_data) case _: clip = clipping.apply_clip_overrides(self, clip, control_data) return clipping.encode_standard(clip, positive_text, negative_text, t5xxl_prompt, adv_encode, token_normalization, weight_interpretation, positive_l, negative_l, width, height, control_data, advanced_encode) class PrimereResolution: RETURN_TYPES = ("INT", "INT", "INT", "STRING") RETURN_NAMES = ("WIDTH", "HEIGHT", "SQUARE_SHAPE", "ASPECT_RATIO") FUNCTION = "calculate_imagesize" CATEGORY = TREE_DASHBOARD @staticmethod def get_ratios(toml_path: str): with open(toml_path, "rb") as f: image_ratios = tomli.load(f) return image_ratios @classmethod def INPUT_TYPES(cls): DEF_TOML_DIR = os.path.join(PRIMERE_ROOT, 'Toml') cls.sd_ratios = cls.get_ratios(os.path.join(DEF_TOML_DIR, "resolution_ratios.toml")) namelist = {} for sd_ratio_key in cls.sd_ratios: rationName = cls.sd_ratios[sd_ratio_key]['name'] namelist[rationName] = sd_ratio_key cls.ratioNames = namelist return { "required": { "ratio": (list(namelist.keys()),), "resolution": ("BOOLEAN", {"default": True, "label_on": "Auto", "label_off": "Manual"}), "manual_res": (utility.VALID_SHAPES, {"default": utility.VALID_SHAPES[2]}), "rnd_orientation": ("BOOLEAN", {"default": False}), "orientation": (["Horizontal", "Vertical"], {"default": "Horizontal"}), "round_to_standard": ("BOOLEAN", {"default": False}), "calculate_by_custom": ("BOOLEAN", {"default": False}), "custom_side_a": ("FLOAT", {"default": 1.6, "min": 1.0, "max": 100.0, "step": 0.05}), "custom_side_b": ("FLOAT", {"default": 2.8, "min": 1.0, "max": 100.0, "step": 0.05}), }, "optional": { "seed": ("INT", {"default": 0, "min": -1, "max": utility.MAX_SEED, "forceInput": True}), "model_version": ("STRING", {"default": 'SDXL', "forceInput": True}), "model_concept": ("STRING", {"default": "Auto", "forceInput": True}), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT" } } def calculate_imagesize(self, ratio: str, resolution: bool, rnd_orientation: bool, orientation: str, round_to_standard: bool, calculate_by_custom: bool, custom_side_a: float, custom_side_b: float, seed: int = 0, model_version: str = "SDXL", model_concept: str = 'Auto', **kwargs): square_shape = kwargs['manual_res'] if seed < 1: seed = random.randint(0, 9) if rnd_orientation == True: if (seed % 2) == 0: orientation = "Horizontal" else: orientation = "Vertical" if resolution == True: if model_version == model_concept and model_concept == 'Hyper': WORKFLOWDATA = kwargs['extra_pnginfo']['workflow']['nodes'] OriginalBaseModel = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', kwargs['prompt']) fullpathFile = folder_paths.get_full_path('checkpoints', OriginalBaseModel) is_link = os.path.islink(str(fullpathFile)) if is_link == False: model_version = utility.getModelType(OriginalBaseModel, 'checkpoints') square_shape = utility.getResolutionByType(model_version) else: input_string = model_version.lower() + '_res' if input_string in kwargs: square_shape = int(kwargs[input_string]) if square_shape is None: square_shape = kwargs['manual_res'] standard = 'STANDARD' if model_version == 'StableCascade': standard = 'CASCADE' if model_version == 'PixartSigma': round_to_standard = True dimensions = utility.get_dimensions_by_shape(self, ratio, square_shape, orientation, round_to_standard, calculate_by_custom, custom_side_a, custom_side_b, standard) dimension_x = dimensions[0] dimension_y = dimensions[1] return dimension_x, dimension_y, square_shape, f"{dimensions[2]}:{dimensions[3]}" class PrimereResolutionMultiplierMPX: RETURN_TYPES = ("INT", "INT", "FLOAT", "IMAGE") RETURN_NAMES = ("WIDTH", "HEIGHT", "UPSCALE_RATIO", "IMAGE") FUNCTION = "multiply_imagesize_mpx" CATEGORY = TREE_DASHBOARD upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] @classmethod def INPUT_TYPES(cls): return { "required": { "use_multiplier": ("BOOLEAN", {"default": True}), "width": ('INT', {"forceInput": True, "default": 512}), "height": ('INT', {"forceInput": True, "default": 512}), "upscale_to_mpx": ("FLOAT", {"default": 12.00, "min": 0.01, "max": 48.00, "step": 0.01}), }, "optional": { "triggered_prescale": ("BOOLEAN", {"default": False}), "image": ("IMAGE", {"forceInput": True}), "area_trigger_mpx": ("FLOAT", {"default": 0.60, "min": 0.01, "max": round(pow(utility.MAX_RESOLUTION, 2) / 1000000, 2), "step": 0.01}), "area_target_mpx": ("FLOAT", {"default": 1.05, "min": 0.25, "max": round(pow(utility.MAX_RESOLUTION, 2) / 1000000, 2), "step": 0.01}), "upscale_model": (['None'] + folder_paths.get_filename_list("upscale_models"), {"default": 'None'}), "upscale_method": (cls.upscale_methods, {"default": 'bicubic'}), } } def multiply_imagesize_mpx(self, width: int, height: int, use_multiplier: bool, upscale_to_mpx: int, triggered_prescale=False, image=None, area_trigger_mpx=0.60, area_target_mpx=1.05, upscale_model='None', upscale_method='bicubic'): if use_multiplier == False or upscale_to_mpx < 0.01: return (width, height, 1, image) if image is not None: width = image.shape[2] height = image.shape[1] if triggered_prescale == True and use_multiplier == True: area_trigger = area_trigger_mpx * (1000 * 1000) area_target = area_target_mpx * (1024 * 1024) area = width * height if area_trigger >= area: sourceMPXTrigger = area differenceTrigger = area_target / sourceMPXTrigger squareDiffTrigger = math.sqrt(differenceTrigger) if image is not None: if upscale_model == 'None': prescaledImage = nodes.ImageScaleBy.upscale(self, image, upscale_method, squareDiffTrigger)[0] else: loaded_upscale_model = nodes_upscale_model.UpscaleModelLoader.execute(upscale_model)[0] prescaledImage = nodes_upscale_model.ImageUpscaleWithModel.execute(loaded_upscale_model, image)[0] image = prescaledImage width = prescaledImage.shape[2] height = prescaledImage.shape[1] newArea = width * height if newArea > area_target: differenceTrigger = area_target / newArea squareDiffTrigger = math.sqrt(differenceTrigger) prescaledImage = nodes.ImageScaleBy.upscale(self, image, upscale_method, squareDiffTrigger)[0] image = prescaledImage width = prescaledImage.shape[2] height = prescaledImage.shape[1] sourceMPX = (width * height) / (1024 * 1024) difference = upscale_to_mpx / sourceMPX squareDiff = math.sqrt(difference) dimension_x = round(width * squareDiff) dimension_y = round(height * squareDiff) ratio = round(squareDiff, 2) try: comfy.model_management.soft_empty_cache() comfy.model_management.cleanup_models(True) except Exception: print('No need to clear cache...') return (dimension_x, dimension_y, ratio, image) class PrimereResolutionCoordinatorMPX: RETURN_TYPES = ("INT", "INT", "INT", "INT", "IMAGE", "INT", "INT", "IMAGE", "INT", "INT") RETURN_NAMES = ("REF_WIDTH", "REF_HEIGHT", "SLAVE_WIDTH", "SLAVE_HEIGHT", "RESIZED_REFERENCE", "REF_WIDTH_RES", "REF_HEIGHT_RES", "RESIZED_SLAVE", "SLAVE_WIDTH_RES", "SLAVE_HEIGHT_RES") FUNCTION = "imagesize_coordinator" CATEGORY = TREE_DASHBOARD upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"] @classmethod def INPUT_TYPES(cls): return { "required": { "use_resizer": ("BOOLEAN", {"default": True}), "reference_image": ("IMAGE", {"forceInput": True}), "slave_image": ("IMAGE", {"forceInput": True}), "resize_to_mpx": ("FLOAT", {"default": 1.00, "min": 0.01, "max": 48.00, "step": 0.01}), "keep_slave_ratio": ("BOOLEAN", {"default": False}), "upscale_model": (['None'] + folder_paths.get_filename_list("upscale_models"), {"default": 'None'}), "upscale_method": (cls.upscale_methods, {"default": 'lanczos'}), } } def imagesize_coordinator(self, use_resizer, reference_image, slave_image, resize_to_mpx, keep_slave_ratio, upscale_model, upscale_method): ref_width = 0 ref_height = 0 slave_width = 0 slave_height = 0 if reference_image is not None and slave_image is not None: ref_width = reference_image.shape[2] ref_height = reference_image.shape[1] slave_width = slave_image.shape[2] slave_height = slave_image.shape[1] if use_resizer == True: referenceMPX = (ref_width * ref_height) / (1024 * 1024) referenceDifference = resize_to_mpx / referenceMPX ref_squareDiff = math.sqrt(referenceDifference) ref_width_resized = round(ref_width * ref_squareDiff) ref_height_resized = round(ref_height * ref_squareDiff) slaveMPX = (slave_width * slave_height) / (1024 * 1024) slaveDifference = resize_to_mpx / slaveMPX slave_squareDiff = math.sqrt(slaveDifference) if keep_slave_ratio == True: slave_width_resized = round(slave_width * slave_squareDiff) slave_height_resized = round(slave_height * slave_squareDiff) else: slave_width_resized = ref_width_resized slave_height_resized = ref_height_resized if upscale_model == 'None': reference_image = nodes.ImageScaleBy.upscale(self, reference_image, upscale_method, ref_squareDiff)[0] if keep_slave_ratio == True: slave_image = nodes.ImageScaleBy.upscale(self, slave_image, upscale_method, slave_squareDiff)[0] else: slave_image = nodes.ImageScale.upscale(self, slave_image, upscale_method, slave_width_resized, slave_height_resized, "disabled")[0] else: loaded_upscale_model = nodes_upscale_model.UpscaleModelLoader.execute(upscale_model)[0] reference_image_model = nodes_upscale_model.ImageUpscaleWithModel.execute(loaded_upscale_model, reference_image)[0] reference_image = nodes.ImageScale.upscale(self, reference_image_model, upscale_method, ref_width_resized, ref_height_resized, "disabled")[0] if keep_slave_ratio == True: slave_image_model = nodes_upscale_model.ImageUpscaleWithModel.execute(loaded_upscale_model, slave_image)[0] slave_image = nodes.ImageScaleBy.upscale(self, slave_image_model, upscale_method, slave_squareDiff)[0] slave_image = nodes.ImageScale.upscale(self, slave_image, upscale_method, slave_width_resized, slave_height_resized, "disabled")[0] else: ref_width_resized = ref_width ref_height_resized = ref_height slave_width_resized = slave_width slave_height_resized = slave_height return (ref_width, ref_height, slave_width, slave_height, reference_image, ref_width_resized, ref_height_resized, slave_image, slave_width_resized, slave_height_resized) class PrimereClearNetworkTagsPrompt: RETURN_TYPES = ("STRING", "STRING") RETURN_NAMES = ("PROMPT+", "PROMPT-") FUNCTION = "clean_network_tags_prompt" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): CONCEPT_LIST = utility.SUPPORTED_MODELS[0:26] CONCEPT_INPUTS = {} for concept in CONCEPT_LIST: CONCEPT_INPUTS["remove_from_" + concept.lower()] = ("BOOLEAN", {"default": True, "label_on": "REMOVE " + concept.upper(), "label_off": "KEEP " + concept.upper()}) return { "required": { "model_version": ("STRING", {"default": 'SD1', "forceInput": True}), "positive_prompt": ("STRING", {"forceInput": True}), "negative_prompt": ("STRING", {"forceInput": True}), "remove_embedding": ("BOOLEAN", {"default": False}), "remove_lora": ("BOOLEAN", {"default": False}), "remove_lycoris": ("BOOLEAN", {"default": False}), "remove_hypernetwork": ("BOOLEAN", {"default": False}), "auto_remover": ("BOOLEAN", {"default": True, "label_on": "Auto clean", "label_off": "Manual clean"}), **CONCEPT_INPUTS } } def clean_network_tags_prompt(self, positive_prompt, negative_prompt, remove_embedding, remove_lora, remove_lycoris, remove_hypernetwork, auto_remover = True, model_version='SD1', **kwargs): clean_state = True if auto_remover == True: NETWORK_START_GETVER = [] NETWORK_START_GETVER.append('', ' ', ',', '}', ')', '|'] + NETWORK_START_GETVER NETWORK_TUPLE_POS = utility.get_networks_prompt(NETWORK_START_GETVER, NETWORK_END, positive_prompt) NETWORK_TUPLE_NEG = utility.get_networks_prompt(NETWORK_START_GETVER, NETWORK_END, negative_prompt) if (len(NETWORK_TUPLE_POS) > 0 or len(NETWORK_TUPLE_NEG) > 0): LoraList = folder_paths.get_filename_list("loras") EmbeddingList = folder_paths.get_filename_list("embeddings") LYCO_DIR = os.path.join(folder_paths.models_dir, 'lycoris') folder_paths.add_model_folder_path("lycoris", LYCO_DIR) LyCORIS = folder_paths.get_filename_list("lycoris") LycorisList = folder_paths.filter_files_extensions(LyCORIS, ['.ckpt', '.safetensors']) if (len(NETWORK_TUPLE_POS) > 0): for NETWORK_DATA in NETWORK_TUPLE_POS: NetworkName = NETWORK_DATA[0] NetworkType = NETWORK_DATA[2] if NetworkType == 'LORA' and remove_lora == True: lora_name = utility.get_closest_element(NetworkName, LoraList) modelname_only = Path(lora_name).stem network_model_version = utility.get_value_from_cache('lora_version', modelname_only) if model_version != network_model_version: positive_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, positive_prompt, modelname_only) if NetworkType == 'LYCORIS' and remove_lycoris == True: lycoris_name = utility.get_closest_element(NetworkName, LycorisList) modelname_only = Path(lycoris_name).stem network_model_version = utility.get_value_from_cache('lycoris_version', modelname_only) if model_version != network_model_version: positive_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, positive_prompt, modelname_only) if NetworkType == 'EMBEDDING' and remove_embedding == True: embed_name = utility.get_closest_element(NetworkName, EmbeddingList) modelname_only = Path(embed_name).stem network_model_version = utility.get_value_from_cache('embedding_version', modelname_only) if model_version != network_model_version: positive_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, positive_prompt, modelname_only) if (len(NETWORK_TUPLE_NEG) > 0): for NETWORK_DATA in NETWORK_TUPLE_NEG: NetworkName = NETWORK_DATA[0] NetworkType = NETWORK_DATA[2] if NetworkType == 'LORA' and remove_lora == True: lora_name = utility.get_closest_element(NetworkName, LoraList) modelname_only = Path(lora_name).stem network_model_version = utility.get_value_from_cache('lora_version', modelname_only) if model_version != network_model_version: negative_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, negative_prompt, modelname_only) if NetworkType == 'LYCORIS' and remove_lycoris == True: lycoris_name = utility.get_closest_element(NetworkName, LycorisList) modelname_only = Path(lycoris_name).stem network_model_version = utility.get_value_from_cache('lycoris_version', modelname_only) if model_version != network_model_version: negative_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, negative_prompt, modelname_only) if NetworkType == 'EMBEDDING' and remove_embedding == True: embed_name = utility.get_closest_element(NetworkName, EmbeddingList) modelname_only = Path(embed_name).stem network_model_version = utility.get_value_from_cache('embedding_version', modelname_only) if model_version != network_model_version: negative_prompt = utility.clear_prompt(NETWORK_START_GETVER, NETWORK_END, negative_prompt, modelname_only) return (positive_prompt, negative_prompt,) else: input_data = kwargs SUPPORTED_CONCEPTS = utility.SUPPORTED_MODELS SUPPORTED_CONCEPTS_UC = [x.upper() for x in SUPPORTED_CONCEPTS] concept_processor = [] for inputKey, inputValue in input_data.items(): if inputKey.startswith("remove_from_") == True: conceptSignUC = inputKey[len("remove_from_"):].upper() conceptIndex = SUPPORTED_CONCEPTS_UC.index(conceptSignUC) CONCEPT_SIGN = SUPPORTED_CONCEPTS[conceptIndex] concept_processor.append(inputValue) if inputValue == False and model_version == CONCEPT_SIGN: clean_state = False if clean_state == False: return (positive_prompt, negative_prompt,) NETWORK_START = [] if remove_embedding == True: NETWORK_START.append('embedding:') if remove_lora == True: NETWORK_START.append(' 0: NETWORK_END = ['\n', '>', ' ', ',', '}', ')', '|'] + NETWORK_START positive_prompt = utility.clear_prompt(NETWORK_START, NETWORK_END, positive_prompt) negative_prompt = utility.clear_prompt(NETWORK_START, NETWORK_END, negative_prompt) return (positive_prompt, negative_prompt,) class PrimereDiTPurifyPrompt: RETURN_TYPES = ("STRING", "STRING") RETURN_NAMES = ("PROMPT+", "PROMPT-") FUNCTION = "dit_purify_prompt" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): CONCEPT_LIST = utility.SUPPORTED_MODELS[0:26] CONCEPT_INPUTS = {} for concept in CONCEPT_LIST: CONCEPT_INPUTS["purify_" + concept.lower()] = ("BOOLEAN", {"default": True, "label_on": "PURIFY " + concept.upper(), "label_off": "KEEP " + concept.upper()}) return { "required": { "model_version": ("STRING", {"default": 'SD1', "forceInput": True}), "positive_prompt": ("STRING", {"forceInput": True}), "negative_prompt": ("STRING", {"forceInput": True}), "max_length": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 10}), **CONCEPT_INPUTS } } def dit_purify_prompt(self, positive_prompt, negative_prompt, model_version = "SD1", max_length = 0, **kwargs): purify_state = True input_data = kwargs SUPPORTED_CONCEPTS = utility.SUPPORTED_MODELS SUPPORTED_CONCEPTS_UC = [x.upper() for x in SUPPORTED_CONCEPTS] concept_processor = [] for inputKey, inputValue in input_data.items(): if inputKey.startswith("purify_") == True: conceptSignUC = inputKey[len("purify_"):].upper() conceptIndex = SUPPORTED_CONCEPTS_UC.index(conceptSignUC) CONCEPT_SIGN = SUPPORTED_CONCEPTS[conceptIndex] concept_processor.append(inputValue) if inputValue == False and model_version == CONCEPT_SIGN: purify_state = False if purify_state == False: return (positive_prompt, negative_prompt,) positive_prompt = utility.DiT_cleaner(positive_prompt, max_length) negative_prompt = utility.DiT_cleaner(negative_prompt, max_length) return (positive_prompt, negative_prompt,) class PrimereNetworkTagLoader: RETURN_TYPES = ("MODEL", "CLIP", "LORA_STACK", "LYCORIS_STACK", "HYPERNETWORK_STACK", "MODEL_KEYWORD", "MODEL_KEYWORD") RETURN_NAMES = ("MODEL", "CLIP", "LORA_STACK", "LYCORIS_STACK", "HYPERNETWORK_STACK", "LORA_KEYWORD", "LYCORIS_KEYWORD") FUNCTION = "load_networks" CATEGORY = TREE_DASHBOARD LORASCOUNT = PrimereLORA.LORASCOUNT EMBCOUNT = PrimereEmbedding.EMBCOUNT HNCOUNT = PrimereHypernetwork.HNCOUNT LYCOSCOUNT = PrimereLYCORIS.LYCOSCOUNT @classmethod def INPUT_TYPES(s): return { "required": { "model": ("MODEL",), "clip": ("CLIP",), "positive_prompt": ("STRING", {"forceInput": True}), "process_lora": ("BOOLEAN", {"default": True}), "process_lycoris": ("BOOLEAN", {"default": True}), "process_hypernetwork": ("BOOLEAN", {"default": True}), "hypernetwork_safe_load": ("BOOLEAN", {"default": True}), "copy_weight_to_clip": ("BOOLEAN", {"default": False}), "lora_clip_custom_weight": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), "lycoris_clip_custom_weight": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), "use_lora_keyword": ("BOOLEAN", {"default": False}), "lora_keyword_placement": (["First", "Last"], {"default": "Last"}), "lora_keyword_selection": (["Select in order", "Random select"], {"default": "Select in order"}), "lora_keywords_num": ("INT", {"default": 1, "min": 1, "max": 50, "step": 1}), "lora_keyword_weight": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.1}), "use_lycoris_keyword": ("BOOLEAN", {"default": False}), "lycoris_keyword_placement": (["First", "Last"], {"default": "Last"}), "lycoris_keyword_selection": (["Select in order", "Random select"], {"default": "Select in order"}), "lycoris_keywords_num": ("INT", {"default": 1, "min": 1, "max": 50, "step": 1}), "lycoris_keyword_weight": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.1}), }, "optional": { "control_data": ("TUPLE", {"default": None}), } } def load_networks(self, model, clip, positive_prompt, process_lora, process_lycoris, process_hypernetwork, copy_weight_to_clip, lora_clip_custom_weight, lycoris_clip_custom_weight, use_lora_keyword, use_lycoris_keyword, lora_keyword_placement, lycoris_keyword_placement, lora_keyword_selection, lycoris_keyword_selection, lora_keywords_num, lycoris_keywords_num, lora_keyword_weight, lycoris_keyword_weight, hypernetwork_safe_load=True, control_data=None): if control_data is not None and len(control_data) > 0 and 'setup_states' in control_data and 'exif_status' in control_data and control_data['exif_status'] == 'SUCCEED': concept = 'Auto' stack_version = control_data['model_version'] if 'model_concept' in control_data: concept = control_data['model_concept'] if 'model_version' in control_data: if concept == 'Auto' and control_data['model_version'] == 'SDXL': stack_version = 'SDXL' if 'setup_states' in control_data and 'network_data' in control_data: if 'lora_setup' in control_data['setup_states'] and control_data['setup_states']['lora_setup'] == True: loader = networkhandler.getNetworkLoader(control_data, 'lora', self.LORASCOUNT, True, stack_version) if len(loader) > 0: networkData = networkhandler.LoraHandler(self, loader, model, clip, [], False, lora_keywords_num, use_lora_keyword, lora_keyword_selection, lora_keyword_weight, lora_keyword_placement) model = networkData[0] clip = networkData[1] if 'lycoris_setup' in control_data['setup_states'] and control_data['setup_states']['lycoris_setup'] == True: loader = networkhandler.getNetworkLoader(control_data, 'lycoris', self.LYCOSCOUNT, True, stack_version) if len(loader) > 0: networkData = networkhandler.LycorisHandler(self, loader, model, clip, [], False, lycoris_keywords_num, use_lycoris_keyword, lycoris_keyword_selection, lycoris_keyword_weight, lycoris_keyword_placement) model = networkData[0] clip = networkData[1] if 'embedding_setup' in control_data['setup_states'] and control_data['setup_states']['embedding_setup'] == True: loader = networkhandler.getNetworkLoader(control_data, 'embedding', self.EMBCOUNT, False, stack_version) if len(loader) > 0: networkData = networkhandler.EmbeddingHandler(self, loader, None, None) if networkData[0][0] is not None: positive_prompt = networkData[0][0] + ', ' + positive_prompt tokens = clip.tokenize(positive_prompt) clip = clip.encode_from_tokens(tokens, return_pooled=False) if 'hypernetwork_setup' in control_data['setup_states'] and control_data['setup_states']['hypernetwork_setup'] == True: loader = networkhandler.getNetworkLoader(control_data, 'hypernetwork', self.HNCOUNT, False, stack_version) if len(loader) > 0: networkData = networkhandler.HypernetworkHandler(self, loader, model, hypernetwork_safe_load) model = networkData[0] NETWORK_START = [] cloned_model = model cloned_clip = clip list_of_keyword_items = [] lora_keywords_num_set = lora_keywords_num lycoris_keywords_num_set = lycoris_keywords_num model_lora_keyword = [None, None] model_lyco_keyword = [None, None] lora_stack = [] lycoris_stack = [] hnet_stack = [] HypernetworkList = folder_paths.get_filename_list("hypernetworks") LoraList = folder_paths.get_filename_list("loras") LYCO_DIR = os.path.join(folder_paths.models_dir, 'lycoris') folder_paths.add_model_folder_path("lycoris", LYCO_DIR) LyCORIS = folder_paths.get_filename_list("lycoris") LycorisList = folder_paths.filter_files_extensions(LyCORIS, ['.ckpt', '.safetensors']) if process_lora == True: NETWORK_START.append(''] + NETWORK_START NETWORK_TUPLE = utility.get_networks_prompt(NETWORK_START, NETWORK_END, positive_prompt) if (len(NETWORK_TUPLE) == 0): return (model, clip, lora_stack, lycoris_stack, hnet_stack, model_lora_keyword, model_lyco_keyword) else: for NETWORK_DATA in NETWORK_TUPLE: NetworkName = NETWORK_DATA[0] try: NetworkStrenght = float(NETWORK_DATA[1]) except ValueError: NetworkStrenght = 1 NetworkType = NETWORK_DATA[2] if (process_lora == True and NetworkType == 'LORA'): lora_name = utility.get_closest_element(NetworkName, LoraList) if lora_name is not None: lora_path = folder_paths.get_full_path("loras", lora_name) lora = comfy.utils.load_torch_file(lora_path, safe_load=True) if (copy_weight_to_clip == True): lora_clip_custom_weight = NetworkStrenght lora_stack.append([lora_name, NetworkStrenght, lora_clip_custom_weight]) try: cloned_model, cloned_clip = comfy.sd.load_lora_for_models(cloned_model, cloned_clip, lora, NetworkStrenght, lora_clip_custom_weight) except Exception: print('Lora load error...') use_lora_keyword = False if use_lora_keyword == True: ModelKvHash = utility.get_model_hash(lora_path) if ModelKvHash is not None: KEYWORD_PATH = os.path.join(PRIMERE_ROOT, 'front_end', 'keywords', 'lora-keyword.txt') keywords = utility.get_model_keywords(KEYWORD_PATH, ModelKvHash, lora_name) if keywords is not None and keywords != "" and isinstance(keywords, str) == True: if keywords.find('|') > 1: keyword_list = [word.strip() for word in keywords.split('|')] keyword_list = list(filter(None, keyword_list)) if (len(keyword_list) > 0): lora_keywords_num = lora_keywords_num_set keyword_qty = len(keyword_list) if (lora_keywords_num > keyword_qty): lora_keywords_num = keyword_qty if lora_keyword_selection == 'Select in order': list_of_keyword_items.extend(keyword_list[:lora_keywords_num]) else: list_of_keyword_items.extend(random.sample(keyword_list, lora_keywords_num)) else: list_of_keyword_items.append(keywords) if len(list_of_keyword_items) > 0: if lora_keyword_selection != 'Select in order': random.shuffle(list_of_keyword_items) list_of_keyword_items = list(set(list_of_keyword_items)) keywords = ", ".join(list_of_keyword_items) if (lora_keyword_weight != 1): keywords = '(' + keywords + ':' + str(round(lora_keyword_weight, 1)) + ')' model_lora_keyword = [keywords, lora_keyword_placement] if (process_lycoris == True and NetworkType == 'LYCORIS'): lycoris_name = utility.get_closest_element(NetworkName, LycorisList) if lycoris_name is not None: lycoris_path = folder_paths.get_full_path("lycoris", lycoris_name) lycoris = comfy.utils.load_torch_file(lycoris_path, safe_load=True) if (copy_weight_to_clip == True): lycoris_clip_custom_weight = NetworkStrenght lycoris_stack.append([lycoris_name, NetworkStrenght, lycoris_clip_custom_weight]) try: cloned_model, cloned_clip = comfy.sd.load_lora_for_models(cloned_model, cloned_clip, lycoris, NetworkStrenght, lycoris_clip_custom_weight) except Exception: print('Lycoris load error...') use_lycoris_keyword = False if use_lycoris_keyword == True: ModelKvHash = utility.get_model_hash(lycoris_path) if ModelKvHash is not None: KEYWORD_PATH = os.path.join(PRIMERE_ROOT, 'front_end', 'keywords', 'lora-keyword.txt') keywords = utility.get_model_keywords(KEYWORD_PATH, ModelKvHash, lycoris_name) if keywords is not None and keywords != "" and isinstance(keywords, str) == True: if keywords.find('|') > 1: keyword_list = [word.strip() for word in keywords.split('|')] keyword_list = list(filter(None, keyword_list)) if (len(keyword_list) > 0): lycoris_keywords_num = lycoris_keywords_num_set keyword_qty = len(keyword_list) if (lycoris_keywords_num > keyword_qty): lycoris_keywords_num = keyword_qty if lycoris_keyword_selection == 'Select in order': list_of_keyword_items.extend(keyword_list[:lycoris_keywords_num]) else: list_of_keyword_items.extend(random.sample(keyword_list, lycoris_keywords_num)) else: list_of_keyword_items.append(keywords) if len(list_of_keyword_items) > 0: if lycoris_keyword_selection != 'Select in order': random.shuffle(list_of_keyword_items) list_of_keyword_items = list(set(list_of_keyword_items)) keywords = ", ".join(list_of_keyword_items) if (lycoris_keyword_weight != 1): keywords = '(' + keywords + ':' + str(round(lycoris_keyword_weight, 1)) + ')' model_lyco_keyword = [keywords, lycoris_keyword_placement] if (process_hypernetwork == True and NetworkType == 'HYPERNET'): hyper_name = utility.get_closest_element(NetworkName, HypernetworkList) if hyper_name is not None: hypernetwork_path = folder_paths.get_full_path("hypernetworks", hyper_name) model_hypernetwork = cloned_model.clone() try: patch = hypernetwork.load_hypernetwork_patch(hypernetwork_path, NetworkStrenght, hypernetwork_safe_load) except Exception: patch = None if patch is not None: model_hypernetwork.set_model_attn1_patch(patch) model_hypernetwork.set_model_attn2_patch(patch) hnet_stack.append([hyper_name, NetworkStrenght]) cloned_model = model_hypernetwork return (cloned_model, cloned_clip, lora_stack, lycoris_stack, hnet_stack, model_lora_keyword, model_lyco_keyword) class PrimereModelKeyword: RETURN_TYPES = ("MODEL_KEYWORD",) RETURN_NAMES = ("MODEL_KEYWORD",) FUNCTION = "load_ckpt_keyword" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): return { "required": { "use_model_keyword": ("BOOLEAN", {"default": False}), "model_keyword_placement": (["First", "Last"], {"default": "Last"}), # "model_keyword_selection": (["Select in order", "Random select"], {"default": "Select in order"}), "model_keywords_num": ("INT", {"default": 1, "min": 1, "max": 50, "step": 1}), "model_keyword_weight": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.1}), "select_keyword": (utility.KEYWORD_SELECTOR_VALUES, {"default": "Select in order"}), }, "optional": { "model_name": ('CHECKPOINT_NAME', {"forceInput": True, "default": None}) }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", "prompt": "PROMPT" }, } def load_ckpt_keyword(self, model_name, use_model_keyword, model_keyword_placement, model_keywords_num, model_keyword_weight, prompt, **kwargs): model_keyword = [None, None] WORKFLOWDATA = kwargs['extra_pnginfo']['workflow']['nodes'] # selectedKeyword = utility.getDataFromWorkflow(WORKFLOWDATA, 'PrimereModelKeyword', 4) selectedKeyword = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelKeyword', 'select_keyword', prompt) if use_model_keyword == True and selectedKeyword != 'None' and selectedKeyword != None: if selectedKeyword != 'Select in order' and selectedKeyword != 'Random select': if selectedKeyword.rfind('/') != -1: selectedKeyword = selectedKeyword.rsplit('/', 1)[1].strip() if (model_keyword_weight != 1): selectedKeyword = '(' + selectedKeyword + ':' + str(round(model_keyword_weight, 1)) + ')' model_keyword = [selectedKeyword, model_keyword_placement] else: ckpt_path = folder_paths.get_full_path("checkpoints", model_name) is_link = os.path.islink(str(ckpt_path)) ModelKvHash = None if is_link == False: ModelKvHash = utility.get_model_hash(ckpt_path) if ModelKvHash is not None: KEYWORD_PATH = os.path.join(PRIMERE_ROOT, 'front_end', 'keywords', 'model-keyword.txt') keywords = utility.get_model_keywords(KEYWORD_PATH, ModelKvHash, model_name) if keywords is not None and isinstance(keywords, str) == True: if keywords.find('|') > 1: keyword_list = keywords.split("|") if (len(keyword_list) > 0): keyword_qty = len(keyword_list) if (model_keywords_num > keyword_qty): model_keywords_num = keyword_qty if selectedKeyword == 'Select in order': list_of_keyword_items = keyword_list[:model_keywords_num] else: list_of_keyword_items = random.sample(keyword_list, model_keywords_num) clean_keywords = [] for keyword_item in list_of_keyword_items: if keyword_item.rfind('/') != -1: keyword_item = keyword_item.rsplit('/', 1)[1].strip() clean_keywords += [keyword_item] keywords = ", ".join(clean_keywords) if (model_keyword_weight != 1): keywords = '(' + keywords + ':' + str(round(model_keyword_weight, 1)) + ')' model_keyword = [keywords, model_keyword_placement] return (model_keyword,) class PrimereUpscaleModel: RETURN_TYPES = ("UPSCALE_MODEL", folder_paths.get_filename_list("upscale_models"),) RETURN_NAMES = ("UPSCALE_MODEL", 'MODEL_NAME',) FUNCTION = "load_upscaler" CATEGORY = TREE_DASHBOARD @classmethod def INPUT_TYPES(cls): return { "required": { "model_name": (folder_paths.get_filename_list("upscale_models"),), } } def load_upscaler(self, model_name): out = nodes_upscale_model.UpscaleModelLoader.execute(model_name)[0] return (out, model_name,)