A1111 for some reason can use a different Prompt word wrap length limit as 75. They can also use 255. So it makes more sense to change the maximum value from 74 to 255.
244 lines
18 KiB
Python
244 lines
18 KiB
Python
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": 255, "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)",
|
||
}
|