From 5d7337f16c5db292e25a2f3c8126454fb5ae8efb Mon Sep 17 00:00:00 2001 From: "DESKTOP-TVBJISQ\\Primere" Date: Sun, 15 Mar 2026 16:17:03 +0100 Subject: [PATCH] V 2.0.0 - Auto config #22 - new params --- Nodes/Dashboard.py | 237 +++++++++++--------- Nodes/Outputs.py | 226 +++++++------------ __init__.py | 4 +- components/clipping.py | 133 ++++++----- components/models.py | 75 ++++++- components/primeresamplers.py | 120 ++++++---- front_end/model_concepts.js | 8 +- front_end/primere_visuals.js | 4 +- terminal_helpers/embedding_version_cache.py | 49 +++- terminal_helpers/lora_version_cache.py | 61 +++-- terminal_helpers/lyco_version_cache.py | 61 +++-- terminal_helpers/model_version_cache.py | 102 +++++---- 12 files changed, 640 insertions(+), 440 deletions(-) diff --git a/Nodes/Dashboard.py b/Nodes/Dashboard.py index b0de481..fd4ecb7 100644 --- a/Nodes/Dashboard.py +++ b/Nodes/Dashboard.py @@ -647,11 +647,14 @@ class PrimereModelConceptSelector: zimage_model, zimage_clip, zimage_vae ) -class PrimereAutoSamplerSettings: +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 = ("DATA", "SAMPLER_NAME", "SCHEDULER_NAME", "STEPS", "CFG", "MODEL_CONCEPT") - FUNCTION = "get_controlledsampler" + 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"] @@ -686,57 +689,74 @@ class PrimereAutoSamplerSettings: "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}), - "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}), - "last_layer": ("INT", {"default": 0, "min": -24, "max": 0, "step": 1}), - "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}), + "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)),), - "clip_attn": (["Custom"] + list(clipping.CLIP_ATTN_PRESETS.keys()), {"default": "Natural"}), - "clip_attn_mult_query": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), - "clip_attn_mult_key": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), - "clip_attn_mult_value": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), - "clip_attn_mult_output": ('FLOAT', {"default": 1.00, "min": 0.80, "max": 1.20, "step": 0.01}), + + "attn_preset": (["Custom", "Auto"] + 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}), + "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_name": (cls.LCM_LORAS,), "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_version": ([1.0, 1.1, 2.0], {"default": 2.0}), - # "speed_lora_precision": ("BOOLEAN", {"default": True, "label_on": "FP32", "label_off": "BF16"}), - # "speed_lora_step": ([4, 6, 8, 10, 12, 16], {"default": 8}), "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_type": (["R&Q", "RockerBOO", "oficial", "adaptive"], {"default": "oficial"}), - # "srpo_lora_rank": ([8, 16, 32, 64, 128, 256], {"default": 8}), "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_type": (["kontext_deblur", "kontext_face_detailer", "anything_extracted"], {"default": "anything_extracted"}), - # "nunchaku_lora_rank": ([64, 256], {"default": 64}), "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"}), @@ -746,11 +766,11 @@ class PrimereAutoSamplerSettings: "refiner_start": ("INT", {"default": 12, "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"}), + "refiner_ignore_prompt": ("BOOLEAN", {"default": True, "label_on": "Ignore prompt", "label_off": "Send prompt to refiner"}) } } - def get_controlledsampler(self, **kwargs): + def get_primeremodelcontrol(self, **kwargs): model_concept = kwargs.pop('model_concept', 'SD1') model_name = kwargs.pop('model_name', None) concepts = kwargs.pop('concepts', 'Auto') @@ -802,18 +822,27 @@ class PrimereAutoSamplerSettings: speed_lora_cfg_val = kwargs.get('speed_lora_cfg') if speed_lora_cfg_val is not None: cfg = float(speed_lora_cfg_val) - clip_attn = kwargs.pop('clip_attn', 'Natural') - clip_attn_mult_query = kwargs.pop('clip_attn_mult_query', 1.0) - clip_attn_mult_key = kwargs.pop('clip_attn_mult_key', 1.0) - clip_attn_mult_value = kwargs.pop('clip_attn_mult_value', 1.0) - clip_attn_mult_output = kwargs.pop('clip_attn_mult_output', 1.0) - if clip_attn == 'Custom': - attn_q, attn_k, attn_v, attn_out = clip_attn_mult_query, clip_attn_mult_key, clip_attn_mult_value, clip_attn_mult_output + 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) + 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 = clipping.CLIP_ATTN_PRESETS.get(clip_attn, (1.0, 1.0, 1.0, 1.0)) + 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) 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_name'] = model_name kwargs['model_concept'] = active_concept kwargs['sampler_name'] = sampler_name kwargs['scheduler_name'] = scheduler_name @@ -823,6 +852,10 @@ class PrimereAutoSamplerSettings: 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 return {"ui": {"active_concept": [active_display]}, "result": (kwargs, sampler_name, scheduler_name, steps, round(cfg, 2), active_concept,)} class PrimereConceptDataTuple: @@ -864,15 +897,15 @@ class PrimereCKPTLoader: }, "optional": { # "model_concept": ("STRING", {"forceInput": True}), - "concept_data": ("TUPLE", {"default": None, "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, concept_data=None, loaded_model=None, loaded_clip=None, loaded_vae=None): - model_concept = concept_data['model_concept'] + 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() @@ -901,35 +934,35 @@ class PrimereCKPTLoader: 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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, concept_data) + 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) @@ -1036,7 +1069,7 @@ class PrimereFastSeed: class PrimereFractalLatent: RETURN_TYPES = ("LATENT", "IMAGE", "TUPLE") - RETURN_NAMES = ("LATENTS", "PREVIEWS", "WORKFLOW_TUPLE") + RETURN_NAMES = ("LATENTS", "PREVIEWS", "CONTROL_DATA") FUNCTION = "primere_latent_noise" CATEGORY = TREE_DASHBOARD @@ -1062,7 +1095,7 @@ class PrimereFractalLatent: }, "optional": { "optional_vae": ("VAE",), - "workflow_tuple": ("TUPLE", {"default": None}), + "control_data": ("TUPLE", {"default": None}), } } @@ -1075,19 +1108,19 @@ class PrimereFractalLatent: 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, workflow_tuple=None, expand_random_limits=False): - if workflow_tuple is not None and len(workflow_tuple) > 0 and 'exif_status' in workflow_tuple and workflow_tuple['exif_status'] == 'SUCCEED': - if 'latent_data' in workflow_tuple and len(workflow_tuple['latent_data']) > 0 and 'setup_states' in workflow_tuple and 'latent_setup' in workflow_tuple['setup_states']: - if workflow_tuple['setup_states']['latent_setup'] == True: + 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 = workflow_tuple['latent_data']['noise_type'] - device = workflow_tuple['latent_data']['device'] - alpha_exponent = workflow_tuple['latent_data']['alpha_exponent'] - modulator = workflow_tuple['latent_data']['modulator'] + 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 @@ -1124,7 +1157,7 @@ class PrimereFractalLatent: 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, workflow_tuple + return {'samples': latents}, tensors, control_data encoder = nodes.VAEEncode() latents = [] @@ -1135,21 +1168,21 @@ class PrimereFractalLatent: 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, workflow_tuple + return {'samples': latents}, tensors, control_data latents = torch.cat(latents) - if workflow_tuple is not None: - workflow_tuple['latent_data'] = {} - workflow_tuple['latent_data']['noise_type'] = noise_type - workflow_tuple['latent_data']['alpha_exponent'] = alpha_exponent - workflow_tuple['latent_data']['modulator'] = modulator - workflow_tuple['latent_data']['device'] = device + 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, workflow_tuple + return {'samples': latents}, tensors, control_data class PrimereCLIP: RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "STRING", "STRING", "STRING", "STRING", "STRING", "TUPLE") - RETURN_NAMES = ("COND+", "COND-", "PROMPT+", "PROMPT-", "T5XXL_PROMPT", "PROMPT L+", "PROMPT L-", "WORKFLOW_TUPLE") + RETURN_NAMES = ("COND+", "COND-", "PROMPT+", "PROMPT-", "T5XXL_PROMPT", "PROMPT L+", "PROMPT L-", "CONTROL_DATA") FUNCTION = "clip_encode" CATEGORY = TREE_DASHBOARD @@ -1176,7 +1209,7 @@ class PrimereCLIP: return { "required": { "clip": ("CLIP", {"forceInput": True}), - "concept_data": ("TUPLE", {"default": None, "forceInput": True}), + "control_data": ("TUPLE", {"default": None, "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}), @@ -1220,7 +1253,7 @@ class PrimereCLIP: "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}), - "workflow_tuple": ("TUPLE", {"default": None}), + "control_data": ("TUPLE", {"default": None}), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", @@ -1228,8 +1261,8 @@ class PrimereCLIP: } } - def clip_encode(self, clip, concept_data, negative_strength, int_style_pos_strength, int_style_neg_strength, opt_pos_strength, opt_neg_strength, style_pos_strength, style_neg_strength, style_handling, style_swap, enhanced_prompt_strength, int_style_pos, int_style_neg, adv_encode, token_normalization, weight_interpretation, l_strength, extra_pnginfo, prompt, copy_prompt_to_l=True, width=1024, height=1024, positive_prompt="", negative_prompt="", enhanced_prompt="", enhanced_prompt_usage="T5-XXL", clip_model='Default', longclip_model='Default', model_keywords=None, lora_keywords=None, lycoris_keywords=None, embedding_pos=None, embedding_neg=None, opt_pos_prompt="", opt_neg_prompt="", style_position=False, style_neg_prompt="", style_pos_prompt="", positive_l="", negative_l="", use_int_style=False, edit_image_list=None, edit_vae=None, workflow_tuple=None): - model_concept = concept_data.get('model_concept', 'SD1') + def clip_encode(self, clip, negative_strength, int_style_pos_strength, int_style_neg_strength, opt_pos_strength, opt_neg_strength, style_pos_strength, style_neg_strength, style_handling, style_swap, enhanced_prompt_strength, int_style_pos, int_style_neg, adv_encode, token_normalization, weight_interpretation, l_strength, extra_pnginfo, prompt, copy_prompt_to_l=True, width=1024, height=1024, positive_prompt="", negative_prompt="", enhanced_prompt="", enhanced_prompt_usage="T5-XXL", clip_model='Default', longclip_model='Default', model_keywords=None, lora_keywords=None, lycoris_keywords=None, embedding_pos=None, embedding_neg=None, opt_pos_prompt="", opt_neg_prompt="", style_position=False, style_neg_prompt="", style_pos_prompt="", positive_l="", negative_l="", use_int_style=False, edit_image_list=None, edit_vae=None, control_data=None): + 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: @@ -1250,29 +1283,29 @@ class PrimereCLIP: embedding_pos, embedding_neg, ) - clip = clipping.apply_clip_attention_multiply(clip, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + 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, workflow_tuple) + # return clipping.encode_chroma(clip, positive_text, negative_text, control_data) case _: - clip = clipping.apply_clip_overrides(self, clip, workflow_tuple) - return clipping.encode_standard(clip, positive_text, negative_text, t5xxl_prompt, adv_encode, token_normalization, weight_interpretation, positive_l, negative_l, width, height, workflow_tuple, advanced_encode) + 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") @@ -1750,37 +1783,37 @@ class PrimereNetworkTagLoader: "lycoris_keyword_weight": ("FLOAT", {"default": 1.0, "min": 0, "max": 10.0, "step": 0.1}), }, "optional": { - "workflow_tuple": ("TUPLE", {"default": None}), + "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, workflow_tuple=None): - if workflow_tuple is not None and len(workflow_tuple) > 0 and 'setup_states' in workflow_tuple and 'exif_status' in workflow_tuple and workflow_tuple['exif_status'] == 'SUCCEED': + 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 = workflow_tuple['model_version'] - if 'model_concept' in workflow_tuple: - concept = workflow_tuple['model_concept'] - if 'model_version' in workflow_tuple: - if concept == 'Auto' and workflow_tuple['model_version'] == 'SDXL': + 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 workflow_tuple and 'network_data' in workflow_tuple: - if 'lora_setup' in workflow_tuple['setup_states'] and workflow_tuple['setup_states']['lora_setup'] == True: - loader = networkhandler.getNetworkLoader(workflow_tuple, 'lora', self.LORASCOUNT, True, stack_version) + 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 workflow_tuple['setup_states'] and workflow_tuple['setup_states']['lycoris_setup'] == True: - loader = networkhandler.getNetworkLoader(workflow_tuple, 'lycoris', self.LYCOSCOUNT, True, stack_version) + 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 workflow_tuple['setup_states'] and workflow_tuple['setup_states']['embedding_setup'] == True: - loader = networkhandler.getNetworkLoader(workflow_tuple, 'embedding', self.EMBCOUNT, False, stack_version) + 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: @@ -1788,8 +1821,8 @@ class PrimereNetworkTagLoader: tokens = clip.tokenize(positive_prompt) clip = clip.encode_from_tokens(tokens, return_pooled=False) - if 'hypernetwork_setup' in workflow_tuple['setup_states'] and workflow_tuple['setup_states']['hypernetwork_setup'] == True: - loader = networkhandler.getNetworkLoader(workflow_tuple, 'hypernetwork', self.HNCOUNT, False, stack_version) + 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] diff --git a/Nodes/Outputs.py b/Nodes/Outputs.py index 08b9d97..9c721a9 100644 --- a/Nodes/Outputs.py +++ b/Nodes/Outputs.py @@ -160,20 +160,8 @@ class PrimereMetaSave: subdirs.append(file_output.sanitize_path_part(Path(concept_name).stem.upper())) if add_modelname_to_path == True and 'model' in image_metadata: - if 'model_concept' in image_metadata and 'model_version' in image_metadata: - original_model_concept_selector = 'Auto' - if extra_pnginfo is not None: - WORKFLOWDATA = extra_pnginfo['workflow']['nodes'] - original_model_concept_selector = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'model_concept', prompt) - if image_metadata['model_concept'] != image_metadata['model_version'] or original_model_concept_selector != 'Auto': - match image_metadata['model_concept']: - case 'Flux': - if image_metadata['concept_data']['flux_selector'] == 'GGUF': - image_metadata['model'] = image_metadata['concept_data']['flux_gguf'] - else: - image_metadata['model'] = image_metadata['concept_data']['flux_diffusion'] - case 'StableCascade': - image_metadata['model'] = image_metadata['concept_data']['cascade_stage_c'] + if image_metadata.get('model_name'): + image_metadata['model'] = image_metadata['model_name'] subdirs.append(file_output.sanitize_path_part(Path(image_metadata['model']).stem.upper())) if subpath_priority == True and 'preferred' in image_metadata and type(image_metadata['preferred']).__name__ == 'dict' and len(image_metadata['preferred']) > 0 and 'subpath' in image_metadata['preferred']: @@ -396,27 +384,22 @@ class PrimereMetaCollector: INPUT_DICT = { "required": { - "positive": ('STRING', {"forceInput": True, "default": "Red sportcar racing"}), + "model_name": ('CHECKPOINT_NAME', {"forceInput": True, "default": None}), + "positive": ('STRING', {"forceInput": True, "default": "Red sportcar racing on the street of metropolis"}), "negative": ('STRING', {"forceInput": True, "default": "Cute cat, nsfw, nude, nudity, porn"}) }, "optional": { + "model_concept": ("STRING", {"default": "Auto", "forceInput": True}), + "control_data": ("TUPLE", {"default": None, "forceInput": True}), "seed": ('INT', {"forceInput": True, "default": 1}), + "t5_xxl_prompt": ('STRING', {"forceInput": True}), "positive_l": ('STRING', {"forceInput": True}), "negative_l": ('STRING', {"forceInput": True}), - "positive_r": ('STRING', {"forceInput": True}), - "negative_r": ('STRING', {"forceInput": True}), - "model": ('CHECKPOINT_NAME', {"forceInput": True, "default": None}), - "model_version": ("STRING", {"default": 'SD1', "forceInput": True}), - "model_concept": ("STRING", {"default": "Auto", "forceInput": True}), - "concept_data": ("TUPLE", {"default": None, "forceInput": True}), "sampler": (comfy.samplers.KSampler.SAMPLERS, {"forceInput": True, "default": "euler"}), "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"forceInput": True, "default": "normal"}), - "width": ('INT', {"forceInput": True, "default": 512}), - "height": ('INT', {"forceInput": True, "default": 512}), - "model_shapes": ('TUPLE', {"forceInput": True, "default": None}), "cfg": ('FLOAT', {"forceInput": True, "default": 7}), "steps": ('INT', {"forceInput": True, "default": 12}), - "vae_name_sd": ('VAE_NAME', {"forceInput": True, "default": None}), - "vae_name_sdxl": ('VAE_NAME', {"forceInput": True, "default": None}), + "width": ('INT', {"forceInput": True, "default": 512}), + "height": ('INT', {"forceInput": True, "default": 512}), "preferred": ("TUPLE", {"default": None, "forceInput": True}), "aesthetic_score": ('INT', {"forceInput": True, "default": 0}) }, @@ -446,7 +429,7 @@ class PrimereMetaCollector: class PrimereKSampler: CATEGORY = TREE_OUTPUTS RETURN_TYPES = ("LATENT", "TUPLE") - RETURN_NAMES = ("LATENT", "WORKFLOW_TUPLE") + RETURN_NAMES = ("LATENT", "CONTROL_DATA") FUNCTION = "pk_sampler" def __init__(self): @@ -471,13 +454,11 @@ class PrimereKSampler: "variation_extender": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), "variation_batch_step": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 0.5, "step": 0.01}), "variation_level": ("BOOLEAN", {"default": False, "label_on": "Maximize", "label_off": "Off"}), - # "model_sampling": ("FLOAT", {"default": 2.5, "min": 0.0, "max": 10.0, "step": 0.01}), - "device": (["DEFAULT", "GPU", "CPU"], {"default": 'DEFAULT'}), - # "align_your_steps": ("BOOLEAN", {"default": False, "label_on": "Use AlignYourSteps", "label_off": "Ignore AlignYourSteps"}), + "device": (["DEFAULT", "GPU", "CPU"], {"default": 'DEFAULT'}) }, "optional": { "model_concept": ("STRING", {"default": "Auto", "forceInput": True}), - "workflow_tuple": ("TUPLE", {"default": None}), + "control_data": ("TUPLE", {"default": None}), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", @@ -490,24 +471,24 @@ class PrimereKSampler: if kwargs['variation_extender'] > 0 or kwargs['device'] != 'DEFAULT' or kwargs['variation_batch_step'] > 0 or kwargs['variation_level'] == True: return float("NaN") - def pk_sampler(self, model, seed, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, extra_pnginfo, prompt, model_concept = "Auto", workflow_tuple = None, denoise=1.0, variation_extender = 0, variation_batch_step = 0, variation_level = False, model_sampling = 2.5, device = 'DEFAULT', align_your_steps = False): + def pk_sampler(self, model, seed, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, extra_pnginfo, prompt, model_concept = "Auto", control_data = None, denoise=1.0, variation_extender = 0, variation_batch_step = 0, variation_level = False, model_sampling = 2.5, device = 'DEFAULT', align_your_steps = False): timestamp_start = time.time() - if workflow_tuple is not None: - align_your_steps = workflow_tuple.get('align_your_steps', align_your_steps) - model_sampling = workflow_tuple.get('model_sampling', model_sampling) - if workflow_tuple is not None and len(workflow_tuple) > 0 and 'exif_status' in workflow_tuple and workflow_tuple['exif_status'] == 'SUCCEED': - if 'sampler_settings' in workflow_tuple and len(workflow_tuple['sampler_settings']) > 0 and 'setup_states' in workflow_tuple and 'sampler_setup' in workflow_tuple['setup_states']: - if workflow_tuple['setup_states']['sampler_setup'] == True: + if control_data is not None: + align_your_steps = control_data.get('align_your_steps', align_your_steps) + model_sampling = control_data.get('model_sampling', model_sampling) + if control_data is not None and len(control_data) > 0 and 'exif_status' in control_data and control_data['exif_status'] == 'SUCCEED': + if 'sampler_settings' in control_data and len(control_data['sampler_settings']) > 0 and 'setup_states' in control_data and 'sampler_setup' in control_data['setup_states']: + if control_data['setup_states']['sampler_setup'] == True: variation_batch_step = 0 variation_level = False - denoise = workflow_tuple['sampler_settings']['denoise'] - device = workflow_tuple['sampler_settings']['device'] - align_your_steps = workflow_tuple['sampler_settings']['align_your_steps'] - model_sampling = workflow_tuple['sampler_settings']['model_sampling'] - if workflow_tuple['sampler_settings']['variation_level'] == True: - variation_extender = workflow_tuple['sampler_settings']['noise_constant'] + denoise = control_data['sampler_settings']['denoise'] + device = control_data['sampler_settings']['device'] + align_your_steps = control_data['sampler_settings']['align_your_steps'] + model_sampling = control_data['sampler_settings']['model_sampling'] + if control_data['sampler_settings']['variation_level'] == True: + variation_extender = control_data['sampler_settings']['noise_constant'] else: - variation_extender = workflow_tuple['sampler_settings']['variation_extender_original'] + variation_extender = control_data['sampler_settings']['variation_extender_original'] samples_out = latent_image # out = latent_image.copy() @@ -605,7 +586,7 @@ class PrimereKSampler: variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, - align_your_steps, noise_extender_ksampler, workflow_tuple)[0] + align_your_steps, noise_extender_ksampler, control_data)[0] case "KwaiKolors": samples_out = primeresamplers.PSamplerKOROLS(self, model, seed, cfg, positive, negative, latent_image, steps, denoise, sampler_name, scheduler_name, model_sampling, 1000)[0] case "SD3": @@ -617,20 +598,17 @@ class PrimereKSampler: noise_constant = noise_extender_cascade samples_out = primeresamplers.PCascadeSampler(self, model, seed, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, device, variation_level, variation_limit, variation_extender_original, variation_batch_step_original, variation_extender, variation_batch_step, batch_counter, noise_extender_cascade)[0] case "Hyper": - CONCEPT_SELECTOR = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'model_concept', prompt) - OriginalBaseModel = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', prompt) + CONCEPT_SELECTOR = control_data.get('model_concept') if control_data else None + OriginalBaseModel = control_data.get('model_name') if control_data else None fullpathFile = folder_paths.get_full_path('checkpoints', OriginalBaseModel) is_link = os.path.islink(str(fullpathFile)) - if is_link == True: - HYPERSD_SELECTOR = 'UNET' - else: - HYPERSD_SELECTOR = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'hypersd_selector', prompt) - HYPERSD_SAMPLER = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'hypersd_sampler', prompt) + HYPERSD_SELECTOR = 'UNET' if is_link else 'LORA' + HYPERSD_SAMPLER = True if model_concept == 'Hyper' and (CONCEPT_SELECTOR == 'Hyper' or CONCEPT_SELECTOR == 'Auto') and steps == 12 and HYPERSD_SELECTOR == 'LORA' and HYPERSD_SAMPLER == True: - cfg = 3.80 - scheduler_name = 'normal' - samples_out = primeresamplers.PSamplerHyper(self, extra_pnginfo, model, seed, steps, cfg, positive, negative, sampler_name, scheduler_name, latent_image, denoise, prompt)[0] + cfg = float(control_data.get('cfg', 3.80)) + scheduler_name = control_data.get('scheduler_name', "normal") + samples_out = primeresamplers.PSamplerHyper(self, extra_pnginfo, model, seed, steps, cfg, positive, negative, sampler_name, scheduler_name, latent_image, denoise, prompt, control_data)[0] case 'QwenGen' | 'QwenEdit': align_your_steps = False @@ -657,14 +635,14 @@ class PrimereKSampler: align_your_steps, noise_extender_ksampler, None)[0] case 'Flux': - FLUX_SAMPLER = workflow_tuple.get('sampler', 'ksampler') if workflow_tuple else 'ksampler' - FLUX_GUIDANCE = float(workflow_tuple.get('guidance', 3.5)) if workflow_tuple else 3.5 + FLUX_SAMPLER = control_data.get('sampler', 'ksampler') if control_data else 'ksampler' + FLUX_GUIDANCE = float(control_data.get('guidance', 3.5)) if control_data else 3.5 align_your_steps = False if FLUX_SAMPLER == 'custom_advanced': samples_out = primeresamplers.PSamplerAdvanced(self, model, seed, FLUX_GUIDANCE, positive, scheduler_name, sampler_name, steps, denoise, latent_image)[0] elif FLUX_SAMPLER == 'ksampler': CONDITIONING_POS = nodes_flux.FluxGuidance.execute(positive, FLUX_GUIDANCE)[0] if FLUX_GUIDANCE > 0 else positive - if workflow_tuple is not None and float(workflow_tuple.get('cfg', 2.0)) < 1.2: + if control_data is not None and float(control_data.get('cfg', 2.0)) < 1.2: CONDITIONING_NEG = CONDITIONING_POS else: CONDITIONING_NEG = nodes_flux.FluxGuidance.execute(negative, FLUX_GUIDANCE)[0] @@ -673,14 +651,14 @@ class PrimereKSampler: CONDITIONING_POS, CONDITIONING_NEG, latent_image, denoise, variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, - align_your_steps, noise_extender_ksampler, None)[0] + align_your_steps, noise_extender_ksampler, None, control_data)[0] else: samples_out = primeresamplers.PKSampler(self, device, seed, model, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, - align_your_steps, noise_extender_ksampler, None)[0] + align_your_steps, noise_extender_ksampler, None, control_data)[0] case _: if model_concept == 'AuraFlow' and model_sampling is not None and model_sampling > 0: @@ -690,25 +668,24 @@ class PrimereKSampler: positive, negative, latent_image, denoise, variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, - align_your_steps, noise_extender_ksampler, None)[0] + align_your_steps, noise_extender_ksampler, None, control_data)[0] - if workflow_tuple is not None: - workflow_tuple['sampler_settings'] = {} - workflow_tuple['sampler_settings']['denoise'] = denoise - workflow_tuple['sampler_settings']['variation_extender_original'] = variation_extender_original - workflow_tuple['sampler_settings']['variation_batch_step_original'] = variation_batch_step_original - workflow_tuple['sampler_settings']['variation_level'] = variation_level - workflow_tuple['sampler_settings']['device'] = device - workflow_tuple['sampler_settings']['align_your_steps'] = align_your_steps - workflow_tuple['sampler_settings']['noise_constant'] = noise_constant - workflow_tuple['sampler_settings']['variation_seed'] = seed - workflow_tuple['sampler_settings']['batch_counter'] = batch_counter - workflow_tuple['sampler_settings']['model_sampling'] = model_sampling + if control_data is not None: + control_data['sampler_settings'] = {} + control_data['sampler_settings']['denoise'] = denoise + control_data['sampler_settings']['variation_extender_original'] = variation_extender_original + control_data['sampler_settings']['variation_batch_step_original'] = variation_batch_step_original + control_data['sampler_settings']['variation_level'] = variation_level + control_data['sampler_settings']['device'] = device + control_data['sampler_settings']['align_your_steps'] = align_your_steps + control_data['sampler_settings']['noise_constant'] = noise_constant + control_data['sampler_settings']['variation_seed'] = seed + control_data['sampler_settings']['batch_counter'] = batch_counter + control_data['sampler_settings']['model_sampling'] = model_sampling timestamp_diff = int(time.time() - timestamp_start) - original_model_concept_selector = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'model_concept', prompt) is_random_model = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'random_model', prompt) - selected_model = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', prompt) + selected_model = control_data.get('model_name') if control_data else utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', prompt) if is_random_model == True: fullSource = PrimereVisualCKPT.allModels slashIndex = selected_model.find('\\') @@ -718,17 +695,6 @@ class PrimereKSampler: random.seed(seed) selected_model = random.choice(models_by_path) - if original_model_concept_selector != 'Auto': - match original_model_concept_selector: - case 'Flux': - flux_selector = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'flux_selector', prompt) - if flux_selector == 'GGUF': - selected_model = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'flux_gguf', prompt) - else: - selected_model = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'flux_diffusion', prompt) - case 'StableCascade': - selected_model = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'cascade_stage_c', prompt) - if selected_model is not None: modelname_only = Path(selected_model).stem model_samplingtime = utility.get_value_from_cache('model_samplingtime', modelname_only) @@ -740,7 +706,7 @@ class PrimereKSampler: diffvalue = str(int(model_samplingtime_list[1]) + timestamp_diff) utility.add_value_to_cache('model_samplingtime', modelname_only, counter + '|' + diffvalue) - return (samples_out, workflow_tuple) + return (samples_out, control_data) class PrimerePreviewImage(): CATEGORY = TREE_OUTPUTS @@ -878,7 +844,7 @@ class PrimereAestheticCKPTScorer: "image": ("IMAGE", ), }, "optional": { - "workflow_data": ('TUPLE', {"forceInput": True}), + "control_data": ('TUPLE', {"forceInput": True}), }, "hidden": { "extra_pnginfo": "EXTRA_PNGINFO", @@ -886,59 +852,17 @@ class PrimereAestheticCKPTScorer: }, } - def aesthetic_scorer(self, image, get_aesthetic_score, add_to_checkpoint, add_to_saved_prompt, prompt, dual_mode = True, workflow_data = None, **kwargs): + def aesthetic_scorer(self, image, get_aesthetic_score, add_to_checkpoint, add_to_saved_prompt, prompt, dual_mode = True, control_data = None, **kwargs): final_prediction = '*** Aesthetic scorer off ***' models = [] + WORKFLOWDATA = kwargs['extra_pnginfo']['workflow']['nodes'] + AE_SCORE_MIN = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'aescore_percent_min', prompt) + AE_SCORE_MAX = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'aescore_percent_max', prompt) + def pipe(model): return pipeline(task="image-classification", model=model, device=model_management.get_torch_device()) if (get_aesthetic_score == True): - '''AESTHETIC_PATH = os.path.join(folder_paths.models_dir, 'aesthetic') - folder_paths.add_model_folder_path("aesthetic", AESTHETIC_PATH) - if os.path.exists(AESTHETIC_PATH) == False: - Path(AESTHETIC_PATH).mkdir(parents=True, exist_ok=True) - AESTH_FULL_LIST = folder_paths.get_filename_list("aesthetic") - aestheticFiles = folder_paths.filter_files_extensions(AESTH_FULL_LIST, ['.pth']) - - if 'chadscorer.pth' not in aestheticFiles: - FileUrl = 'https://huggingface.co/primerecomfydev/chadscorer/resolve/main/chadscorer.pth?download=true' - FullFilePath = os.path.join(AESTHETIC_PATH, 'chadscorer.pth') - ModelDownload = utility.downloader(FileUrl, FullFilePath) - if (ModelDownload == True): - AESTH_FULL_LIST = folder_paths.get_filename_list("aesthetic") - aestheticFiles = folder_paths.filter_files_extensions(AESTH_FULL_LIST, ['.pth']) - - if 'chadscorer.pth' in aestheticFiles: - folder_paths.folder_names_and_paths["aesthetic"] = ([os.path.join(folder_paths.models_dir, "aesthetic")], folder_paths.supported_pt_extensions) - m_path = folder_paths.folder_names_and_paths["aesthetic"][0] - aesthetic_model = os.path.join(m_path[0], 'chadscorer.pth') - fsize = os.path.getsize(aesthetic_model) - freemem = comfy.model_management.get_free_memory() - if (fsize * 1.2) < freemem: - model = utility.MLP(768) - s = torch.load(aesthetic_model) - model.load_state_dict(s) - model.to("cuda") - model.eval() - device = "cuda" - try: - model2, preprocess = clip.load("ViT-L/14", device=device) # RN50x64 - tensor_image = image[0] - img = (tensor_image * 255).to(torch.uint8).numpy() - pil_image = Image.fromarray(img, mode='RGB') - image2 = preprocess(pil_image).unsqueeze(0).to(device) - with torch.no_grad(): - image_features = model2.encode_image(image2) - pass - im_emb_arr = utility.normalized(image_features.cpu().detach().numpy()) - prediction = model(torch.from_numpy(im_emb_arr).to(device).type(torch.cuda.FloatTensor)) - final_prediction = int(float(prediction[0]) * 100) - del model - except Exception: - final_prediction = 0 - else: - final_prediction = 0''' - AE_MODEL_ROOT = os.path.join(folder_paths.models_dir, 'aesthetic') AEMODELS_ENCODERS_PATHS = utility.getValidAscorerPaths(AE_MODEL_ROOT) if len(AEMODELS_ENCODERS_PATHS) > 0: @@ -985,13 +909,15 @@ class PrimereAestheticCKPTScorer: else: final_prediction = '*** No aesthetic models downloaded ***' - if (type(final_prediction) != 'str'): + if type(final_prediction) != str: final_prediction = str(final_prediction) - if workflow_data is not None and final_prediction.isdigit(): - if add_to_checkpoint == True and (workflow_data['model_concept'] == workflow_data['model_version']): - if 'model' in workflow_data: - selected_model = workflow_data['model'] + if control_data is not None and final_prediction.isdigit(): + if add_to_checkpoint == True and (control_data['model_concept']): + if 'model_name' in control_data: + AE_SCORE_MIN = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'aescore_percent_min', prompt) + AE_SCORE_MAX = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'aescore_percent_max', prompt) + selected_model = control_data['model_name'] modelname_only = Path(selected_model).stem model_ascore = utility.get_value_from_cache('model_ascores', modelname_only) if model_ascore is None: @@ -1003,8 +929,7 @@ class PrimereAestheticCKPTScorer: utility.add_value_to_cache('model_ascores', modelname_only, counter + '|' + score) if add_to_saved_prompt == True and final_prediction.isdigit(): - if 'positive' in workflow_data: - WORKFLOWDATA = kwargs['extra_pnginfo']['workflow']['nodes'] + if 'positive' in control_data: selectedStyle = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualStyle', 'styles', prompt) if selectedStyle is None: selectedStyle = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereStyleLoader', 'styles', prompt) @@ -1042,7 +967,9 @@ class PrimereAestheticCKPTScorer: if (positive_prompt is not None): if len(positive_prompt) > 100: positive_prompt = positive_prompt[:100] - if positive_prompt in workflow_data['positive']: + if positive_prompt in control_data['positive']: + AE_SCORE_MIN = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualStyle', 'aescore_percent_min', prompt) + AE_SCORE_MAX = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualStyle', 'aescore_percent_max', prompt) style_ascore = utility.get_value_from_cache('styles_ascores', selectedStyle) if style_ascore is None: utility.add_value_to_cache('styles_ascores', selectedStyle, '1|' + final_prediction) @@ -1052,7 +979,16 @@ class PrimereAestheticCKPTScorer: score = str(int(style_ascore_list[1]) + int(final_prediction)) utility.add_value_to_cache('styles_ascores', selectedStyle, counter + '|' + score) - return {"ui": {"text": [final_prediction]}, "result": (final_prediction,)} + if isinstance(final_prediction, str) and final_prediction.isdigit(): + if AE_SCORE_MIN is not None and AE_SCORE_MAX is not None: + final_prediction = max(0, min(100, int(((int(final_prediction) - AE_SCORE_MIN) / (AE_SCORE_MAX - AE_SCORE_MIN)) * 100))) + else: + final_prediction = int(final_prediction) + else: + final_prediction = '*** Aesthetic scorer error ***' + + result_int = final_prediction if isinstance(final_prediction, int) else 0 + return {"ui": {"text": [final_prediction]}, "result": (result_int,)} class DebugToFile(): CATEGORY = TREE_OUTPUTS diff --git a/__init__.py b/__init__.py index 49555dd..476a608 100644 --- a/__init__.py +++ b/__init__.py @@ -38,7 +38,7 @@ for subdirs in valid_FElist: NODE_CLASS_MAPPINGS = { "PrimereSamplersSteps": Dashboard.PrimereSamplersSteps, - "PrimereAutoSamplerSettings": Dashboard.PrimereAutoSamplerSettings, + "PrimereModelControl": Dashboard.PrimereModelControl, "PrimereVAE": Dashboard.PrimereVAE, "PrimereCKPT": Dashboard.PrimereCKPT, "PrimereVAELoader": Dashboard.PrimereVAELoader, @@ -117,7 +117,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "PrimereSamplersSteps": "Primere Samplers & Steps & Cfg", - "PrimereAutoSamplerSettings": "Primere Auto Sampler Settings", + "PrimereModelControl": "Primere Primere ModelControl", "PrimereVAE": "Primere VAE Selector", "PrimereCKPT": "Primere CKPT Selector", "PrimereVAELoader": "Primere VAE Loader", diff --git a/components/clipping.py b/components/clipping.py index 1bfd938..2b37401 100644 --- a/components/clipping.py +++ b/components/clipping.py @@ -558,32 +558,55 @@ def build_prompt_context( SDXL_CONCEPTS = {'SDXL', 'Illustrious', 'Pony', 'Playground'} -CLIP_ATTN_PRESETS = { - "Off": (1.00, 1.00, 1.00, 1.00), - "Natural": (1.00, 1.02, 0.98, 1.00), - "Realism": (1.00, 1.05, 0.95, 1.00), - "Photography": (1.02, 1.05, 0.93, 0.98), - "Cinematic": (1.05, 1.05, 1.00, 0.95), - "Portrait": (1.03, 1.08, 0.92, 0.97), - "Art": (0.95, 0.95, 1.10, 1.05), - "Illustration": (0.90, 1.00, 1.15, 1.00), - "Anime": (0.88, 0.95, 1.18, 1.05), - "Prompt adherence": (1.10, 1.10, 1.00, 1.00), - "Abstract": (0.85, 0.88, 1.18, 1.12), - "Creative": (0.85, 0.90, 1.20, 1.10), - "Surreal": (0.80, 0.85, 1.25, 1.15), +# Unified preset: (q, k, v, out, cross_q, cross_k, cross_v, cross_out) +# First 4: applied to CLIP attention + UNet self-attention (attn1) +# Last 4: applied to UNet cross-attention (attn2) — ignored for clip-only models +ATTN_PRESETS = { + "Off": (1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00), + "Natural": (1.00, 1.02, 0.98, 1.00, 1.00, 1.02, 0.98, 1.00), + "Realism": (1.00, 1.05, 0.95, 1.00, 1.05, 1.05, 0.95, 1.00), + "Photography": (1.02, 1.05, 0.93, 0.98, 1.05, 1.05, 0.93, 0.98), + "Cinematic": (1.05, 1.05, 1.00, 0.95, 1.05, 1.05, 1.00, 0.95), + "Portrait": (1.03, 1.08, 0.92, 0.97, 1.05, 1.10, 0.90, 1.00), + "Art": (0.95, 0.95, 1.10, 1.05, 0.90, 0.95, 1.10, 1.00), + "Illustration": (0.90, 1.00, 1.15, 1.00, 0.90, 1.00, 1.15, 1.00), + "Anime": (0.88, 0.95, 1.18, 1.05, 0.88, 0.95, 1.18, 1.00), + "Prompt adherence": (1.10, 1.10, 1.00, 1.00, 1.10, 1.10, 1.00, 1.00), + "Abstract": (0.85, 0.88, 1.18, 1.12, 0.85, 0.88, 1.15, 1.05), + "Creative": (0.85, 0.90, 1.20, 1.10, 0.85, 0.90, 1.15, 1.05), } -def apply_clip_overrides(loader_self, clip, workflow_tuple): - if not workflow_tuple: +ATTN_PRESET_KEYWORDS = { + 'Anime': ['anime', 'waifu', 'nai', 'hentai', 'manga'], + 'Photography': ['photo', 'realistic', 'realvis', 'realism'], + 'Illustration': ['illustration', 'illus', 'cartoon', 'draw'], + 'Cinematic': ['cinematic', 'film', 'movie', 'cinema'], + 'Portrait': ['portrait'], + 'Art': ['paint', 'artistic', 'watercolor'], + 'Natural': ['natural'], + 'Abstract': ['abstract', 'surreal'], +} + + +def detect_attn_preset(model_name, default='Off'): + if not model_name: + return default + name = model_name.lower().replace('\\', '/').split('/')[-1].split('.')[0] + for preset, keywords in ATTN_PRESET_KEYWORDS.items(): + if any(kw in name for kw in keywords): + return preset + return default + +def apply_clip_overrides(loader_self, clip, control_data): + if not control_data: return clip - encoder_1 = workflow_tuple.get('encoder_1', None) - last_layer = int(workflow_tuple.get('last_layer', 0)) + encoder_1 = control_data.get('encoder_1', None) + last_layer = int(control_data.get('last_layer', 0)) baked_clip = clip - if encoder_1 and encoder_1 != 'None' and not workflow_tuple.get('clip_selection', False): + if encoder_1 and encoder_1 != 'None' and not control_data.get('clip_selection', False): try: - model_concept = workflow_tuple.get('model_concept', 'SD1') + model_concept = control_data.get('model_concept', 'SD1') is_longclip = 'longclip' in encoder_1.lower() or encoder_1.lower().endswith('.pt') if is_longclip: if model_concept in SDXL_CONCEPTS: @@ -603,13 +626,13 @@ def apply_clip_overrides(loader_self, clip, workflow_tuple): return clip -def apply_clip_attention_multiply(clip, workflow_tuple): - if not workflow_tuple: +def apply_clip_attention_multiply(clip, control_data): + if not control_data: return clip - q = float(workflow_tuple.get('clip_attn_q', 1.0)) - k = float(workflow_tuple.get('clip_attn_k', 1.0)) - v = float(workflow_tuple.get('clip_attn_v', 1.0)) - out = float(workflow_tuple.get('clip_attn_out', 1.0)) + q = float(control_data.get('clip_attn_q', 1.0)) + k = float(control_data.get('clip_attn_k', 1.0)) + v = float(control_data.get('clip_attn_v', 1.0)) + out = float(control_data.get('clip_attn_out', 1.0)) if q == 1.0 and k == 1.0 and v == 1.0 and out == 1.0: return clip try: @@ -618,14 +641,14 @@ def apply_clip_attention_multiply(clip, workflow_tuple): return clip -def encode_standard(clip, positive_text, negative_text, t5xxl_prompt, adv_encode, token_normalization, weight_interpretation, positive_l, negative_l, width, height, workflow_tuple, advanced_encode_fn): +def 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_fn): if adv_encode: tokens_p = clip.tokenize(positive_text) tokens_n = clip.tokenize(negative_text) if 'l' not in tokens_p or 'g' not in tokens_p or 'l' not in tokens_n or 'g' not in tokens_n: embeddings_final_pos, pooled_pos = advanced_encode_fn(clip, positive_text, token_normalization, weight_interpretation, w_max=1.0, apply_to_pooled=True) embeddings_final_neg, pooled_neg = advanced_encode_fn(clip, negative_text, token_normalization, weight_interpretation, w_max=1.0, apply_to_pooled=True) - return ([[embeddings_final_pos, {"pooled_output": pooled_pos}]], [[embeddings_final_neg, {"pooled_output": pooled_neg}]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return ([[embeddings_final_pos, {"pooled_output": pooled_pos}]], [[embeddings_final_neg, {"pooled_output": pooled_neg}]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) else: if 'l' in clip.tokenize(positive_l): tokens_p["l"] = clip.tokenize(positive_l)["l"] @@ -645,7 +668,7 @@ def encode_standard(clip, positive_text, negative_text, t5xxl_prompt, adv_encode tokens_n["g"] += empty["g"] cond_p, pooled_p = clip.encode_from_tokens(tokens_p, return_pooled=True) cond_n, pooled_n = clip.encode_from_tokens(tokens_n, return_pooled=True) - return ([[cond_p, {"pooled_output": pooled_p, "width": width, "height": height, "crop_w": 0, "crop_h": 0, "target_width": width, "target_height": height}]], [[cond_n, {"pooled_output": pooled_n, "width": width, "height": height, "crop_w": 0, "crop_h": 0, "target_width": width, "target_height": height}]], positive_text, negative_text, "", positive_l, negative_l, workflow_tuple) + return ([[cond_p, {"pooled_output": pooled_p, "width": width, "height": height, "crop_w": 0, "crop_h": 0, "target_width": width, "target_height": height}]], [[cond_n, {"pooled_output": pooled_n, "width": width, "height": height, "crop_w": 0, "crop_h": 0, "target_width": width, "target_height": height}]], positive_text, negative_text, "", positive_l, negative_l, control_data) else: tokens_pos = clip.tokenize(positive_text) tokens_neg = clip.tokenize(negative_text) @@ -657,16 +680,16 @@ def encode_standard(clip, positive_text, negative_text, t5xxl_prompt, adv_encode out_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True, return_dict=True) cond_pos = out_pos.pop("cond") cond_neg = out_neg.pop("cond") - return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) -def encode_sd3(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple): +def encode_sd3(clip, positive_text, negative_text, t5xxl_prompt, control_data): if t5xxl_prompt: pos_out = nodes_sd3.CLIPTextEncodeSD3.execute(clip, positive_text, positive_text, t5xxl_prompt, 'none') tokens_neg = clip.tokenize(negative_text) out_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True, return_dict=True) cond_neg = out_neg.pop("cond") - return (pos_out[0], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return (pos_out[0], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) else: tokens_pos = clip.tokenize(positive_text) tokens_neg = clip.tokenize(negative_text) @@ -674,20 +697,20 @@ def encode_sd3(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple) out_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True, return_dict=True) cond_pos = out_pos.pop("cond") cond_neg = out_neg.pop("cond") - return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, "", "", "", workflow_tuple) + return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, "", "", "", control_data) -def encode_stable_cascade(clip, positive_text, negative_text, workflow_tuple): +def encode_stable_cascade(clip, positive_text, negative_text, control_data): positive_text = utility.DiT_cleaner(positive_text) negative_text = utility.DiT_cleaner(negative_text) tokens_pos = clip.tokenize(positive_text) tokens_neg = clip.tokenize(negative_text) cond_pos, pooled_pos = clip.encode_from_tokens(tokens_pos, return_pooled=True) cond_neg, pooled_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True) - return ([[cond_pos, {"pooled_output": pooled_pos}]], [[cond_neg, {"pooled_output": pooled_neg}]], positive_text, negative_text, "", "", "", workflow_tuple) + return ([[cond_pos, {"pooled_output": pooled_pos}]], [[cond_neg, {"pooled_output": pooled_neg}]], positive_text, negative_text, "", "", "", control_data) -def encode_pixart_sigma(clip, positive_text, negative_text, workflow_tuple): +def encode_pixart_sigma(clip, positive_text, negative_text, control_data): positive_text = utility.DiT_cleaner(positive_text) negative_text = utility.DiT_cleaner(negative_text) @@ -710,32 +733,32 @@ def encode_pixart_sigma(clip, positive_text, negative_text, workflow_tuple): cond_pos_main = out_pos_main.pop("cond") cond_neg_main = out_neg_main.pop("cond") - return ({'refiner': [[cond_pos_ref, out_pos_ref]], 'main': [[cond_pos_main, out_pos_main]]}, {'refiner': [[cond_neg_ref, out_neg_ref]], 'main': [[cond_neg_main, out_neg_main]]}, positive_text, negative_text, "", "", "", workflow_tuple) + return ({'refiner': [[cond_pos_ref, out_pos_ref]], 'main': [[cond_pos_main, out_pos_main]]}, {'refiner': [[cond_neg_ref, out_neg_ref]], 'main': [[cond_neg_main, out_neg_main]]}, positive_text, negative_text, "", "", "", control_data) -def encode_chroma(clip, positive_text, negative_text, workflow_tuple): +def encode_chroma(clip, positive_text, negative_text, control_data): tokens_pos = clip.tokenize(positive_text) tokens_neg = clip.tokenize(negative_text) out_pos = clip.encode_from_tokens(tokens_pos, return_pooled=True, return_dict=True) out_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True, return_dict=True) cond_pos = out_pos.pop("cond") cond_neg = out_neg.pop("cond") - return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, "", "", "", workflow_tuple) + return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, "", "", "", control_data) -def encode_flux(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple): - FLUX_SAMPLER = workflow_tuple.get('sampler', 'ksampler') - FLUX_GUIDANCE = workflow_tuple.get('guidance', 2) +def encode_flux(clip, positive_text, negative_text, t5xxl_prompt, control_data): + FLUX_SAMPLER = control_data.get('sampler', 'ksampler') + FLUX_GUIDANCE = control_data.get('guidance', 2) if FLUX_SAMPLER == 'custom_advanced' and len(t5xxl_prompt) > 5: CONDITIONING_POS = nodes_flux.CLIPTextEncodeFlux.execute(clip, positive_text, t5xxl_prompt, FLUX_GUIDANCE)[0] - return (CONDITIONING_POS, CONDITIONING_POS, positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return (CONDITIONING_POS, CONDITIONING_POS, positive_text, negative_text, t5xxl_prompt, "", "", control_data) tokens_pos = clip.tokenize(positive_text) tokens_neg = clip.tokenize(negative_text) out_pos = clip.encode_from_tokens(tokens_pos, return_pooled=True, return_dict=True) out_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True, return_dict=True) cond_pos = out_pos.pop("cond") cond_neg = out_neg.pop("cond") - return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) _SANA_MAX_TOKENS = 300 @@ -757,8 +780,8 @@ def _sana_encode_text(tokenizer, text_encoder, text, device): return embs * masks.unsqueeze(-1) -def encode_sana(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple): - scheduler_name = workflow_tuple.get('scheduler_name', 'flow_dpm-solver') if workflow_tuple else 'flow_dpm-solver' +def encode_sana(clip, positive_text, negative_text, t5xxl_prompt, control_data): + scheduler_name = control_data.get('scheduler_name', 'flow_dpm-solver') if control_data else 'flow_dpm-solver' device = model_management.get_torch_device() if scheduler_name == 'flow_dpm-solver' and hasattr(clip, 'text_encoder'): @@ -776,7 +799,7 @@ def encode_sana(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple null_y = null_embs.repeat(len(prompts), 1, 1)[:, None] clip.text_encoder.to(model_management.text_encoder_offload_device()) comfy.model_management.soft_empty_cache(True) - return ([[caption_embs, {"emb_masks": emb_masks}]], [[null_y, {}]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return ([[caption_embs, {"emb_masks": emb_masks}]], [[null_y, {}]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) else: tokenizer = clip["tokenizer"] text_encoder = clip["text_encoder"] @@ -784,10 +807,10 @@ def encode_sana(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple with torch.no_grad(): sana_embs_pos = _sana_encode_text(tokenizer, text_encoder, positive_text, enc_device) sana_embs_neg = _sana_encode_text(tokenizer, text_encoder, negative_text, enc_device) - return ([[sana_embs_pos, {}]], [[sana_embs_neg, {}]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return ([[sana_embs_pos, {}]], [[sana_embs_neg, {}]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) -def encode_qwen_edit(loader_self, clip, positive_text, negative_text, t5xxl_prompt, edit_vae, edit_image_list, workflow_tuple): +def encode_qwen_edit(loader_self, clip, positive_text, negative_text, t5xxl_prompt, edit_vae, edit_image_list, control_data): if type(edit_image_list).__name__ == "Tensor": edit_image_list = [edit_image_list] positive_text = utility.DiT_cleaner(positive_text) @@ -795,10 +818,10 @@ def encode_qwen_edit(loader_self, clip, positive_text, negative_text, t5xxl_prom conditioning = utility.edit_encoder(clip, positive_text, edit_vae, edit_image_list) tokens_neg = clip.tokenize(negative_text, images=[]) conditioning_neg = clip.encode_from_tokens_scheduled(tokens_neg) - return (conditioning, conditioning_neg, positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return (conditioning, conditioning_neg, positive_text, negative_text, t5xxl_prompt, "", "", control_data) -def encode_kolors(clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple): +def encode_kolors(clip, positive_text, negative_text, t5xxl_prompt, control_data): positive_text = utility.DiT_cleaner(positive_text) negative_text = utility.DiT_cleaner(negative_text) device = model_management.text_encoder_device() @@ -847,17 +870,17 @@ def encode_kolors(clip, positive_text, negative_text, t5xxl_prompt, workflow_tup 'pooled_prompt_embeds': text_proj.half(), 'negative_pooled_prompt_embeds': negative_text_proj.half(), } - return (kolors_embeds, None, positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return (kolors_embeds, None, positive_text, negative_text, t5xxl_prompt, "", "", control_data) -def encode_hunyuan(loader_self, clip, positive_text, negative_text, t5xxl_prompt, workflow_tuple): +def encode_hunyuan(loader_self, clip, positive_text, negative_text, t5xxl_prompt, control_data): if clip['t5'] is not None: positive_text = utility.DiT_cleaner(positive_text) negative_text = utility.DiT_cleaner(negative_text) t5xxl_prompt = utility.DiT_cleaner(t5xxl_prompt) pos_out = HunyuanClipping(loader_self, positive_text, t5xxl_prompt, clip['clip'], clip['t5']) neg_out = HunyuanClipping(loader_self, negative_text, "", clip['clip'], clip['t5']) - return (pos_out[0], neg_out[0], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) + return (pos_out[0], neg_out[0], positive_text, negative_text, t5xxl_prompt, "", "", control_data) else: clip_model = clip['clip'] positive_text = utility.DiT_cleaner(positive_text, 512) @@ -866,4 +889,4 @@ def encode_hunyuan(loader_self, clip, positive_text, negative_text, t5xxl_prompt out_neg = clip_model.encode_from_tokens(clip_model.tokenize(negative_text), return_pooled=True, return_dict=True) cond_pos = out_pos.pop("cond") cond_neg = out_neg.pop("cond") - return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", workflow_tuple) \ No newline at end of file + return ([[cond_pos, out_pos]], [[cond_neg, out_neg]], positive_text, negative_text, t5xxl_prompt, "", "", control_data) \ No newline at end of file diff --git a/components/models.py b/components/models.py index 21e0943..84e88fe 100644 --- a/components/models.py +++ b/components/models.py @@ -8,6 +8,7 @@ import nodes import comfy_extras.nodes_sd3 as nodes_sd3 import comfy_extras.nodes_model_advanced as nodes_model_advanced import comfy_extras.nodes_cfg as nodes_cfg +from comfy_extras.nodes_attention_multiply import attention_multiply from comfy import model_management from pathlib import Path from .tree import PRIMERE_ROOT @@ -56,6 +57,56 @@ def resolve_symlink(ckpt_name): return File_link, linkedFileName, model_ext +DISCRETE_CONCEPTS = {'SD1', 'SD2', 'SDXL', 'Illustrious', 'Turbo', 'Pony', 'Hyper', 'Lightning'} +UNET_CONCEPTS = {'SD1', 'SD2', 'SDXL', 'Illustrious', 'Turbo', 'Pony', 'Hyper', 'Lightning', 'Playground', 'LCM'} + + +def apply_generic_patches(loader_self, model, concept_data): + model_concept = concept_data.get('model_concept', '') + + discrete_sampling = concept_data.get('discrete_sampling', 'default') + if discrete_sampling != 'default' and model_concept in DISCRETE_CONCEPTS: + try: + discrete_zsnr = bool(concept_data.get('discrete_zsnr', False)) + model = nodes_model_advanced.ModelSamplingDiscrete.patch(loader_self, model, discrete_sampling, discrete_zsnr)[0] + except Exception as e: + print(f"Primere: ModelSamplingDiscrete failed: {e}") + + if model_concept in UNET_CONCEPTS: + self_q = concept_data.get('clip_attn_q', 1.0) + self_k = concept_data.get('clip_attn_k', 1.0) + self_v = concept_data.get('clip_attn_v', 1.0) + self_out = concept_data.get('clip_attn_out', 1.0) + if (self_q, self_k, self_v, self_out) != (1.0, 1.0, 1.0, 1.0): + try: + model = attention_multiply("attn1", model, self_q, self_k, self_v, self_out) + except Exception as e: + print(f"Primere: UNet self-attention multiply failed: {e}") + + if model_concept in UNET_CONCEPTS: + cross_q = concept_data.get('attn_cross_q', 1.0) + cross_k = concept_data.get('attn_cross_k', 1.0) + cross_v = concept_data.get('attn_cross_v', 1.0) + cross_out = concept_data.get('attn_cross_out', 1.0) + if (cross_q, cross_k, cross_v, cross_out) != (1.0, 1.0, 1.0, 1.0): + try: + model = attention_multiply("attn2", model, cross_q, cross_k, cross_v, cross_out) + except Exception as e: + print(f"Primere: UNet cross-attention multiply failed: {e}") + + precision = concept_data.get('precision', None) + if precision and precision not in ('quant8', 'quant4'): + dtype_map = {'fp32': 'fp32', 'fp16': 'fp16'} + dtype = dtype_map.get(precision) + if dtype: + try: + model = nodes_model_advanced.ModelComputeDtype.patch(loader_self, model, dtype)[0] + except Exception as e: + print(f"Primere: ModelComputeDtype failed: {e}") + + return model + + def apply_lora(loader_self, model, lora_path, strength): if not os.path.exists(lora_path) or strength == 0: return model @@ -102,6 +153,7 @@ def load_sd_model(loader_self, ckpt_name, use_yaml, model_config_full_path, conc OUTPUT_VAE = utility.vae_loader_class.load_vae(vae_name)[0] else: OUTPUT_VAE = LOADED_CHECKPOINT[2] + OUTPUT_MODEL = apply_generic_patches(loader_self, OUTPUT_MODEL, concept_data) return OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE @@ -131,6 +183,7 @@ def load_sd3_model(loader_self, ckpt_name, concept_data): lora_path = folder_paths.get_full_path('loras', lora_name) if lora_path: OUTPUT_MODEL = apply_lora(loader_self, OUTPUT_MODEL, lora_path, lora_strength) + OUTPUT_MODEL = apply_generic_patches(loader_self, OUTPUT_MODEL, concept_data) return OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE @@ -212,6 +265,13 @@ def load_flux_model(loader_self, ckpt_name, concept_data): rescale_cfg = concept_data.get('rescale_cfg', 1.0) if rescale_cfg != 1.0: OUTPUT_MODEL = nodes_model_advanced.RescaleCFG.patch(loader_self, OUTPUT_MODEL, rescale_cfg)[0] + flux_max_shift = concept_data.get('flux_max_shift', 1.15) + flux_base_shift = concept_data.get('flux_base_shift', 0.5) + try: + OUTPUT_MODEL = nodes_model_advanced.ModelSamplingFlux.patch(loader_self, OUTPUT_MODEL, flux_max_shift, flux_base_shift, 1024, 1024)[0] + except Exception as e: + print(f"Primere: ModelSamplingFlux failed: {e}") + OUTPUT_MODEL = apply_generic_patches(loader_self, OUTPUT_MODEL, concept_data) return OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE @@ -268,7 +328,11 @@ def load_playground_model(loader_self, ckpt_name, use_yaml, model_config_full_pa OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE = load_sd_model(loader_self, ckpt_name, use_yaml, model_config_full_path, concept_data) sigma_max = concept_data.get('sigma_max', 120) sigma_min = concept_data.get('sigma_min', 0.002) - OUTPUT_MODEL = nodes_model_advanced.ModelSamplingContinuousEDM.patch(loader_self, OUTPUT_MODEL, 'edm_playground_v2.5', sigma_max, sigma_min)[0] + edm_sampling = concept_data.get('edm_sampling', 'edm_playground_v2.5') + try: + OUTPUT_MODEL = nodes_model_advanced.ModelSamplingContinuousEDM.patch(loader_self, OUTPUT_MODEL, edm_sampling, sigma_max, sigma_min)[0] + except Exception as e: + print(f"Primere: ModelSamplingContinuousEDM failed: {e}") return OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE @@ -300,6 +364,7 @@ def load_lightning_hyper_model(loader_self, ckpt_name, concept_data): if lora_path: OUTPUT_MODEL = utility.BDanceConceptHelper(loader_self, model_concept, True, 'LORA', None, OUTPUT_MODEL, lora_path, None, None, lora_strength) + OUTPUT_MODEL = apply_generic_patches(loader_self, OUTPUT_MODEL, concept_data) return OUTPUT_MODEL, OUTPUT_CLIP, OUTPUT_VAE @@ -312,11 +377,9 @@ def load_lcm_model(loader_self, ckpt_name, concept_data): MODEL_VERSION = utility.getModelType(ckpt_name, 'checkpoints') if concept_data.get('lcm_lora') == True: - lora_name = concept_data.get('lcm_lora_name', None) - if lora_name: - lora_path = folder_paths.get_full_path('loras', lora_name) - if lora_path: - OUTPUT_MODEL = apply_lora(loader_self, OUTPUT_MODEL, lora_path, concept_data.get('lcm_lora_strength', 1.0)) + lora_file = 'lcm_lora_sdxl.safetensors' if 'SDXL' in MODEL_VERSION else 'lcm_lora_sd.safetensors' + lora_path = os.path.join(PRIMERE_ROOT, 'Nodes', 'Downloads', lora_file) + OUTPUT_MODEL = apply_lora(loader_self, OUTPUT_MODEL, lora_path, concept_data.get('lcm_lora_strength', 1.0)) class ModelSamplingAdvanced(utility.ModelSamplingDiscreteLCM, nodes_model_advanced.LCM): pass diff --git a/components/primeresamplers.py b/components/primeresamplers.py index 7608809..cf08132 100644 --- a/components/primeresamplers.py +++ b/components/primeresamplers.py @@ -25,32 +25,71 @@ def PKSampler(self, device, seed, model, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, - variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, align_your_steps, noise_extender, model_sampling = None): + variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, align_your_steps, noise_extender, model_sampling=None, control_data=None): if model_sampling is not None and model_sampling > 0: model = nodes_model_advanced.ModelSamplingSD3.patch(self, model, model_sampling, 1.0)[0] - if variation_level == True: - samples = latentnoise.noisy_samples(model, device, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, seed, noise_extender) - else: - if variation_extender_original > 0 or device != 'DEFAULT' or variation_batch_step_original > 0: + samples = None + + if scheduler_name == 'beta' and control_data is not None: + beta_alpha = float(control_data.get('beta_alpha', 0.6)) + beta_beta = float(control_data.get('beta_beta', 0.6)) + try: + sigmas = comfy.samplers.beta_scheduler(model.get_model_object("model_sampling"), steps, alpha=beta_alpha, beta=beta_beta) + sampler = comfy.samplers.sampler_object(sampler_name) + samples = (nodes_custom_sampler.SamplerCustom.execute(model, True, seed, cfg, positive, negative, sampler, sigmas, latent_image)[0],) + except Exception as e: + print(f"Primere: BetaSamplingScheduler failed: {e}") + + if samples is None: + if variation_level == True: samples = latentnoise.noisy_samples(model, device, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, seed, noise_extender) else: - if align_your_steps == True: - modelname_only = model - model_version = utility.get_value_from_cache('model_version', modelname_only) - match model_version: - case 'SDXL': - model_type = 'SDXL' - case _: - model_type = 'SD1' - - sigmas = nodes_align_your_steps.AlignYourStepsScheduler.get_sigmas(self, model_type, steps, denoise) - sampler = comfy.samplers.sampler_object(sampler_name) - AYS_samples = nodes_custom_sampler.SamplerCustom.execute(model, True, seed, cfg, positive, negative, sampler, sigmas[0], latent_image) - samples = (AYS_samples[0],) + if variation_extender_original > 0 or device != 'DEFAULT' or variation_batch_step_original > 0: + samples = latentnoise.noisy_samples(model, device, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, seed, noise_extender) else: - samples = nodes.KSampler.sample(self, model, seed, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise=denoise) + if align_your_steps == True: + modelname_only = model + model_version = utility.get_value_from_cache('model_version', modelname_only) + match model_version: + case 'SDXL': + model_type = 'SDXL' + case _: + model_type = 'SD1' + sigmas = nodes_align_your_steps.AlignYourStepsScheduler.get_sigmas(self, model_type, steps, denoise) + sampler = comfy.samplers.sampler_object(sampler_name) + AYS_samples = nodes_custom_sampler.SamplerCustom.execute(model, True, seed, cfg, positive, negative, sampler, sigmas[0], latent_image) + samples = (AYS_samples[0],) + else: + samples = nodes.KSampler.sample(self, model, seed, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise=denoise) + + if control_data and control_data.get('refiner') == True: + refiner_model_name = control_data.get('refiner_model', None) + if refiner_model_name and refiner_model_name != 'None': + try: + REFINER_CHECKPOINT = nodes.CheckpointLoaderSimple.load_checkpoint(self, refiner_model_name) + RAW_IMAGE = nodes.VAEDecode.decode(self, REFINER_CHECKPOINT[2], samples[0])[0] + RAW_IMAGE_ENCODED = nodes.VAEEncode.encode(self, REFINER_CHECKPOINT[2], RAW_IMAGE)[0] + REFINER_SAMPLER = control_data.get('refiner_sampler', 'dpmpp_2m') + REFINER_SCHEDULER = control_data.get('refiner_scheduler', 'normal') + REFINER_CFG = float(control_data.get('refiner_cfg', 2.0)) + REFINER_STEPS = int(control_data.get('refiner_steps', 22)) + REFINER_DENOISE = float(control_data.get('refiner_denoise', 0.9)) + REFINER_START = int(control_data.get('refiner_start', 12)) + sigmas_refiner = nodes_custom_sampler.BasicScheduler.execute(REFINER_CHECKPOINT[0], REFINER_SCHEDULER, REFINER_STEPS, REFINER_DENOISE)[0] + splitted_sigma = nodes_custom_sampler.SplitSigmas.execute(sigmas_refiner, REFINER_START)[1] + sampler_refiner = comfy.samplers.sampler_object(REFINER_SAMPLER) + if control_data.get('refiner_ignore_prompt', True): + empty_cond = nodes.CLIPTextEncode.encode(self, REFINER_CHECKPOINT[1], "")[0] + refiner_pos = empty_cond + refiner_neg = empty_cond + else: + refiner_pos = positive + refiner_neg = negative + samples = (nodes_custom_sampler.SamplerCustom.execute(REFINER_CHECKPOINT[0], True, seed, REFINER_CFG, refiner_pos, refiner_neg, sampler_refiner, splitted_sigma, RAW_IMAGE_ENCODED)[0],) + except Exception as e: + print(f"Primere: Refiner sampling failed: {e}") return samples @@ -97,16 +136,15 @@ def PCascadeSampler(self, model, seed, steps, cfg, sampler_name, scheduler_name, return samples -def PSamplerHyper(self, extra_pnginfo, model, seed, steps, cfg, positive, negative, sampler_name, scheduler_name, latent_image, denoise, prompt): - WORKFLOWDATA = extra_pnginfo['workflow']['nodes'] - OriginalBaseModel = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', prompt) +def PSamplerHyper(self, extra_pnginfo, model, seed, steps, cfg, positive, negative, sampler_name, scheduler_name, latent_image, denoise, prompt, control_data): + # WORKFLOWDATA = extra_pnginfo['workflow']['nodes'] + OriginalBaseModel = control_data['model_name'] # OriginalBaseModel = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereVisualCKPT', 'base_model', prompt) fullpathFile = folder_paths.get_full_path('checkpoints', OriginalBaseModel) is_link = os.path.islink(str(fullpathFile)) + HyperSDSelector = None if is_link == True: HyperSDSelector = 'UNET' - else: - HyperSDSelector = utility.getDataFromWorkflowByName(WORKFLOWDATA, 'PrimereModelConceptSelector', 'hypersd_selector', prompt) - if (HyperSDSelector == 'UNET'): + if HyperSDSelector == 'UNET': sigmas = utility.get_hypersd_sigmas(model) sampler = comfy.samplers.sampler_object(sampler_name) hyper_samples = nodes_custom_sampler.SamplerCustom.execute(model, True, seed, cfg, positive, negative, sampler, sigmas[0], latent_image) @@ -155,14 +193,14 @@ def PSamplerPixart(self, device, seed, model, steps, cfg, sampler_name, scheduler_name, positive, negative, latent_image, denoise, - variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, align_your_steps, noise_extender, workflow_tuple): + variation_extender, variation_batch_step_original, batch_counter, variation_extender_original, variation_batch_step, variation_level, variation_limit, align_your_steps, noise_extender, control_data): - if workflow_tuple: - sampler_name = workflow_tuple.get('sampler_name', sampler_name) - scheduler_name = workflow_tuple.get('scheduler_name', scheduler_name) - steps = workflow_tuple.get('steps', steps) - cfg = workflow_tuple.get('cfg', cfg) - PIXART_DENOISE = float(workflow_tuple.get('refiner_sampling_denoise', denoise)) if workflow_tuple and workflow_tuple.get('refiner') == True else denoise + if control_data: + sampler_name = control_data.get('sampler_name', sampler_name) + scheduler_name = control_data.get('scheduler_name', scheduler_name) + steps = control_data.get('steps', steps) + cfg = control_data.get('cfg', cfg) + PIXART_DENOISE = float(control_data.get('refiner_sampling_denoise', denoise)) if control_data and control_data.get('refiner') == True else denoise sigmas_main = nodes_custom_sampler.BasicScheduler.execute(model['main'], scheduler_name, steps, denoise=PIXART_DENOISE)[0] sampler = comfy.samplers.sampler_object(sampler_name) @@ -182,22 +220,22 @@ def PSamplerPixart(self, device, seed, model, samples_main = nodes_custom_sampler.SamplerCustom.execute(model['main'], True, seed, cfg, positive['main'], negative['main'], sampler, sigmas_main, latent_image)[0] if 'refiner' in model and model['refiner'] is not None: - PIXART_VAE = utility.vae_loader_class.load_vae(workflow_tuple.get('vae'))[0] + PIXART_VAE = utility.vae_loader_class.load_vae(control_data.get('vae'))[0] RAW_IMAGE = nodes.VAEDecode.decode(self, PIXART_VAE, samples_main)[0] - PIXART_REFINER_CHECKPOINT = nodes.CheckpointLoaderSimple.load_checkpoint(self, workflow_tuple.get('refiner_model')) + PIXART_REFINER_CHECKPOINT = nodes.CheckpointLoaderSimple.load_checkpoint(self, control_data.get('refiner_model')) RAW_IMAGE_ENCODED = nodes.VAEEncode.encode(self, PIXART_REFINER_CHECKPOINT[2], RAW_IMAGE)[0] - REFINER_SAMPLER = workflow_tuple.get('refiner_sampler', 'dpmpp_2m') - REFINER_SCHEDULER = workflow_tuple.get('refiner_scheduler', 'normal') - REFINER_CFG = float(workflow_tuple.get('refiner_cfg', 2.0)) - REFINER_STEPS = int(workflow_tuple.get('refiner_steps', 22)) - PIXART_DENOISE_REFINER = float(workflow_tuple.get('refiner_denoise', 0.9)) - PIXART_REFINER_START = int(workflow_tuple.get('refiner_start', 12)) + REFINER_SAMPLER = control_data.get('refiner_sampler', 'dpmpp_2m') + REFINER_SCHEDULER = control_data.get('refiner_scheduler', 'normal') + REFINER_CFG = float(control_data.get('refiner_cfg', 2.0)) + REFINER_STEPS = int(control_data.get('refiner_steps', 22)) + PIXART_DENOISE_REFINER = float(control_data.get('refiner_denoise', 0.9)) + PIXART_REFINER_START = int(control_data.get('refiner_start', 12)) sigmas_refiner = nodes_custom_sampler.BasicScheduler.execute(model['refiner'], REFINER_SCHEDULER, REFINER_STEPS, PIXART_DENOISE_REFINER)[0] splitted_low_sigma = nodes_custom_sampler.SplitSigmas.execute(sigmas_refiner, PIXART_REFINER_START)[1] sampler_refiner = comfy.samplers.sampler_object(REFINER_SAMPLER) - REFINER_IGNORE_PROMPT = workflow_tuple.get('refiner_ignore_prompt', False) + REFINER_IGNORE_PROMPT = control_data.get('refiner_ignore_prompt', False) if REFINER_IGNORE_PROMPT: empty_cond = nodes.CLIPTextEncode.encode(self, PIXART_REFINER_CHECKPOINT[1], "")[0] refiner_pos = empty_cond diff --git a/front_end/model_concepts.js b/front_end/model_concepts.js index 611815f..7822f96 100644 --- a/front_end/model_concepts.js +++ b/front_end/model_concepts.js @@ -2,16 +2,18 @@ import { app } from "/scripts/app.js"; import { ComfyWidgets } from "/scripts/widgets.js"; import { applyPrimereButtonStyle, showToast } from "./frontend_helper.js"; -const TARGET_NODE_NAME = "PrimereAutoSamplerSettings"; +const TARGET_NODE_NAME = "PrimereModelControl"; const CONCEPT_JSON_URL = new URL("/extensions/ComfyUI_Primere_Nodes/model_concept.json", import.meta.url).href; +const JSON_EXCLUDE_KEYS = new Set(["model_name"]); + function modelNameToKey(modelPath) { const base = modelPath.split(/[\\/]/).pop(); return base.replace(/\.[^/.]+$/, ""); } function collectNodeData(node, includeLoraToggles = false) { - const SKIP_KEYS = new Set(["concepts", "models", "runtime_concept"]); + const SKIP_KEYS = new Set(["concepts", "models", "runtime_concept", ...JSON_EXCLUDE_KEYS]); const widgets = node.widgets || []; const loraBooleans = new Set( @@ -175,7 +177,7 @@ function initializeSamplerNode(node) { } app.registerExtension({ - name: "Primere.AutoSamplerSettings", + name: "Primere.ModelControl", setup() { app.api.addEventListener("primere.concept_setting", (event) => { diff --git a/front_end/primere_visuals.js b/front_end/primere_visuals.js index 76b8389..d0f596a 100644 --- a/front_end/primere_visuals.js +++ b/front_end/primere_visuals.js @@ -20,8 +20,8 @@ const state = { sortType: 'name', operator: 'ASC', PreviewPath: true, - aeScoreMin: 400, - aeScoreMax: 900, + aeScoreMin: 550, + aeScoreMax: 800, nodeHelper: {}, cache_key: '', source_subdirname: '', diff --git a/terminal_helpers/embedding_version_cache.py b/terminal_helpers/embedding_version_cache.py index 02a5bee..896de59 100644 --- a/terminal_helpers/embedding_version_cache.py +++ b/terminal_helpers/embedding_version_cache.py @@ -12,25 +12,50 @@ primere_root = Path(__file__).parent.parent.absolute() components_path = os.path.join(primere_root, 'components') sys.path.append(components_path) -import utility as utility +import utility EmbeddingList = folder_paths.get_filename_list("embeddings") +def match_supported(name): + name_lower = name.lower() + for supported in utility.SUPPORTED_MODELS: + if supported.lower() == name_lower: + return supported + return None + +def get_type_from_dir(oneEmbedding): + parts = Path(oneEmbedding).parts + if len(parts) > 1: + return match_supported(parts[0]) + return None + print('------------------- START -------------------------') print(str(len(EmbeddingList)) + ' embeddings in system') - print('--------------- CACHED EMBEDDING INFO ---------------------') + if len(EmbeddingList) > 0: model_counter = 1 - for onelora in EmbeddingList: - name_only = Path(onelora).stem + for oneEmbedding in EmbeddingList: + name_only = Path(oneEmbedding).stem + prefix = f"Embedding [{model_counter}] / {len(EmbeddingList)}" + model_version = utility.get_value_from_cache('embedding_version', name_only) - if model_version is None or model_version not in utility.SUPPORTED_MODELS: - model_version = 'SD1' - utility.add_value_to_cache('embedding_version', name_only, model_version) - print('Embedding cached: ' + name_only + ' -> ' + str(model_version)) - else: - print('Embedding already cached: ' + name_only + ' -> ' + str(model_version)) - model_counter = model_counter + 1 + if model_version is not None and model_version in utility.SUPPORTED_MODELS: + print(f"{prefix} already cached: {name_only} -> {model_version}") + model_counter += 1 + continue + + dir_version = get_type_from_dir(oneEmbedding) + if dir_version: + utility.add_value_to_cache('embedding_version', name_only, dir_version) + print(f"{prefix} cached from directory: {name_only} -> {dir_version}") + model_counter += 1 + continue + + cache_msg = f"UNKNOWN | path: models/embeddings/{oneEmbedding}" + utility.add_value_to_cache('embedding_version', name_only, cache_msg) + print(f"{prefix} {cache_msg}") + + model_counter += 1 else: - print('No embedding in your system....') \ No newline at end of file + print('No embeddings in your system....') diff --git a/terminal_helpers/lora_version_cache.py b/terminal_helpers/lora_version_cache.py index b1d5942..ecc6ca1 100644 --- a/terminal_helpers/lora_version_cache.py +++ b/terminal_helpers/lora_version_cache.py @@ -12,30 +12,57 @@ primere_root = Path(__file__).parent.parent.absolute() components_path = os.path.join(primere_root, 'components') sys.path.append(components_path) -import utility as utility +import utility + +def match_supported(name): + name_lower = name.lower() + for supported in utility.SUPPORTED_MODELS: + if supported.lower() == name_lower: + return supported + return None + +def get_type_from_dir(oneModel): + parts = Path(oneModel).parts + if len(parts) > 1: + return match_supported(parts[0]) + return None ModelsList = folder_paths.get_filename_list("loras") print('------------------- START -------------------------') print(str(len(ModelsList)) + ' loras in system') - print('--------------- CACHED LORAS INFO ---------------------') + if len(ModelsList) > 0: model_counter = 1 for oneModel in ModelsList: - model_path = folder_paths.get_full_path("loras", oneModel) - modelaname_only = Path(oneModel).stem - model_version = utility.get_value_from_cache('lora_version', modelaname_only) - if model_version is None or model_version not in utility.SUPPORTED_MODELS: - model_version = utility.getModelType(oneModel, 'loras') - if model_version and model_version is not None and model_version != 'NoneType': - utility.add_value_to_cache('lora_version', modelaname_only, str(model_version)) - print('Lora [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + str(model_version)) - else: - utility.add_value_to_cache('lora_version', modelaname_only, 'unknown') - print('Lora [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + 'unknown') - else: - print('Lora [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' already cached: ' + modelaname_only + ' -> ' + str(model_version)) - model_counter = model_counter + 1 + modelname_only = Path(oneModel).stem + prefix = f"Lora [{model_counter}] / {len(ModelsList)}" + + model_version = utility.get_value_from_cache('lora_version', modelname_only) + if model_version is not None and model_version in utility.SUPPORTED_MODELS: + print(f"{prefix} already cached: {modelname_only} -> {model_version}") + model_counter += 1 + continue + + model_version = utility.getModelType(oneModel, 'loras') + if model_version and model_version not in (None, False, 'NoneType') and model_version in utility.SUPPORTED_MODELS: + utility.add_value_to_cache('lora_version', modelname_only, model_version) + print(f"{prefix} cached from metadata: {modelname_only} -> {model_version}") + model_counter += 1 + continue + + dir_version = get_type_from_dir(oneModel) + if dir_version: + utility.add_value_to_cache('lora_version', modelname_only, dir_version) + print(f"{prefix} cached from directory: {modelname_only} -> {dir_version}") + model_counter += 1 + continue + + cache_msg = f"UNKNOWN | path: models/loras/{oneModel}" + utility.add_value_to_cache('lora_version', modelname_only, cache_msg) + print(f"{prefix} {cache_msg}") + + model_counter += 1 else: - print('No loras in your system....') \ No newline at end of file + print('No loras in your system....') diff --git a/terminal_helpers/lyco_version_cache.py b/terminal_helpers/lyco_version_cache.py index 66c5729..ffca914 100644 --- a/terminal_helpers/lyco_version_cache.py +++ b/terminal_helpers/lyco_version_cache.py @@ -12,33 +12,60 @@ primere_root = Path(__file__).parent.parent.absolute() components_path = os.path.join(primere_root, 'components') sys.path.append(components_path) -import utility as utility +import utility 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") ModelsList = folder_paths.filter_files_extensions(LyCORIS, ['.ckpt', '.safetensors']) +def match_supported(name): + name_lower = name.lower() + for supported in utility.SUPPORTED_MODELS: + if supported.lower() == name_lower: + return supported + return None + +def get_type_from_dir(oneModel): + parts = Path(oneModel).parts + if len(parts) > 1: + return match_supported(parts[0]) + return None + print('------------------- START -------------------------') print(str(len(ModelsList)) + ' lycoris in system') - print('--------------- CACHED LYCORIS INFO ---------------------') + if len(ModelsList) > 0: model_counter = 1 for oneModel in ModelsList: - model_path = folder_paths.get_full_path("lycoris", oneModel) - modelaname_only = Path(oneModel).stem - model_version = utility.get_value_from_cache('lycoris_version', modelaname_only) - if model_version is None or model_version not in utility.SUPPORTED_MODELS: - model_version = utility.getModelType(oneModel, 'lycoris') - if model_version and model_version is not None and model_version != 'NoneType': - utility.add_value_to_cache('lycoris_version', modelaname_only, str(model_version)) - print('Lyco [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + str(model_version)) - else: - utility.add_value_to_cache('lycoris_version', modelaname_only, 'unknown') - print('Lyco [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + 'unknown') - else: - print('Lyco [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' already cached: ' + modelaname_only + ' -> ' + str(model_version)) - model_counter = model_counter + 1 + modelname_only = Path(oneModel).stem + prefix = f"Lyco [{model_counter}] / {len(ModelsList)}" + + model_version = utility.get_value_from_cache('lycoris_version', modelname_only) + if model_version is not None and model_version in utility.SUPPORTED_MODELS: + print(f"{prefix} already cached: {modelname_only} -> {model_version}") + model_counter += 1 + continue + + model_version = utility.getModelType(oneModel, 'lycoris') + if model_version and model_version not in (None, False, 'NoneType') and model_version in utility.SUPPORTED_MODELS: + utility.add_value_to_cache('lycoris_version', modelname_only, model_version) + print(f"{prefix} cached from metadata: {modelname_only} -> {model_version}") + model_counter += 1 + continue + + dir_version = get_type_from_dir(oneModel) + if dir_version: + utility.add_value_to_cache('lycoris_version', modelname_only, dir_version) + print(f"{prefix} cached from directory: {modelname_only} -> {dir_version}") + model_counter += 1 + continue + + cache_msg = f"UNKNOWN | path: models/lycoris/{oneModel}" + utility.add_value_to_cache('lycoris_version', modelname_only, cache_msg) + print(f"{prefix} {cache_msg}") + + model_counter += 1 else: - print('No lycoris in your system....') \ No newline at end of file + print('No lycoris in your system....') diff --git a/terminal_helpers/model_version_cache.py b/terminal_helpers/model_version_cache.py index 83cf4a6..c3414bb 100644 --- a/terminal_helpers/model_version_cache.py +++ b/terminal_helpers/model_version_cache.py @@ -12,54 +12,80 @@ primere_root = Path(__file__).parent.parent.absolute() components_path = os.path.join(primere_root, 'components') sys.path.append(components_path) -import utility as utility +import utility + +EXCLUDED_SUBDIRS = {'.locks', 'Bjornulf_civitAI', 'depthfm', 'models--xiaozaa--cat-tryoff-flux'} + +def match_supported(name): + name_lower = name.lower() + for supported in utility.SUPPORTED_MODELS: + if supported.lower() == name_lower: + return supported + return None + +def get_type_from_dirs(oneModel, resolved_path=None): + parts = Path(oneModel).parts + if len(parts) > 1: + matched = match_supported(parts[0]) + if matched: + return matched + if resolved_path is not None: + parent_dir = Path(resolved_path).parent.name + matched = match_supported(parent_dir) + if matched: + return matched + return None ModelsList = folder_paths.get_filename_list("checkpoints") print('------------------- START -------------------------') print(str(len(ModelsList)) + ' models in system') - print('--------------- CACHED MODELS INFO ---------------------') + if len(ModelsList) > 0: model_counter = 1 for oneModel in ModelsList: + parts = Path(oneModel).parts + first_part = parts[0] if len(parts) > 1 else '' + if first_part in EXCLUDED_SUBDIRS: + model_counter += 1 + continue + model_path = folder_paths.get_full_path("checkpoints", oneModel) is_link = os.path.islink(str(model_path)) - modelaname_only = Path(oneModel).stem - if is_link == False: - model_version = utility.get_value_from_cache('model_version', modelaname_only) - if model_version is None or model_version not in utility.SUPPORTED_MODELS: - model_version = utility.getModelType(oneModel, 'checkpoints') - if model_version and model_version is not None and model_version != 'NoneType': - utility.add_value_to_cache('model_version', modelaname_only, str(model_version)) - print('Model [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + str(model_version)) - else: - utility.add_value_to_cache('model_version', modelaname_only, 'unknown') - print('Model [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' cached: ' + modelaname_only + ' -> ' + 'unknown') - else: - print('Model [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' already cached: ' + modelaname_only + ' -> ' + str(model_version)) - else: - model_version = utility.get_value_from_cache('model_version', modelaname_only) - if model_version is None or model_version not in utility.SUPPORTED_MODELS: - File_link = Path(str(model_path)).resolve() - comfyModelDir = str(Path(folder_paths.folder_names_and_paths['checkpoints'][0][0]).parent) - # modelType = str(File_link)[(len(comfyModelDir) + 1):(str(File_link).find('\\', len(comfyModelDir) + 1))] - try: - modelType = str(File_link)[str(File_link).index(os.sep + Path(comfyModelDir).stem) + len(Path(comfyModelDir).stem) + 2:(str(File_link).find(os.sep, len(comfyModelDir) + 1))] - linkName_U = str(folder_paths.folder_names_and_paths["diffusion_models"][0][0]) - linkName_D = str(folder_paths.folder_names_and_paths["diffusion_models"][0][1]) - if str(Path(linkName_U).stem + '\\') in str(File_link): - modelType = str(Path(linkName_U).stem) - if str(Path(linkName_D).stem + '\\') in str(File_link): - modelType = str(Path(linkName_D).stem) + modelname_only = Path(oneModel).stem + resolved_path = Path(str(model_path)).resolve() if is_link else None + prefix = f"Model [{model_counter}] / {len(ModelsList)}" - utility.add_value_to_cache('model_version', modelaname_only, f"{modelType}_symlink") - print(f"Model [{model_counter}] / {len(ModelsList)} symlinked file: {modelaname_only} -> from: {modelType}") - except Exception: - utility.add_value_to_cache('model_version', modelaname_only, f"unknown_symlink") - print(f"Model [{model_counter}] / {len(ModelsList)} unknown by meta error: {modelaname_only}") - else: - print('Model [' + str(model_counter) + '] / ' + str(len(ModelsList)) + ' already cached: ' + modelaname_only + ' -> ' + str(model_version)) - model_counter = model_counter + 1 + model_version = utility.get_value_from_cache('model_version', modelname_only) + if model_version is not None and model_version in utility.SUPPORTED_MODELS: + print(f"{prefix} already cached: {modelname_only} -> {model_version}") + model_counter += 1 + continue + + model_version = utility.getModelType(oneModel, 'checkpoints') + if model_version and model_version not in (None, False, 'NoneType') and model_version in utility.SUPPORTED_MODELS: + utility.add_value_to_cache('model_version', modelname_only, model_version) + src = f" (symlink from: {resolved_path})" if is_link else "" + print(f"{prefix} cached from metadata: {modelname_only} -> {model_version}{src}") + model_counter += 1 + continue + + dir_version = get_type_from_dirs(oneModel, resolved_path) + if dir_version: + utility.add_value_to_cache('model_version', modelname_only, dir_version) + src = f" (symlink from: {resolved_path})" if is_link else "" + print(f"{prefix} cached from directory: {modelname_only} -> {dir_version}{src}") + model_counter += 1 + continue + + if is_link: + cache_msg = f"UNKNOWN | checkpoint: models/checkpoints/{oneModel} | original: {resolved_path}" + else: + cache_msg = f"UNKNOWN | path: models/checkpoints/{oneModel}" + utility.add_value_to_cache('model_version', modelname_only, cache_msg) + print(f"{prefix} {cache_msg}") + + model_counter += 1 else: - print('No models in your system....') \ No newline at end of file + print('No models in your system....')