import re import logging from itertools import chain from nodes import MAX_RESOLUTION import comfy.model_patcher import comfy.sd import comfy.model_management import comfy.samplers from .smZNodes import HijackClip, HijackClipComfy, get_learned_conditioning from comfy_extras.nodes_clip_sdxl import CLIPTextEncodeSDXL class smZ_CLIPTextEncode: @classmethod def INPUT_TYPES(s): return {"required": { "text": ("STRING", {"multiline": True, "dynamicPrompts": True}), "clip": ("CLIP", ), "parser": (["comfy", "comfy++", "A1111", "full", "compel", "fixed attention"],{"default": "comfy"}), "mean_normalization": ("BOOLEAN", {"default": True, "tooltip": "Toggles whether weights are normalized by taking the mean"}), "multi_conditioning": ("BOOLEAN", {"default": True}), "use_old_emphasis_implementation": ("BOOLEAN", {"default": False}), "with_SDXL": ("BOOLEAN", {"default": False}), "ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}), "width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), "height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), "crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), "crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}), "target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), "target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}), "text_g": ("STRING", {"multiline": True, "placeholder": "CLIP_G", "dynamicPrompts": True}), "text_l": ("STRING", {"multiline": True, "placeholder": "CLIP_L", "dynamicPrompts": True}), }, "optional": { "smZ_steps": ("INT", {"default": 1, "min": 1, "max": 0xffffffffffffffff}), }, } RETURN_TYPES = ("CONDITIONING",) FUNCTION = "encode" CATEGORY = "conditioning" def encode(self, clip: comfy.sd.CLIP, text, parser, mean_normalization, multi_conditioning, use_old_emphasis_implementation, with_SDXL, ascore, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l, smZ_steps=1): from .modules.shared import Options, opts, opts_default debug=opts.debug # get global opts' debug if (opts_new := clip.patcher.model_options.get(Options.KEY, None)) is not None: opts.update(opts_new) debug = opts_new.debug else: opts.update(opts_default) opts.debug = debug opts.prompt_mean_norm = mean_normalization opts.use_old_emphasis_implementation = use_old_emphasis_implementation opts.multi_conditioning = multi_conditioning class_name = clip.cond_stage_model.__class__.__name__ is_sdxl = "SDXL" in class_name on_sdxl = with_SDXL and is_sdxl parsers = { "full": "Full parser", "compel": "Compel parser", "A1111": "A1111 parser", "fixed attention": "Fixed attention", "comfy++": "Comfy++ parser", } opts.prompt_attention = parsers.get(parser, "Comfy parser") def _comfy_path(clip, text): nonlocal on_sdxl, class_name, ascore, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l if on_sdxl and class_name == "SDXLClipModel": return CLIPTextEncodeSDXL().encode(clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l) elif on_sdxl and class_name == "SDXLRefinerClipModel": from comfy_extras.nodes_clip_sdxl import CLIPTextEncodeSDXLRefiner return CLIPTextEncodeSDXLRefiner().encode(clip, clip, ascore, width, height, text) else: from nodes import CLIPTextEncode return CLIPTextEncode().encode(clip, text) def comfy_path(clip): nonlocal text if on_sdxl and class_name == "SDXLRefinerClipModel": return _comfy_path(clip, text) else: if multi_conditioning: prompts = re.compile(r"\bAND\b").split(text) return (list(chain(*(_comfy_path(clip, prompt)[0] for prompt in prompts))), ) else: return _comfy_path(clip, text) if parser == "comfy": with HijackClipComfy(clip) as clip: return comfy_path(clip) elif parser == "comfy++": with HijackClip(clip, opts) as clip: with HijackClipComfy(clip) as clip: return comfy_path(clip) with HijackClip(clip, opts) as clip: model = lambda txt: clip.encode_from_tokens(clip.tokenize(txt), return_pooled=True, return_dict=True) steps = max(smZ_steps, 1) if on_sdxl and class_name == "SDXLClipModel": # skip prompt-editing schedules = CLIPTextEncodeSDXL().encode(clip, width, height, crop_w, crop_h, target_width, target_height, [text_g], [text_l])[0] else: schedules = get_learned_conditioning(model, [text], steps, multi_conditioning) if on_sdxl and class_name == "SDXLRefinerClipModel": for cx in schedules: cx[1] |= {"aesthetic_score": ascore, "width": width,"height": height} return (schedules, ) # Hack: string type that is always equal in not equal comparisons class AnyType(str): def __eq__(self, _): return True def __ne__(self, _): return False # Our any instance wants to be a wildcard string anytype = AnyType("*") class smZ_Settings: @classmethod def INPUT_TYPES(s): from .modules.shared import opts_default as opts from .modules.text_processing.emphasis import get_options_descriptions_nl i = 0 def create_heading(): nonlocal i return "ㅤ"*(i:=i+1) create_heading_value = lambda x: ("STRING", {"multiline": False, "default": x, "placeholder": x}) optional = { # "show_headings": ("BOOLEAN", {"default": True}), # "show_descriptions": ("BOOLEAN", {"default":True}), create_heading(): create_heading_value("Stable Diffusion"), "info_comma_padding_backtrack": ("STRING", {"multiline": True, "placeholder": "Prompt word wrap length limit\nin tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"}), "Prompt word wrap length limit": ("INT", {"default": opts.comma_padding_backtrack, "min": 0, "max": 74, "step": 1, "tooltip": "🚧Prompt word wrap length limit\n\nin tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"}), "enable_emphasis": ("BOOLEAN", {"default": opts.enable_emphasis, "tooltip": "🚧Emphasis mode\n\nmakes it possible to make model to pay (more:1.1) or (less:0.9) attention to text when you use the syntax in prompt;\n\n" + get_options_descriptions_nl()}), "info_RNG": ("STRING", {"multiline": True, "placeholder": "Random number generator source.\nchanges seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"}), "RNG": (["cpu", "gpu", "nv"],{"default": opts.randn_source, "tooltip": "Random number generator source.\n\nchanges seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"}), create_heading(): create_heading_value("Compute Settings"), "info_disable_nan_check": ("STRING", {"multiline": True, "placeholder": "Disable NaN check in produced images/latent spaces. Only for CFGDenoiser."}), "disable_nan_check": ("BOOLEAN", {"default": opts.disable_nan_check, "tooltip": "Disable NaN check in produced images/latent spaces. Only for CFGDenoiser."}), create_heading(): create_heading_value("Sampler parameters"), "info_eta_ancestral": ("STRING", {"multiline": True, "placeholder": "Eta for k-diffusion samplers\nnoise multiplier; currently only applies to ancestral samplers (i.e. Euler a) and SDE samplers"}), "eta": ("FLOAT", {"default": opts.eta, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Eta for k-diffusion samplers\n\nnoise multiplier; currently only applies to ancestral samplers (i.e. Euler a) and SDE samplers"}), "info_s_churn": ("STRING", {"multiline": True, "placeholder": "Sigma churn\namount of stochasticity; only applies to Euler, Heun, Heun++2, and DPM2"}), "s_churn": ("FLOAT", {"default": opts.s_churn, "min": 0.0, "max": 100.0, "step": 0.01, "tooltip": "Sigma churn\n\namount of stochasticity; only applies to Euler, Heun, Heun++2, and DPM2"}), "info_s_tmin": ("STRING", {"multiline": True, "placeholder": "Sigma tmin\nenable stochasticity; start value of the sigma range; only applies to Euler, Heun, Heun++2, and DPM2'"}), "s_tmin": ("FLOAT", {"default": opts.s_tmin, "min": 0.0, "max": 10.0, "step": 0.01, "tooltip": "Sigma tmin\n\nenable stochasticity; start value of the sigma range; only applies to Euler, Heun, Heun++2, and DPM2'"}), "info_s_tmax": ("STRING", {"multiline": True, "placeholder": "Sigma tmax\n0 = inf; end value of the sigma range; only applies to Euler, Heun, Heun++2, and DPM2"}), "s_tmax": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 999.0, "step": 0.01, "tooltip": "Sigma tmax\n\n0 = inf; end value of the sigma range; only applies to Euler, Heun, Heun++2, and DPM2"}), "info_s_noise": ("STRING", {"multiline": True, "placeholder": "Sigma noise\namount of additional noise to counteract loss of detail during sampling"}), "s_noise": ("FLOAT", {"default": opts.s_noise, "min": 0.0, "max": 1.1, "step": 0.001, "tooltip": "Sigma noise\n\namount of additional noise to counteract loss of detail during sampling"}), "info_eta_noise_seed_delta": ("STRING", {"multiline": True, "placeholder": "Eta noise seed delta\ndoes not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"}), "ENSD": ("INT", {"default": opts.eta_noise_seed_delta, "min": 0, "max": 0xffffffffffffffff, "step": 1, "tooltip": "Eta noise seed delta\n\ndoes not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"}), "info_skip_early_cond": ("STRING", {"multiline": True, "placeholder": "Ignore negative prompt during early sampling\ndisables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"}), "skip_early_cond": ("FLOAT", {"default": opts.skip_early_cond, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Ignore negative prompt during early sampling\n\ndisables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"}), "info_sgm_noise_multiplier": ("STRING", {"multiline": True, "placeholder": "SGM noise multiplier\nmatch initial noise to official SDXL implementation - only useful for reproducing images\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818"}), "sgm_noise_multiplier": ("BOOLEAN", {"default": opts.sgm_noise_multiplier, "tooltip": "SGM noise multiplier\n\nmatch initial noise to official SDXL implementation - only useful for reproducing images\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818"}), "info_upcast_sampling": ("STRING", {"multiline": True, "placeholder": "upcast sampling.\nNo effect with --force-fp32. Usually produces similar results to --force-fp32 with better performance while using less memory."}), "upcast_sampling": ("BOOLEAN", {"default": opts.upcast_sampling, "tooltip": "🚧upcast sampling.\n\nNo effect with --force-fp32. Usually produces similar results to --force-fp32 with better performance while using less memory."}), create_heading(): create_heading_value("Optimizations"), "info_NGMS": ("STRING", {"multiline": True, "placeholder": "Negative Guidance minimum sigma\nskip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster. Only for CFGDenoiser.\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177\nhttps://github.com/lllyasviel/stable-diffusion-webui-forge/pull/1434"}), "NGMS": ("FLOAT", {"default": opts.s_min_uncond, "min": 0.0, "max": 15.0, "step": 0.01, "tooltip": "Negative Guidance minimum sigma\n\nskip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster.\nsee https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177\nhttps://github.com/lllyasviel/stable-diffusion-webui-forge/pull/1434"}), "info_NGMS_all_steps": ("STRING", {"multiline": True, "placeholder": "Negative Guidance minimum sigma all steps\nBy default, NGMS above skips every other step; this makes it skip all steps"}), "NGMS all steps": ("BOOLEAN", {"default": opts.s_min_uncond_all, "tooltip": "Negative Guidance minimum sigma all steps\n\nBy default, NGMS above skips every other step; this makes it skip all steps"}), "info_pad_cond_uncond": ("STRING", {"multiline": True, "placeholder": "Pad prompt/negative prompt to be same length\nimproves performance when prompt and negative prompt have different lengths; changes seeds. Only for CFGDenoiser."}), "pad_cond_uncond": ("BOOLEAN", {"default": opts.pad_cond_uncond, "tooltip": "🚧Pad prompt/negative prompt to be same length\n\nimproves performance when prompt and negative prompt have different lengths; changes seeds. Only for CFGDenoiser."}), "info_batch_cond_uncond": ("STRING", {"multiline": True, "placeholder": "Batch cond/uncond\ndo both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed. Only for CFGDenoiser."}), "batch_cond_uncond": ("BOOLEAN", {"default": opts.batch_cond_uncond, "tooltip": "🚧Batch cond/uncond\n\ndo both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed. Only for CFGDenoiser."}), create_heading(): create_heading_value("Compatibility"), "info_use_prev_scheduling": ("STRING", {"multiline": True, "placeholder": "Previous prompt editing timelines\nFor [red:green:N]; previous: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"}), "Use previous prompt editing timelines": ("BOOLEAN", {"default": opts.use_old_scheduling, "tooltip": "🚧Previous prompt editing timelines\n\nFor [red:green:N]; previous: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"}), create_heading(): create_heading_value("Experimental"), "info_use_CFGDenoiser": ("STRING", {"multiline": True, "placeholder": "CFGDenoiser\nAn experimental option to use stable-diffusion-webui's denoiser. It allows you to use the 'Optimizations' settings listed here."}), "Use CFGDenoiser": ("BOOLEAN", {"default": opts.use_CFGDenoiser, "tooltip": "🚧CFGDenoiser\n\nAn experimental option to use stable-diffusion-webui's denoiser. It allows you to use the 'Optimizations' settings listed here."}), "info_debug": ("STRING", {"multiline": True, "placeholder": "Debugging messages in the console."}), "debug": ("BOOLEAN", {"default": opts.debug, "label_on": "on", "label_off": "off", "tooltip": "Debugging messages in the console."}), } return { "required": { "*": (anytype, {"forceInput": True}), }, "optional": { "extra": ("STRING", {"multiline": True, "default": '{"show_headings":true,"show_descriptions":false,"mode":"*"}'}), **optional, }, } RETURN_TYPES = (anytype,) FUNCTION = "apply" CATEGORY = "advanced" OUTPUT_TOOLTIPS = ("The model used for denoising latents.",) def apply(self, *args, **kwargs): first = kwargs.pop('*', None) if '*' in kwargs else args[0] if not hasattr(first, 'clone') or first is None: return (first,) kwargs['s_min_uncond'] = kwargs.pop('NGMS', 0.0) kwargs['s_min_uncond_all'] = kwargs.pop('NGMS all steps', False) kwargs['comma_padding_backtrack'] = kwargs.pop('Prompt word wrap length limit') kwargs['use_old_scheduling']=kwargs.pop("Use previous prompt editing timelines") kwargs['use_CFGDenoiser'] = kwargs.pop("Use CFGDenoiser") kwargs['randn_source'] = kwargs.pop('RNG') kwargs['eta_noise_seed_delta'] = kwargs.pop('ENSD') kwargs['s_tmax'] = kwargs['s_tmax'] or float('inf') from .modules.shared import Options, logger, opts_default, opts as opts_global opts_global.update(opts_default) opts = opts_default.clone() kwargs_new = {k: v for k, v in kwargs.items() if not ('info' in k or 'heading' in k or 'ㅤ' in k)} opts.update(kwargs_new) opts_global.debug = opts.debug opts_key = Options.KEY if isinstance(first, comfy.model_patcher.ModelPatcher): first = first.clone() first.model_options[opts_key] = opts elif isinstance(first, comfy.sd.CLIP): first = first.clone() first.patcher.model_options[opts_key] = opts logger.setLevel(logging.DEBUG if opts_global.debug else logging.INFO) return (first,) # A dictionary that contains all nodes you want to export with their names # NOTE: names should be globally unique NODE_CLASS_MAPPINGS = { "smZ CLIPTextEncode": smZ_CLIPTextEncode, "smZ Settings": smZ_Settings, } # A dictionary that contains the friendly/humanly readable titles for the nodes NODE_DISPLAY_NAME_MAPPINGS = { "smZ CLIPTextEncode" : "CLIP Text Encode++", "smZ Settings" : "Settings (smZ)", }