Added some LTXV nodes, bugfixes, updated workflow & context nodes

This commit is contained in:
TenserTensor
2026-03-25 00:10:10 +03:00
parent 8902529caf
commit 4927403a87
14 changed files with 919 additions and 739 deletions
+9 -3
View File
@@ -2,7 +2,7 @@
from typing import override
from comfy_api.latest import ComfyExtension, io
from comfy_api.latest import ComfyAPI, ComfyExtension, io
from .nodes_context import NODES as NODES_CONTEXT
from .nodes_controlnet import NODES as NODES_CONTROLNET
from .nodes_debug import NODES as NODES_DEBUG
@@ -15,8 +15,12 @@ from .nodes_loaders_d import NODES as NODES_LOADERS_D
from .nodes_postproduction import NODES as NODES_POSTPRODUCTION
from .nodes_sampling import NODES as NODES_SAMPLING
from .nodes_text_encoder import NODES as NODES_TEXT_ENCODER
from .nodes_vae import NODES as NODES_VAE
from .nodes_text_encoder_d import NODES as NODES_TEXT_ENCODER_D
from .nodes_vae_d import NODES as NODES_VAE_D
from .nodes_workflow import NODES as NODES_WORKFLOW
from .nodes_workflow_d import NODES as NODES_WORKFLOW_D
api = ComfyAPI()
class TenserTensorExtension(ComfyExtension):
@@ -35,6 +39,8 @@ class TenserTensorExtension(ComfyExtension):
*NODES_POSTPRODUCTION,
*NODES_SAMPLING,
*NODES_TEXT_ENCODER,
*NODES_VAE,
*NODES_TEXT_ENCODER_D,
*NODES_VAE_D,
*NODES_WORKFLOW,
*NODES_WORKFLOW_D,
]
+1 -1
View File
@@ -10,7 +10,7 @@ from comfy import ldm, sd, utils, model_patcher, model_management, lora, float
from .dequant import is_quantized, is_torch_compatible
from .loader import gguf_clip_loader, gguf_sd_loader
from .ops import GGMLOps, move_patch_to_device
from ..utils import raise_unless, get_embedding_directory, apply_lora_stack
from ..utils import raise_unless, get_embedding_directory
def attention_override_pytorch(func, *args, **kwargs):
+51 -36
View File
@@ -4,9 +4,7 @@ import dataclasses
import re
from dataclasses import dataclass
from comfy.clip_vision import Output
from comfy_api.latest import IO, ComfyExtension, Input
from comfy_api.latest import io, Input
from .nodes_workflow import TTWorkflowSettings, WorkflowSettings
CATEGORY = "TenserTensor/Context"
@@ -23,29 +21,46 @@ class TTContext():
Use create() to instantiate from kwargs, and set_attr/get_attr for safe field access.
Workflow config fields are accessible via set_workflow_config_attr/get_workflow_config_attr.
"""
cfg: IO.Float | None = None
clip: IO.Clip | None = None
control_net: IO.ControlNet | None = None
conditioning: IO.Conditioning | None = None
guidance: IO.Float | None = None
guider: IO.Guider | None = None
image: IO.Image | None = None
latent: IO.Latent | None = None
lora_triggers: IO.String | None = None
mask: IO.Mask | None = None
model: IO.Model | None = None
negative: IO.Conditioning | None = None
positive: IO.Conditioning | None = None
prompt: IO.String | None = None
rnd_noise: IO.Noise | None = None
sampler: IO.Sampler | None = None
seed: IO.Int | None = None
sigmas: IO.Sigmas | None = None
steps: IO.Int | None = None
vae: IO.Vae | None = None
ascore_negative: io.Float | None = None
ascore_positive: io.Float | None = None
cfg: io.Float | None = None
clip: io.Clip | None = None
clip_g_negative: io.String | None = None
clip_g_positive: io.String | None = None
clip_l_negative: io.String | None = None
clip_l_positive: io.String | None = None
conditioning: io.Conditioning | None = None
control_net: io.ControlNet | None = None
frame_rate: io.String | None = None
guidance: io.Float | None = None
guider: io.Guider | None = None
height: io.Int | None = None
image: io.Image | None = None
latent: io.Latent | None = None
length_sec: io.Float | None = None
lora_triggers: io.String | None = None
mask: io.Mask | None = None
model: io.Model | None = None
negative: io.Conditioning | None = None
negative_prompt: io.String | None = None
noise_seed: io.Int | None = None
positive: io.Conditioning | None = None
positive_prompt: io.String | None = None
prompt: io.String | None = None
rnd_noise: io.Noise | None = None
sampler: io.Sampler | None = None
sampler_name: io.String | None = None
scheduler: io.String | None = None
seed: io.Int | None = None
sigmas: io.Sigmas | None = None
steps: io.Int | None = None
t5xxl_negative: io.String | None = None
t5xxl_positive: io.String | None = None
target_height: io.Int | None = None
target_width: io.Int | None = None
vae: io.Vae | None = None
width: io.Int | None = None
workflow_config: WorkflowSettings | None = None
sampler_name: IO.String | None = None
scheduler: IO.String | None = None
@classmethod
def create(cls, **kwargs):
@@ -101,20 +116,20 @@ class TTContext():
raise AttributeError(f"ERROR: Workflow Config is not set in context")
@IO.comfytype(io_type="TT_CONTEXT")
@io.comfytype(io_type="TT_CONTEXT")
class Context:
Type = TTContext
class Input(IO.Input):
class Input(io.Input):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class Output(IO.Output):
class Output(io.Output):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class ContextNode(IO.ComfyNode):
class ContextNode(io.ComfyNode):
"""
Abstract base class for all context nodes.
Handles context creation, delegation to handle(), and building return tuple.
@@ -139,7 +154,7 @@ class ContextNode(IO.ComfyNode):
return Context.Input(input_name, optional=optional)
case _:
f_type = cls.F_TYPES[input_name]
if f_type in (IO.Boolean, IO.Int, IO.Float, IO.String,):
if f_type in (io.Boolean, io.Int, io.Float, io.String,):
return f_type.Input(input_name, optional=optional, force_input=True)
else:
return f_type.Input(input_name, optional=optional)
@@ -155,7 +170,7 @@ class ContextNode(IO.ComfyNode):
return input_schema
@classmethod
def outputs(cls) -> list[Output]:
def outputs(cls) -> list[io.Output]:
output_schema = []
for f_name in cls.RETURNS if len(cls.RETURNS) > 0 else cls.REQUIRED + cls.OPTIONAL:
@@ -168,24 +183,24 @@ class ContextNode(IO.ComfyNode):
return output_schema
@classmethod
def node_output(cls, context) -> IO.NodeOutput:
def node_output(cls, context) -> io.NodeOutput:
args = [context]
for f_name in cls.RETURNS if len(cls.RETURNS) > 0 else cls.REQUIRED + cls.OPTIONAL:
if f_name != "context":
args.append(context.get_attr(f_name))
return IO.NodeOutput(*args)
return io.NodeOutput(*args)
@classmethod
def display_name(cls):
return re.sub(r'(?<=[a-z])(?=[A-Z])', ' ', cls.__name__.replace("TT_", "TT ").replace("Node", ""))
@classmethod
def define_schema(cls) -> IO.Schema:
def define_schema(cls) -> io.Schema:
if len(cls.F_TYPES) == 0:
cls.fill_field_types()
return IO.Schema(
return io.Schema(
node_id=cls.__name__,
display_name=cls.display_name(),
category=CATEGORY,
@@ -206,7 +221,7 @@ class ContextNode(IO.ComfyNode):
return (context, kwargs,)
@classmethod
def execute(cls, context=None, **kwargs) -> IO.NodeOutput:
def execute(cls, context=None, **kwargs) -> io.NodeOutput:
if not context:
context = TTContext.create(**kwargs)
cls.handle(context, **kwargs)
+6 -6
View File
@@ -2,9 +2,9 @@
import folder_paths
from comfy import controlnet
from comfy_api.latest import io, ui, ComfyExtension
from comfy_api.latest import io, ui
from .nodes_image import SingleCondCFGGuider, get_image_files, load_image
from .nodes_image import AdaptiveCFGGuider, get_image_files, load_image
CATEGORY = "TenserTensor/ControlNet"
@@ -92,8 +92,8 @@ class TT_Flux2ApplyControlNetNode(io.ComfyNode):
kwargs.get("strength"),
kwargs.get("control_net"))
if not isinstance(guider, SingleCondCFGGuider):
guider = SingleCondCFGGuider.from_cfg_guider(guider)
if not isinstance(guider, AdaptiveCFGGuider):
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
if strength == 0.0:
return io.NodeOutput(guider)
@@ -141,8 +141,8 @@ class TT_Flux2ApplyControlNetAdvancedNode(io.ComfyNode):
kwargs.get("strength"),
kwargs.get("control_net"))
if not isinstance(guider, SingleCondCFGGuider):
guider = SingleCondCFGGuider.from_cfg_guider(guider)
if not isinstance(guider, AdaptiveCFGGuider):
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
if strength == 0.0:
return io.NodeOutput(guider)
-4
View File
@@ -193,10 +193,6 @@ class TT_JsonPreviewDebugNode(io.ComfyNode):
return io.NodeOutput(markdown, ui=ui.PreviewText(markdown))
NODES = [
# TT_StableAudioOpenModelsLoaderNode,
# TT_AudioLatentFactoryNode,
+8 -8
View File
@@ -7,17 +7,17 @@ from datetime import datetime
from pathlib import Path
from typing import Any
import folder_paths
import numpy
import torch
from PIL import Image, ImageOps, ImageSequence
from comfy import model_management, utils
from comfy_api.latest import ComfyExtension, io, ui
from node_helpers import conditioning_set_values, pillow
from spandrel import ModelLoader, ImageModelDescriptor
from .nodes_text_encoder import SingleCondCFGGuider
from .nodes_vae import vae_encode
import folder_paths
from comfy import model_management, utils
from comfy_api.latest import io, ui
from node_helpers import conditioning_set_values, pillow
from .nodes_text_encoder import AdaptiveCFGGuider
from .nodes_vae_d import vae_encode
CATEGORY = "TenserTensor/Image"
RESIZE_METHODS = ["nearest-exact", "bilinear", "area", "bicubic", "bislerp"]
@@ -457,8 +457,8 @@ class TT_GuiderImageReferenceNode(io.ComfyNode):
kwargs.get("dimension_step")
)
if not isinstance(guider, SingleCondCFGGuider):
guider = SingleCondCFGGuider.from_cfg_guider(guider)
if not isinstance(guider, AdaptiveCFGGuider):
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
resized = resize_image_to_megapixels(timage, resize_method, megapixels, dimension_step)
latent = vae_encode(resized, vae)
+5 -3
View File
@@ -176,7 +176,7 @@ def prepare_multimodal_latents(**kwargs):
kwargs.get("use_ref_frame"),
kwargs.get("seed"),
kwargs.get("noise_seed"),
kwargs.get("megapixels")
kwargs.get("megapixels"),
)
video_latent, width, height, total_length, fps = create_empty_video_latent(**kwargs)
@@ -196,6 +196,7 @@ def prepare_multimodal_latents(**kwargs):
seed,
noise_seed,
megapixels,
kwargs.get("length_sec"),
total_length,
fps,
width,
@@ -232,7 +233,7 @@ class TT_LtxvLatentFactoryNode(io.ComfyNode):
io.Combo.Input("aspect_ratio", options=CommonTypes.ASPECT_RATIOS),
io.Combo.Input("megapixels", options=CommonTypes.MEGAPIXELS),
io.Combo.Input("orientation", options=CommonTypes.ORIENTATIONS),
io.Int.Input("length_sec", default=5, min=1, max=20),
io.Float.Input("length_sec", default=5.0, min=0.1, max=120.0, step=0.1),
io.Combo.Input("frame_rate", options=CommonTypes.FRAME_RATES, default="24fps"),
io.Int.Input("batch_size", default=1, min=1, max=64),
],
@@ -244,8 +245,9 @@ class TT_LtxvLatentFactoryNode(io.ComfyNode):
io.Int.Output(display_name="SEED"),
io.Int.Output(display_name="NOISE_SEED"),
io.String.Output(display_name="MEGAPIXELS"),
io.String.Output(display_name="LENGTH_SEC"),
io.Int.Output(display_name="TOTAL_LENGTH"),
io.Int.Output(display_name="FPS"),
io.Int.Output(display_name="FRAME_RATE"),
io.Int.Output(display_name="WIDTH"),
io.Int.Output(display_name="HEIGHT"),
]
+1 -1
View File
@@ -9,7 +9,7 @@ from comfy.latent_formats import SDXL, Flux, Flux2, SD3
from comfy_api.latest import io, ComfyExtension
from .nodes_image import MEGAPIXELS, resize_image, rotate_image, flip_image
from .nodes_vae import vae_decode, vae_encode
from .nodes_vae_d import vae_decode, vae_encode
CATEGORY = "TenserTensor/Latent"
+9 -8
View File
@@ -2,12 +2,13 @@
import latent_preview
import torch
from comfy import sample, model_management, samplers
from comfy_api.latest import ComfyExtension, io
from comfy import sample, model_management, samplers
from comfy_api.latest import io
from .nodes_context import Context
from .nodes_latent import SCALE_FACTORS, SCALE_METHODS, scale_latent
from .nodes_text_encoder import SingleCondCFGGuider
from .nodes_latent_d import scale_latent
from .nodes_text_encoder import AdaptiveCFGGuider
from .utils import CommonTypes
CATEGORY = "TenserTensor/Sampling"
@@ -262,8 +263,8 @@ def execute_guided_sampling(**kwargs):
kwargs.get("preview_latent"),
)
if not isinstance(guider, SingleCondCFGGuider):
guider = SingleCondCFGGuider.from_cfg_guider(guider)
if not isinstance(guider, AdaptiveCFGGuider):
guider = AdaptiveCFGGuider.from_cfg_guider(guider)
model_patcher = guider.model_patcher
latent_dict = latent.copy()
@@ -343,8 +344,8 @@ class TT_GuidedUpscaleKSamplerNode(io.ComfyNode):
io.Sampler.Input("sampler"),
io.Noise.Input("random_noise"),
io.Boolean.Input("scale_latent", default=False, label_on="Scale", label_off="Skip"),
io.Combo.Input("scale_factor", options=SCALE_FACTORS, default="1x", advanced=True),
io.Combo.Input("scale_method", options=SCALE_METHODS, default="bicubic", advanced=True),
io.Combo.Input("scale_factor", options=CommonTypes.SCALE_FACTORS, default="1x", advanced=True),
io.Combo.Input("scale_method", options=CommonTypes.SCALE_METHODS, default="bicubic", advanced=True),
io.Boolean.Input("preview_latent", default=False, label_on="Show Preview", label_off="Only Sample"),
],
outputs=[
+4 -152
View File
@@ -1,155 +1,10 @@
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from comfy_api.latest import io, ComfyExtension
from .nodes_context import Context
CATEGORY = "TenserTensor/VAE"
TILE_SIZE, OVERLAP = 512, 64
from comfy_api.latest import io
def vae_decode(latent, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
samples = latent["samples"]
compression = vae.spacial_compression_decode()
image = vae.decode_tiled(
samples,
tile_x=tile_width // compression,
tile_y=tile_height // compression,
overlap=overlap // compression
)
return image
def vae_encode(image, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
samples = vae.encode_tiled(
image,
tile_x=tile_width,
tile_y=tile_height,
overlap=overlap
)
return {"samples": samples}
class TT_VaeDecodeTiledNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeDecodeTiledNode",
display_name="TT Vae Decode (Tiled)",
category=CATEGORY,
description="",
inputs=[
io.Vae.Input("vae"),
io.Latent.Input("latent"),
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
],
outputs=[
io.Image.Output("IMAGE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
image = vae_decode(**kwargs)
return io.NodeOutput(image)
class TT_VaeDecodeContextNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeDecodeContextNode",
display_name="TT Vae Decode (Context)",
category=CATEGORY,
description="",
inputs=[
Context.Input("context"),
],
outputs=[
Context.Output("CONTEXT"),
io.Image.Output("IMAGE"),
]
)
@classmethod
def execute(cls, context) -> io.NodeOutput:
vae = context.get("vae")
if vae is None:
raise ValueError("ERROR: VAE is required for decode")
latent = context.get("latent")
if latent is None:
raise ValueError("ERROR: Latent image is required for decode")
image = vae_decode(latent, vae)
return io.NodeOutput(context, image)
class TT_VaeEncodeTiledNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeEncodeTiledNode",
display_name="TT Vae Encode (Tiled)",
category=CATEGORY,
description="",
inputs=[
io.Image.Input("image"),
io.Vae.Input("vae"),
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
],
outputs=[
io.Latent.Output("LATENT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
latent = vae_encode(**kwargs)
return io.NodeOutput(latent)
class TT_VaeEncodeContextNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeEncodeContextNode",
display_name="TT Vae Encode (Context)",
category=CATEGORY,
description="",
inputs=[
Context.Input("context"),
],
outputs=[
Context.Output("CONTEXT"),
io.Latent.Output("LATENT"),
]
)
@classmethod
def execute(cls, context) -> io.NodeOutput:
vae = context.get("vae")
if vae is None:
raise ValueError("ERROR: VAE is required for encode")
image = context.get("image")
if image is None:
raise ValueError("ERROR: Pixel image is required for encode")
latent = vae_encode(image, vae)
return io.NodeOutput(context, latent)
class TT_GuidedUpscaleKSamplerNode(io.ComfyNode):
pass
# ==============================================================================
@@ -157,8 +12,5 @@ class TT_VaeEncodeContextNode(io.ComfyNode):
# ==============================================================================
NODES = [
TT_VaeDecodeTiledNode,
TT_VaeDecodeContextNode,
TT_VaeEncodeTiledNode,
TT_VaeEncodeContextNode,
]
+164
View File
@@ -0,0 +1,164 @@
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
from comfy_api.latest import io, ComfyExtension
from .nodes_context import Context
CATEGORY = "TenserTensor/VAE"
TILE_SIZE, OVERLAP = 512, 64
def vae_decode(latent, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
samples = latent["samples"]
compression = vae.spacial_compression_decode()
image = vae.decode_tiled(
samples,
tile_x=tile_width // compression,
tile_y=tile_height // compression,
overlap=overlap // compression
)
return image
def vae_encode(image, vae, tile_width=TILE_SIZE, tile_height=TILE_SIZE, overlap=OVERLAP):
samples = vae.encode_tiled(
image,
tile_x=tile_width,
tile_y=tile_height,
overlap=overlap
)
return {"samples": samples}
class TT_VaeDecodeTiledNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeDecodeTiledNode",
display_name="TT Vae Decode (Tiled)",
category=CATEGORY,
description="",
inputs=[
io.Vae.Input("vae"),
io.Latent.Input("latent"),
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
],
outputs=[
io.Image.Output("IMAGE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
image = vae_decode(**kwargs)
return io.NodeOutput(image)
class TT_VaeDecodeContextNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeDecodeContextNode",
display_name="TT Vae Decode (Context)",
category=CATEGORY,
description="",
inputs=[
Context.Input("context"),
],
outputs=[
Context.Output("CONTEXT"),
io.Image.Output("IMAGE"),
]
)
@classmethod
def execute(cls, context) -> io.NodeOutput:
vae = context.get("vae")
if vae is None:
raise ValueError("ERROR: VAE is required for decode")
latent = context.get("latent")
if latent is None:
raise ValueError("ERROR: Latent image is required for decode")
image = vae_decode(latent, vae)
return io.NodeOutput(context, image)
class TT_VaeEncodeTiledNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeEncodeTiledNode",
display_name="TT Vae Encode (Tiled)",
category=CATEGORY,
description="",
inputs=[
io.Image.Input("image"),
io.Vae.Input("vae"),
io.Int.Input("tile_width", default=512, min=64, max=4096, step=64),
io.Int.Input("tile_height", default=512, min=64, max=4096, step=64),
io.Int.Input("overlap", default=64, min=8, max=256, step=8),
],
outputs=[
io.Latent.Output("LATENT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
latent = vae_encode(**kwargs)
return io.NodeOutput(latent)
class TT_VaeEncodeContextNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_VaeEncodeContextNode",
display_name="TT Vae Encode (Context)",
category=CATEGORY,
description="",
inputs=[
Context.Input("context"),
],
outputs=[
Context.Output("CONTEXT"),
io.Latent.Output("LATENT"),
]
)
@classmethod
def execute(cls, context) -> io.NodeOutput:
vae = context.get("vae")
if vae is None:
raise ValueError("ERROR: VAE is required for encode")
image = context.get("image")
if image is None:
raise ValueError("ERROR: Pixel image is required for encode")
latent = vae_encode(image, vae)
return io.NodeOutput(context, latent)
# ==============================================================================
# V3 entrypoint — registers context nodes with ComfyUI
# ==============================================================================
NODES = [
TT_VaeDecodeTiledNode,
TT_VaeDecodeContextNode,
TT_VaeEncodeTiledNode,
TT_VaeEncodeContextNode,
]
+85 -516
View File
@@ -1,556 +1,132 @@
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import dataclasses
import math
from dataclasses import dataclass
import torch
from comfy.samplers import KSampler, sampler_object, Sampler
from comfy_api.latest import io, ComfyExtension
from nodes import MAX_RESOLUTION
from comfy.samplers import KSampler, sampler_object
from comfy_api.latest import io
from .nodes_workflow_d import WorkflowSettings, TTWorkflowSettings
from .utils import CommonTypes
CATEGORY = "TenserTensor/Workflow"
@dataclass
class TTWorkflowSettings():
ascore_negative: float | None = None
ascore_positive: float | None = None
cfg: int | None = None
clip_g_negative: str | None = None
clip_l_negative: str | None = None
clip_g_positive: str | None = None
clip_l_positive: str | None = None
guidance: float | None = None
height: int | None = None
lora_triggers: str | None = None
prompt: str | None = None
sampler: Sampler | None = None
sampler_name: str | None = None
scheduler: str | None = None
seed: int | None = None
sigmas: torch.Tensor | None = None
steps: int | None = None
target_height: int | None = None
target_width: int | None = None
t5xxl_negative: str | None = None
t5xxl_positive: str | None = None
width: int | None = None
# ==============================================================================
# Helper functions — pipeline utilities and loaders
# ==============================================================================
@classmethod
def create(cls, **kwargs) -> TTWorkflowSettings:
wf = cls()
for field in dataclasses.fields(wf):
wf.set_attr(field.name, kwargs.get(field.name, None))
def ltxv_sigmas(**kwargs):
latent, scheduler_config = (
kwargs.get("video_latent_opt"),
kwargs.get("scheduler_config"),
)
return wf
base_shift, max_shift = 0.95, 2.05
if scheduler_config.get("scheduler_config") == "Manual":
base_shift = scheduler_config.get("schedule_base_shift", 0.95),
max_shift = scheduler_config.get("schedule_max_shift", 2.05)
def update_attrs(self, **kwargs) -> TTWorkflowSettings:
for key in kwargs.keys():
if hasattr(self, key):
val = kwargs.get(key)
if val is not None:
self.set_attr(key, val)
token_count = math.prod(latent["samples"].shape[2:]) if latent else 4096
sigmas = torch.linspace(1.0, 0.0, kwargs.get("steps") + 1)
return self
MIN_STEPS, MAX_STEPS = 1024, 4096
def set_attr(self, key, attr) -> None:
if hasattr(self, key):
setattr(self, key, attr)
slope = (max_shift - base_shift) / (MAX_STEPS - MIN_STEPS)
intercept = base_shift - slope * MIN_STEPS
sigma_shift = (token_count) * slope + intercept
sigma = 1.0
timesteps = math.exp(sigma_shift) / (math.exp(sigma_shift) + (1 / sigmas - 1) ** sigma)
sigmas = torch.where(sigmas != 0, timesteps, 0)
def get_attr(self, key, default=None) -> None:
if hasattr(self, key):
val = getattr(self, key)
return val if val is not None else default
stretch_sigmas = kwargs.get("stretch_sigmas")
if stretch_sigmas.get("stretch_sigmas") == "In Range":
non_zero_mask = sigmas != 0
non_zero_sigmas = sigmas[non_zero_mask]
one_minus_z = 1.0 - non_zero_sigmas
scale_factor = one_minus_z[-1] / (1.0 - stretch_sigmas.get("sigmas_terminal", 0.1))
stretched = 1.0 - (one_minus_z / scale_factor)
sigmas[non_zero_mask] = stretched
return sigmas
@io.comfytype(io_type="TT_WORKFLOW_CONFIG")
class WorkflowSettings:
Type = TTWorkflowSettings
class Input(io.Input):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class Output(io.Output):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class TT_SdxlWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_SdxlWorkflowSettingsNode",
display_name="TT SDXL Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
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=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("ASCORE_POSITIVE"),
io.Float.Output("ASCORE_NEGATIVE"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_SdxlWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_SdxlWorkflowSettingsAdvancedNode",
display_name="TT SDXL Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
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.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("ASCORE_POSITIVE"),
io.Float.Output("ASCORE_NEGATIVE"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("CLIP_G_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("CLIP_G_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_FluxWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_FluxWorkflowSettingsNode",
display_name="TT FLUX Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_FluxWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_FluxWorkflowSettingsAdvancedNode",
display_name="TT FLUX Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("T5XXL_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("T5XXL_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
def compute_empirical_mu(image_seq_len, num_steps):
slope_high_res, intercept_high_res = 8.73809524e-05, 1.89833333
slope_low_res, intercept_low_res = 0.00016927, 0.45666666
HIGH_RES_THRESHOLD = 4300
MIN_STEPS, MAX_STEPS = 10, 200
mu_at_max_steps = slope_low_res * image_seq_len + intercept_low_res
if image_seq_len > HIGH_RES_THRESHOLD:
return float(mu_at_max_steps)
mu_at_min_steps = slope_high_res * image_seq_len + intercept_high_res
slope = (mu_at_max_steps - mu_at_min_steps) / (MAX_STEPS - MIN_STEPS)
intercept = mu_at_max_steps - MAX_STEPS * slope
mu = slope * num_steps + intercept
return float(mu)
def generalized_time_snr_shift(t, mu, sigma):
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
def get_schedule(steps, width, height):
image_seq_len = (width * height / (16 * 16))
mu = compute_empirical_mu(image_seq_len, steps)
timesteps = torch.linspace(1, 0, steps + 1)
timesteps = generalized_time_snr_shift(timesteps, mu, 1.0)
return timesteps
def build_sampler_sigmas(sampler_name, steps, width, height):
sampler = sampler_object(sampler_name)
sigmas = get_schedule(steps, width, height)
def build_ltxv_sampler_sigmas(**kwargs):
sampler = sampler_object(kwargs.get("sampler_name"))
sigmas = ltxv_sigmas(**kwargs)
return sampler, sigmas
class TT_Flux2WorkflowSettingsNode(io.ComfyNode):
# ==============================================================================
# Node classes — ComfyUI node definitions
# ==============================================================================
class TT_LtxvWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Flux2WorkflowSettingsNode",
display_name="TT FLUX2 Workflow Settings",
node_id="TT_LtxvWorkflowSettingsNode",
display_name="TT LTXV Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Latent.Input("video_latent_opt", optional=True),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.DynamicCombo.Input("stretch_sigmas", options=[
io.DynamicCombo.Option("In Range", [
io.Float.Input("sigmas_terminal", default=0.1, min=0.0, max=0.99, step=0.01),
]),
io.DynamicCombo.Option("Bypass", []),
]),
io.DynamicCombo.Input("scheduler_config", options=[
io.DynamicCombo.Option("Defaults", []),
io.DynamicCombo.Option("Manual", [
io.Float.Input("schedule_base_shift", default=0.95, min=0.0, max=10.0, step=0.01),
io.Float.Input("schedule_max_shift", default=2.05, min=0.0, max=10.0, step=0.01),
]),
]),
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("noise_seed", default=0, min=0, max=0xffffffffffffffff, control_after_generate=True),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS)
io.Float.Input("length_sec", default=5.0, min=0.1, max=120.0, step=0.1),
io.Combo.Input("frame_rate", options=CommonTypes.FRAME_RATES, default="24fps"),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("NOISE_SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Float.Output("GUIDANCE"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.String.Output("LENGTH_SEC"),
io.String.Output("FRAME_RATE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
kwargs.get("sampler_name"),
workflow_config = TTWorkflowSettings.create(**kwargs)
sampler, sigmas = build_ltxv_sampler_sigmas(**kwargs)
workflow_config.set_attr("sampler", sampler)
workflow_config.set_attr("sigmas", sigmas)
args = (
workflow_config,
sampler,
sigmas,
kwargs.get("seed"),
kwargs.get("noise_seed"),
kwargs.get("steps"),
kwargs.get("width"),
kwargs.get("height"),
kwargs.get("cfg"),
kwargs.get("length_sec"),
kwargs.get("frame_rate"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"guidance": kwargs.get("guidance"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
}
return io.NodeOutput(*args.values())
class TT_Flux2WorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Flux2WorkflowSettingsAdvancedNode",
display_name="TT FLUX2 Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.String.Input("prompt", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.String.Output("PROMPT"),
io.String.Output("LORA_TRIGGERS"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("width"), kwargs.get("height"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"prompt": kwargs.get("prompt"),
"lora_triggers": kwargs.get("lora_triggers"),
"guidance": kwargs.get("guidance"),
}
return io.NodeOutput(*args.values())
def sd3_shift_sigma(sigma, schedule_shift):
return schedule_shift * sigma / (1 + (schedule_shift - 1) * sigma)
def build_sd3_sampler_sigmas(sampler_name, steps, schedule_shift=3.0):
sampler = sampler_object(sampler_name)
timesteps = torch.linspace(1, 0, steps + 1)
sigmas = sd3_shift_sigma(timesteps, schedule_shift)
return sampler, sigmas
class TT_Sd35GgufWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Sd35GgufWorkflowSettingsNode",
display_name="TT SD3.5 GGUF Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"target_width": kwargs.get("target_width"),
"target_height": kwargs.get("target_height"),
}
return io.NodeOutput(*args.values())
class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Sd35GgufWorkflowSettingsAdvancedNode",
display_name="TT SD3.5 GGUF Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("CLIP_G_POSITIVE"),
io.String.Output("T5XXL_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("CLIP_G_NEGATIVE"),
io.String.Output("T5XXL_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"target_width": kwargs.get("target_width"),
"target_height": kwargs.get("target_height"),
"clip_l_positive": kwargs.get("clip_l_positive"),
"clip_g_positive": kwargs.get("clip_g_positive"),
"t5xxl_positive": kwargs.get("t5xxl_positive"),
"clip_l_negative": kwargs.get("clip_l_negative"),
"clip_g_negative": kwargs.get("clip_g_negative"),
"t5xxl_negative": kwargs.get("t5xxl_negative"),
"lora_triggers": kwargs.get("lora_triggers"),
}
return io.NodeOutput(*args.values())
return io.NodeOutput(workflow_config, *args)
# ==============================================================================
@@ -558,12 +134,5 @@ class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
# ==============================================================================
NODES = [
TT_SdxlWorkflowSettingsNode,
TT_SdxlWorkflowSettingsAdvancedNode,
TT_FluxWorkflowSettingsNode,
TT_FluxWorkflowSettingsAdvancedNode,
TT_Flux2WorkflowSettingsNode,
TT_Flux2WorkflowSettingsAdvancedNode,
TT_Sd35GgufWorkflowSettingsNode,
TT_Sd35GgufWorkflowSettingsAdvancedNode,
TT_LtxvWorkflowSettingsNode,
]
+575
View File
@@ -0,0 +1,575 @@
# (c) TenserTensor <tenser.tensor@proton.me> || Apache-2.0 (apache.org/licenses/LICENSE-2.0)
import dataclasses
import math
from dataclasses import dataclass
import torch
from comfy.samplers import KSampler, sampler_object, Sampler
from comfy_api.latest import io
from nodes import MAX_RESOLUTION
CATEGORY = "TenserTensor/Workflow"
@dataclass
class TTWorkflowSettings():
ascore_negative: float | None = None
ascore_positive: float | None = None
cfg: int | None = None
clip_g_negative: str | None = None
clip_g_positive: str | None = None
clip_l_negative: str | None = None
clip_l_positive: str | None = None
frame_rate: str | None = None
guidance: float | None = None
height: int | None = None
length_sec: float | None = None
lora_triggers: str | None = None
negative_prompt: str | None = None
noise_seed: int | None = None
positive_prompt: str | None = None
prompt: str | None = None
sampler: Sampler | None = None
sampler_name: str | None = None
scheduler: str | None = None
seed: int | None = None
sigmas: torch.Tensor | None = None
steps: int | None = None
target_height: int | None = None
target_width: int | None = None
t5xxl_negative: str | None = None
t5xxl_positive: str | None = None
width: int | None = None
@classmethod
def create(cls, **kwargs) -> TTWorkflowSettings:
wf = cls()
for field in dataclasses.fields(wf):
wf.set_attr(field.name, kwargs.get(field.name, None))
return wf
def update_attrs(self, **kwargs) -> TTWorkflowSettings:
for key in kwargs.keys():
if hasattr(self, key):
val = kwargs.get(key)
if val is not None:
self.set_attr(key, val)
return self
def set_attr(self, key, attr) -> None:
if hasattr(self, key):
setattr(self, key, attr)
def get_attr(self, key, default=None) -> None:
if hasattr(self, key):
val = getattr(self, key)
return val if val is not None else default
@io.comfytype(io_type="TT_WORKFLOW_CONFIG")
class WorkflowSettings:
Type = TTWorkflowSettings
class Input(io.Input):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class Output(io.Output):
def __init__(self, id: str, **kwargs):
super().__init__(id, **kwargs)
class TT_SdxlWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_SdxlWorkflowSettingsNode",
display_name="TT SDXL Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
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=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("ASCORE_POSITIVE"),
io.Float.Output("ASCORE_NEGATIVE"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_SdxlWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_SdxlWorkflowSettingsAdvancedNode",
display_name="TT SDXL Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
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.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("ASCORE_POSITIVE"),
io.Float.Output("ASCORE_NEGATIVE"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("CLIP_G_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("CLIP_G_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_FluxWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_FluxWorkflowSettingsNode",
display_name="TT FLUX Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
class TT_FluxWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_FluxWorkflowSettingsAdvancedNode",
display_name="TT FLUX Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Combo.Input("scheduler", options=KSampler.SCHEDULERS),
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.String.Output("SAMPLER_NAME"),
io.String.Output("SCHEDULER"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("T5XXL_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("T5XXL_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {"workflow_config": workflow_config, **kwargs}
return io.NodeOutput(*args.values())
def compute_empirical_mu(image_seq_len, num_steps):
slope_high_res, intercept_high_res = 8.73809524e-05, 1.89833333
slope_low_res, intercept_low_res = 0.00016927, 0.45666666
HIGH_RES_THRESHOLD = 4300
MIN_STEPS, MAX_STEPS = 10, 200
mu_at_max_steps = slope_low_res * image_seq_len + intercept_low_res
if image_seq_len > HIGH_RES_THRESHOLD:
return float(mu_at_max_steps)
mu_at_min_steps = slope_high_res * image_seq_len + intercept_high_res
slope = (mu_at_max_steps - mu_at_min_steps) / (MAX_STEPS - MIN_STEPS)
intercept = mu_at_max_steps - MAX_STEPS * slope
mu = slope * num_steps + intercept
return float(mu)
def generalized_time_snr_shift(t, mu, sigma):
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
def get_schedule(steps, width, height):
image_seq_len = (width * height / (16 * 16))
mu = compute_empirical_mu(image_seq_len, steps)
timesteps = torch.linspace(1, 0, steps + 1)
timesteps = generalized_time_snr_shift(timesteps, mu, 1.0)
return timesteps
def build_sampler_sigmas(sampler_name, steps, width, height):
sampler = sampler_object(sampler_name)
sigmas = get_schedule(steps, width, height)
return sampler, sigmas
class TT_Flux2WorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Flux2WorkflowSettingsNode",
display_name="TT FLUX2 Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS)
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Float.Output("GUIDANCE"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
kwargs.get("sampler_name"),
kwargs.get("steps"),
kwargs.get("width"),
kwargs.get("height"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"guidance": kwargs.get("guidance"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
}
return io.NodeOutput(*args.values())
class TT_Flux2WorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Flux2WorkflowSettingsAdvancedNode",
display_name="TT FLUX2 Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=3.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.String.Input("prompt", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True),
io.Float.Input("guidance", default=3.0, min=1.0, max=10.0, step=0.1),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.String.Output("PROMPT"),
io.String.Output("LORA_TRIGGERS"),
io.Float.Output("GUIDANCE"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("width"), kwargs.get("height"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"prompt": kwargs.get("prompt"),
"lora_triggers": kwargs.get("lora_triggers"),
"guidance": kwargs.get("guidance"),
}
return io.NodeOutput(*args.values())
def sd3_shift_sigma(sigma, schedule_shift):
return schedule_shift * sigma / (1 + (schedule_shift - 1) * sigma)
def build_sd3_sampler_sigmas(sampler_name, steps, schedule_shift=3.0):
sampler = sampler_object(sampler_name)
timesteps = torch.linspace(1, 0, steps + 1)
sigmas = sd3_shift_sigma(timesteps, schedule_shift)
return sampler, sigmas
class TT_Sd35GgufWorkflowSettingsNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Sd35GgufWorkflowSettingsNode",
display_name="TT SD3.5 GGUF Workflow Settings",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"target_width": kwargs.get("target_width"),
"target_height": kwargs.get("target_height"),
}
return io.NodeOutput(*args.values())
class TT_Sd35GgufWorkflowSettingsAdvancedNode(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="TT_Sd35GgufWorkflowSettingsAdvancedNode",
display_name="TT SD3.5 GGUF Workflow Settings (Advanced)",
category=CATEGORY,
description="",
inputs=[
io.Int.Input("seed", default=0, min=0, max=0xffffffffffffffff),
io.Int.Input("steps", default=30, min=1, max=10_000),
io.Float.Input("cfg", default=5.0, min=0.0, max=100.0, step=0.1),
io.Combo.Input("sampler_name", options=KSampler.SAMPLERS),
io.Float.Input("schedule_shift", default=3.0, min=0.0, max=20.0, step=0.1, advanced=True),
io.String.Input("clip_l_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_positive", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_positive", multiline=True, dynamic_prompts=True),
io.String.Input("clip_l_negative", multiline=True, dynamic_prompts=True),
io.String.Input("clip_g_negative", multiline=True, dynamic_prompts=True),
io.String.Input("t5xxl_negative", multiline=True, dynamic_prompts=True),
io.String.Input("lora_triggers", multiline=True, dynamic_prompts=True, advanced=True),
io.Int.Input("width", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("height", default=1024, min=16, max=MAX_RESOLUTION, step=8),
io.Int.Input("target_width", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
io.Int.Input("target_height", default=1024, min=16, max=MAX_RESOLUTION, step=8, advanced=True),
],
outputs=[
WorkflowSettings.Output("WORKFLOW_CONFIG"),
io.Sampler.Output("SAMPLER"),
io.Sigmas.Output("SIGMAS"),
io.Int.Output("SEED"),
io.Int.Output("STEPS"),
io.Float.Output("CFG"),
io.Int.Output("WIDTH"),
io.Int.Output("HEIGHT"),
io.Int.Output("TARGET_WIDTH"),
io.Int.Output("TARGET_HEIGHT"),
io.String.Output("CLIP_L_POSITIVE"),
io.String.Output("CLIP_G_POSITIVE"),
io.String.Output("T5XXL_POSITIVE"),
io.String.Output("CLIP_L_NEGATIVE"),
io.String.Output("CLIP_G_NEGATIVE"),
io.String.Output("T5XXL_NEGATIVE"),
io.String.Output("LORA_TRIGGERS"),
]
)
@classmethod
def execute(cls, **kwargs) -> io.NodeOutput:
kwargs["sampler"], kwargs["sigmas"] = build_sd3_sampler_sigmas(
kwargs.get("sampler_name"), kwargs.get("steps"), kwargs.get("schedule_shift"),
)
workflow_config = TTWorkflowSettings.create(**kwargs)
args = {
"workflow_config": workflow_config,
"sampler": kwargs["sampler"],
"sigmas": kwargs["sigmas"],
"seed": kwargs.get("seed"),
"steps": kwargs.get("steps"),
"cfg": kwargs.get("cfg"),
"width": kwargs.get("width"),
"height": kwargs.get("height"),
"target_width": kwargs.get("target_width"),
"target_height": kwargs.get("target_height"),
"clip_l_positive": kwargs.get("clip_l_positive"),
"clip_g_positive": kwargs.get("clip_g_positive"),
"t5xxl_positive": kwargs.get("t5xxl_positive"),
"clip_l_negative": kwargs.get("clip_l_negative"),
"clip_g_negative": kwargs.get("clip_g_negative"),
"t5xxl_negative": kwargs.get("t5xxl_negative"),
"lora_triggers": kwargs.get("lora_triggers"),
}
return io.NodeOutput(*args.values())
# ==============================================================================
# V3 entrypoint — registers context nodes with ComfyUI
# ==============================================================================
NODES = [
TT_SdxlWorkflowSettingsNode,
TT_SdxlWorkflowSettingsAdvancedNode,
TT_FluxWorkflowSettingsNode,
TT_FluxWorkflowSettingsAdvancedNode,
TT_Flux2WorkflowSettingsNode,
TT_Flux2WorkflowSettingsAdvancedNode,
TT_Sd35GgufWorkflowSettingsNode,
TT_Sd35GgufWorkflowSettingsAdvancedNode,
]
+1 -1
View File
@@ -13,7 +13,7 @@ description = """A collection of custom nodes for ComfyUI that streamline common
— VAE encoders and decoders: tiled and context-based
— Image tools: load, resize, upscale, preview, save, ControlNet reference
— Post-production: brightness, contrast, LUT color grading, film grain, upscaler"""
version = "1.7.8"
version = "2.0.0"
license = { file = "LICENSE" }
dependencies = ["gguf", "kornia"]