From 79d323f10f461acd903214eaa644edfd6f62ea01 Mon Sep 17 00:00:00 2001 From: TenserTensor Date: Thu, 5 Mar 2026 14:57:25 +0300 Subject: [PATCH] Added nodes TT_SdxlClipTextEncoderNode, TT_SdxlClipTextEncoderContextNode, TT_Flux1ClipTextEncoderNode, TT_Flux1ClipTextEncoderContextNode, minor bugfixes and refactoring, added docs, changed version --- __init__.py | 2 +- nodes/text_encoders/clip_text_encode_flux.py | 2 +- .../clip_text_encode_flux_context.py | 2 +- nodes/text_encoders/clip_text_encode_sdxl.py | 2 +- .../clip_text_encode_sdxl_context.py | 2 +- nodes_controlnet.py | 4 + nodes_text_encoder.py | 188 ++++++++++++++++++ pyproject.toml | 2 +- .../TT_Flux1ClipTextEncoderContextNode.md | 29 +++ web/docs/TT_Flux1ClipTextEncoderNode.md | 32 +++ web/docs/TT_SdxlClipTextEncoderContextNode.md | 30 +++ web/docs/TT_SdxlClipTextEncoderNode.md | 39 ++++ 12 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 web/docs/TT_Flux1ClipTextEncoderContextNode.md create mode 100644 web/docs/TT_Flux1ClipTextEncoderNode.md create mode 100644 web/docs/TT_SdxlClipTextEncoderContextNode.md create mode 100644 web/docs/TT_SdxlClipTextEncoderNode.md diff --git a/__init__.py b/__init__.py index 0747f8d..621a94a 100644 --- a/__init__.py +++ b/__init__.py @@ -9,7 +9,7 @@ __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] print() SEP = f"{CYAN}{'=' * 60}{RESET}" print(SEP) -print(f"{YELLOW}TenserTensor ComfyUI Nodes Pack (v1.6.12): {BRIGHT_GREEN}{NODES_COUNT} Nodes Loaded{RESET}") +print(f"{YELLOW}TenserTensor ComfyUI Nodes Pack (v1.6.13): {BRIGHT_GREEN}{NODES_COUNT} Nodes Loaded{RESET}") print(SEP) print(f"{BRIGHT_CYAN}[V] Repository: https://github.com/tenser-tensor/ComfyUI-TenserTensor{RESET}") print(f"{BRIGHT_CYAN}[V] ComfyUI Registry: https://registry.comfy.org/publishers/tenser-tensor{RESET}") diff --git a/nodes/text_encoders/clip_text_encode_flux.py b/nodes/text_encoders/clip_text_encode_flux.py index 23b3f1c..4a98198 100644 --- a/nodes/text_encoders/clip_text_encode_flux.py +++ b/nodes/text_encoders/clip_text_encode_flux.py @@ -20,7 +20,7 @@ class TT_ClipTextEncodeFlux(): RETURN_TYPES = ("CONDITIONING", "CONDITIONING",) RETURN_NAMES = ("POSITIVE", "NEGATIVE",) FUNCTION = "execute" - CATEGORY = "TenserTensor/Text Encoders/FLUX" + CATEGORY = "TenserTensor/Deprecated/Text Encoders/FLUX" def execute(self, **kwargs): return encode_prompts_flux(**kwargs) diff --git a/nodes/text_encoders/clip_text_encode_flux_context.py b/nodes/text_encoders/clip_text_encode_flux_context.py index cb67a07..533b6ad 100644 --- a/nodes/text_encoders/clip_text_encode_flux_context.py +++ b/nodes/text_encoders/clip_text_encode_flux_context.py @@ -15,7 +15,7 @@ class TT_ClipTextEncodeFluxContext(): RETURN_TYPES = ("TT_CONTEXT", "CONDITIONING", "CONDITIONING",) RETURN_NAMES = ("CONTEXT", "POSITIVE", "NEGATIVE",) FUNCTION = "execute" - CATEGORY = "TenserTensor/Text Encoders/FLUX" + CATEGORY = "TenserTensor/Deprecated/Text Encoders/FLUX" def execute(self, context): clip = context["clip"] diff --git a/nodes/text_encoders/clip_text_encode_sdxl.py b/nodes/text_encoders/clip_text_encode_sdxl.py index 6b4ac58..0adca45 100644 --- a/nodes/text_encoders/clip_text_encode_sdxl.py +++ b/nodes/text_encoders/clip_text_encode_sdxl.py @@ -26,7 +26,7 @@ class TT_ClipTextEncodeSdxl(): RETURN_TYPES = ("CONDITIONING", "CONDITIONING",) RETURN_NAMES = ("POSITIVE", "NEGATIVE",) FUNCTION = "execute" - CATEGORY = "TenserTensor/Text Encoders/SDXL" + CATEGORY = "TenserTensor/Deprecated/Text Encoders/SDXL" def execute(self, **kwargs): return encode_prompts_sdxl(**kwargs) diff --git a/nodes/text_encoders/clip_text_encode_sdxl_context.py b/nodes/text_encoders/clip_text_encode_sdxl_context.py index de1848f..13d2872 100644 --- a/nodes/text_encoders/clip_text_encode_sdxl_context.py +++ b/nodes/text_encoders/clip_text_encode_sdxl_context.py @@ -15,7 +15,7 @@ class TT_ClipTextEncodeSdxlContext(): RETURN_TYPES = ("TT_CONTEXT", "CONDITIONING", "CONDITIONING",) RETURN_NAMES = ("CONTEXT", "POSITIVE", "NEGATIVE",) FUNCTION = "execute" - CATEGORY = "TenserTensor/Text Encoders/SDXL" + CATEGORY = "TenserTensor/Deprecated/Text Encoders/SDXL" def execute(self, context): clip = context["clip"] diff --git a/nodes_controlnet.py b/nodes_controlnet.py index d66d42f..0749fcd 100644 --- a/nodes_controlnet.py +++ b/nodes_controlnet.py @@ -16,7 +16,11 @@ def get_control_net_files(): def load_control_net(filename): cnet_path = folder_paths.get_full_path_or_raise("controlnet", filename) + + print(f"CNET path: {cnet_path}") + cnet = controlnet.load_controlnet(cnet_path) + if cnet is None: raise RuntimeError("ERROR: ControlNet file is invalid or corrupted.") diff --git a/nodes_text_encoder.py b/nodes_text_encoder.py index 2306d43..6709543 100644 --- a/nodes_text_encoder.py +++ b/nodes_text_encoder.py @@ -3,12 +3,192 @@ from typing import override from comfy_api.latest import io, ComfyExtension +from nodes import MAX_RESOLUTION from .nodes_context import Context from .nodes_image import SingleCondCFGGuider CATEGORY = "TenserTensor/Text Encoder" +def encode_prompts_sdxl(**kwargs): + clip = kwargs.get("clip") + + if clip is None: + raise ValueError("ERROR: CLIP is required for text encoder") + + positive_tokens = clip.tokenize(kwargs.get("clip_g_positive")) + positive_tokens["l"] = clip.tokenize(kwargs.get("clip_l_positive"))["l"] + negative_tokens = clip.tokenize(kwargs.get("clip_g_negative")) + negative_tokens["l"] = clip.tokenize(kwargs.get("clip_l_negative"))["l"] + + cond_dict = { + "aesthetic_score": kwargs.get("ascore_positive"), + "width": kwargs.get("width"), + "height": kwargs.get("height"), + "target_width": kwargs.get("target_width"), + "target_height": kwargs.get("target_height"), + } + + positive = clip.encode_from_tokens_scheduled(positive_tokens, add_dict=cond_dict) + cond_dict["aesthetic_score"] = kwargs.get("ascore_negative") + negative = clip.encode_from_tokens_scheduled(negative_tokens, add_dict=cond_dict) + + return positive, negative + + +class TT_SdxlClipTextEncoderNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_SdxlClipTextEncoderNode", + display_name="TT SDXL CLIP Text Encoder", + category=CATEGORY, + description="", + inputs=[ + io.Clip.Input("clip"), + io.String.Input("clip_l_positive", multiline=True, placeholder="CLIP_L Positive", dynamic_prompts=True), + io.String.Input("clip_g_positive", multiline=True, placeholder="CLIP_G Positive", dynamic_prompts=True), + io.String.Input("clip_l_negative", multiline=True, placeholder="CLIP_L Negative", dynamic_prompts=True), + io.String.Input("clip_g_negative", multiline=True, placeholder="CLIP_G Negative", dynamic_prompts=True), + io.Float.Input("ascore_positive", default=9.0, min=0.0, max=100.0, step=0.1), + io.Float.Input("ascore_negative", default=6.0, min=0.0, max=100.0, step=0.1), + io.Int.Input("width", default=512, min=64, max=MAX_RESOLUTION, step=8), + io.Int.Input("height", default=512, min=64, max=MAX_RESOLUTION, step=8), + io.Int.Input("target_width", default=512, min=64, max=MAX_RESOLUTION, step=8), + io.Int.Input("target_height", default=512, min=64, max=MAX_RESOLUTION, step=8), + ], + outputs=[ + io.Conditioning.Output("POSITIVE"), + io.Conditioning.Output("NEGATIVE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + positive, negative = encode_prompts_sdxl(**kwargs) + + return io.NodeOutput(positive, negative) + + +class TT_SdxlClipTextEncoderContextNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_SdxlClipTextEncoderContextNode", + display_name="TT SDXL CLIP Text Encoder (Context)", + category=CATEGORY, + description="", + inputs=[ + Context.Input("context") + ], + outputs=[ + Context.Output("CONTEXT"), + io.Conditioning.Output("POSITIVE"), + io.Conditioning.Output("NEGATIVE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + context = kwargs.get("context") + + args = {} + for key in ( + "clip", "clip_l_positive", "clip_g_positive", "clip_l_negative", "clip_g_negative", + "ascore_positive", "ascore_negative", "width", "height", "target_width", "target_height", + ): + args[key] = context.get_attr(key) + + positive, negative = encode_prompts_sdxl(**args) + context.set_attr("positive", positive) + context.set_attr("negative", negative) + + return io.NodeOutput(context, positive, negative) + + +def encode_prompts_flux(**kwargs): + clip = kwargs.get("clip") + + if clip is None: + raise ValueError("ERROR: CLIP is required for text encoder") + + positive_tokens = clip.tokenize(kwargs.get("clip_l_positive")) + positive_tokens["t5xxl"] = clip.tokenize(kwargs.get("t5xxl_positive"))["t5xxl"] + negative_tokens = clip.tokenize(kwargs.get("clip_l_negative")) + negative_tokens["t5xxl"] = clip.tokenize(kwargs.get("t5xxl_negative"))["t5xxl"] + + guidance = kwargs.get("guidance") + positive = clip.encode_from_tokens_scheduled(positive_tokens, add_dict={"guidance": guidance, }) + negative = clip.encode_from_tokens_scheduled(negative_tokens, add_dict={"guidance": guidance, }) + + return positive, negative + + +class TT_Flux1ClipTextEncoderNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_Flux1ClipTextEncoderNode", + display_name="TT FLUX1 CLIP Text Encoder", + category=CATEGORY, + description="", + inputs=[ + io.Clip.Input("clip"), + io.String.Input("clip_l_positive", multiline=True, placeholder="CLIP_L Positive", dynamic_prompts=True), + io.String.Input("t5xxl_positive", multiline=True, placeholder="T5XXL Positive", dynamic_prompts=True), + io.String.Input("clip_l_negative", multiline=True, placeholder="CLIP_L Negative", dynamic_prompts=True), + io.String.Input("t5xxl_negative", multiline=True, placeholder="T5XXL Negative", dynamic_prompts=True), + io.Float.Input("guidance", default=9.0, min=0.0, max=100.0, step=0.1), + ], + outputs=[ + io.Conditioning.Output("POSITIVE"), + io.Conditioning.Output("NEGATIVE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + positive, negative = encode_prompts_flux(**kwargs) + + return io.NodeOutput(positive, negative) + + +class TT_Flux1ClipTextEncoderContextNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="TT_Flux1ClipTextEncoderContextNode", + display_name="TT FLUX1 CLIP Text Encoder (Context)", + category=CATEGORY, + description="", + inputs=[ + Context.Input("context") + ], + outputs=[ + Context.Output("CONTEXT"), + io.Conditioning.Output("POSITIVE"), + io.Conditioning.Output("NEGATIVE"), + ] + ) + + @classmethod + def execute(cls, **kwargs) -> io.NodeOutput: + context = kwargs.get("context") + + args = {} + for key in ( + "clip", "clip_l_positive", "t5xxl_positive", "clip_l_negative", "t5xxl_negative", + "guidance", + ): + args[key] = context.get_attr(key) + + positive, negative = encode_prompts_flux(**args) + context.set_attr("positive", positive) + context.set_attr("negative", negative) + + return io.NodeOutput(context, positive, negative) + + def encode_prompts_flux2(**kwargs): clip = kwargs.get("clip") model = kwargs.get("model") @@ -103,6 +283,10 @@ class TextEncodeNodesExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ + TT_SdxlClipTextEncoderNode, + TT_SdxlClipTextEncoderContextNode, + TT_Flux1ClipTextEncoderNode, + TT_Flux1ClipTextEncoderContextNode, TT_Flux2TextEncoderNode, TT_Flux2TextEncoderContextNode, ] @@ -117,6 +301,10 @@ async def comfy_entrypoint() -> TextEncodeNodesExtension: # ============================================================================== __all__ = [ + "TT_SdxlClipTextEncoderNode", + "TT_SdxlClipTextEncoderContextNode", + "TT_Flux1ClipTextEncoderNode", + "TT_Flux1ClipTextEncoderContextNode", "TT_Flux2TextEncoderNode", "TT_Flux2TextEncoderContextNode", ] diff --git a/pyproject.toml b/pyproject.toml index 323be90..529de0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ [project] name = "ComfyUI-TenserTensor" description = "A collection of custom nodes for ComfyUI that streamline common workflows. Includes all-in-one model loaders (checkpoint/unet, LoRA, CLIP, VAE), GGUF quantized model support (FLUX, FLUX2, SDXL), workflow settings nodes, latent factory (aspect ratio, megapixels, FLUX/SDXL), context nodes, CLIP text encoders, KSamplers (context, advanced, two-stage - draft/refine), VAE encoders/decoders (context, tiled), and post-production tools (brightness, contrast, LUT color grading, film grain, upscaler)." -version = "1.6.12" +version = "1.6.13" license = { file = "LICENSE" } dependencies = ["gguf", "kornia"] diff --git a/web/docs/TT_Flux1ClipTextEncoderContextNode.md b/web/docs/TT_Flux1ClipTextEncoderContextNode.md new file mode 100644 index 0000000..ec6fdd2 --- /dev/null +++ b/web/docs/TT_Flux1ClipTextEncoderContextNode.md @@ -0,0 +1,29 @@ +## TT FLUX1 CLIP Text Encoder (Context) *TT_Flux1ClipTextEncoderContextNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Context-based variant of the FLUX1 CLIP text encoder. Reads all required parameters from a Context object and writes the resulting conditioning back +into it. + +### Inputs + +| Parameter | Type | Required | Description | +|-----------|------------|----------|-----------------------------------------------------------| +| `context` | TT_CONTEXT | Yes | Context object carrying CLIP, prompts, and guidance scale | + +### Outputs + +| Parameter | Type | Description | +|------------|--------------|-----------------------------------------------------------------------| +| `CONTEXT` | TT_CONTEXT | Updated Context with positive and negative conditioning stored inside | +| `POSITIVE` | CONDITIONING | Positive conditioning | +| `NEGATIVE` | CONDITIONING | Negative conditioning | + +### Usage + +Drop-in replacement for [TT FLUX1 CLIP Text Encoder](TT_Flux1ClipTextEncoderNode.md) in Context-based workflows. The Context must have `clip`, +`clip_l_positive`, `t5xxl_positive`, `clip_l_negative`, `t5xxl_negative`, and `guidance` set before reaching this node. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_Flux1ClipTextEncoderNode.md b/web/docs/TT_Flux1ClipTextEncoderNode.md new file mode 100644 index 0000000..7d7340e --- /dev/null +++ b/web/docs/TT_Flux1ClipTextEncoderNode.md @@ -0,0 +1,32 @@ +## TT FLUX1 CLIP Text Encoder *TT_Flux1ClipTextEncoderNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Encodes positive and negative text prompts for FLUX1 models using dual text encoders (CLIP-L and T5-XXL) with guidance scale conditioning. + +### Inputs + +| Parameter | Type | Required | Description | +|-------------------|--------|----------|-------------------------------------------| +| `clip` | CLIP | Yes | Dual text encoder model (CLIP-L + T5-XXL) | +| `clip_l_positive` | STRING | Yes | CLIP-L positive prompt | +| `t5xxl_positive` | STRING | Yes | T5-XXL positive prompt | +| `clip_l_negative` | STRING | Yes | CLIP-L negative prompt | +| `t5xxl_negative` | STRING | Yes | T5-XXL negative prompt | +| `guidance` | FLOAT | Yes | Guidance scale, default 9.0 | + +### Outputs + +| Parameter | Type | Description | +|------------|--------------|-----------------------| +| `POSITIVE` | CONDITIONING | Positive conditioning | +| `NEGATIVE` | CONDITIONING | Negative conditioning | + +### Usage + +Connect `POSITIVE` and `NEGATIVE` outputs to a sampler. CLIP-L handles short, structured descriptions; T5-XXL understands longer, more complex +prompts — you can use the same text in both or tailor each separately. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_SdxlClipTextEncoderContextNode.md b/web/docs/TT_SdxlClipTextEncoderContextNode.md new file mode 100644 index 0000000..4f15c6a --- /dev/null +++ b/web/docs/TT_SdxlClipTextEncoderContextNode.md @@ -0,0 +1,30 @@ +## TT SDXL CLIP Text Encoder (Context) *TT_SdxlClipTextEncoderContextNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Context-based variant of the SDXL CLIP text encoder. Reads all required parameters from a Context object and writes the resulting conditioning back +into it. + +### Inputs + +| Parameter | Type | Required | Description | +|-----------|------------|----------|------------------------------------------------------------------------------------| +| `context` | TT_CONTEXT | Yes | Context object carrying CLIP, prompts, aesthetic scores, and resolution parameters | + +### Outputs + +| Parameter | Type | Description | +|------------|--------------|-----------------------------------------------------------------------| +| `CONTEXT` | TT_CONTEXT | Updated Context with positive and negative conditioning stored inside | +| `POSITIVE` | CONDITIONING | Positive conditioning | +| `NEGATIVE` | CONDITIONING | Negative conditioning | + +### Usage + +Drop-in replacement for [TT SDXL CLIP Text Encoder](TT_SdxlClipTextEncoderNode.md) in Context-based workflows. The Context must have `clip`, +`clip_l_positive`, `clip_g_positive`, `clip_l_negative`, `clip_g_negative`, `ascore_positive`, `ascore_negative`, `width`, `height`, `target_width`, +and `target_height` set before reaching this node. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor) diff --git a/web/docs/TT_SdxlClipTextEncoderNode.md b/web/docs/TT_SdxlClipTextEncoderNode.md new file mode 100644 index 0000000..2708983 --- /dev/null +++ b/web/docs/TT_SdxlClipTextEncoderNode.md @@ -0,0 +1,39 @@ +## TT SDXL CLIP Text Encoder *TT_SdxlClipTextEncoderNode* + +*This description was generated with AI assistance. If you spot any errors, please create an issue on GitHub.* + +Encodes positive and negative text prompts for SDXL models using dual CLIP encoders (CLIP-L and CLIP-G) with aesthetic score and resolution +conditioning. + +### Inputs + +| Parameter | Type | Required | Description | +|-------------------|--------|----------|--------------------------------------------------------| +| `clip` | CLIP | Yes | Dual CLIP model (CLIP-L + CLIP-G) | +| `clip_l_positive` | STRING | Yes | CLIP-L positive prompt | +| `clip_g_positive` | STRING | Yes | CLIP-G positive prompt | +| `clip_l_negative` | STRING | Yes | CLIP-L negative prompt | +| `clip_g_negative` | STRING | Yes | CLIP-G negative prompt | +| `ascore_positive` | FLOAT | Yes | Aesthetic score for positive conditioning, default 9.0 | +| `ascore_negative` | FLOAT | Yes | Aesthetic score for negative conditioning, default 6.0 | +| `width` | INT | Yes | Source image width | +| `height` | INT | Yes | Source image height | +| `target_width` | INT | Yes | Target output width | +| `target_height` | INT | Yes | Target output height | + +### Outputs + +| Parameter | Type | Description | +|------------|--------------|-----------------------| +| `POSITIVE` | CONDITIONING | Positive conditioning | +| `NEGATIVE` | CONDITIONING | Negative conditioning | + +### Usage + +Connect `POSITIVE` and `NEGATIVE` outputs to a sampler. Set `width`/`height` to match your source image dimensions and `target_width`/`target_height` +to your desired output resolution. Higher `ascore_positive` (8–10) steers generation toward higher aesthetic quality; lower `ascore_negative` (4–6) +pushes away from low-quality outputs. + +--- + +[View on GitHub](https://github.com/tenser-tensor/ComfyUI-TenserTensor)