Added nodes TT_SdxlClipTextEncoderNode, TT_SdxlClipTextEncoderContextNode, TT_Flux1ClipTextEncoderNode, TT_Flux1ClipTextEncoderContextNode, minor bugfixes and refactoring, added docs, changed version

This commit is contained in:
TenserTensor
2026-03-05 14:57:25 +03:00
parent ef9c458a55
commit 79d323f10f
12 changed files with 328 additions and 6 deletions
+1 -1
View File
@@ -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}")
+1 -1
View File
@@ -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)
@@ -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"]
+1 -1
View File
@@ -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)
@@ -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"]
+4
View File
@@ -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.")
+188
View File
@@ -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",
]
+1 -1
View File
@@ -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"]
@@ -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)
+32
View File
@@ -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)
@@ -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)
+39
View File
@@ -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)