| import math
|
| import torch
|
| from .utils import AnyType
|
| import comfy.model_management
|
| from nodes import MAX_RESOLUTION
|
| import time
|
|
|
| any = AnyType("*")
|
|
|
| class SimpleMathFloat:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("FLOAT", { "default": 0.0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 0.05 }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("FLOAT", )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value):
|
| return (float(value), )
|
|
|
| class SimpleMathPercent:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("FLOAT", { "default": 0.0, "min": 0, "max": 1, "step": 0.05 }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("FLOAT", )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value):
|
| return (float(value), )
|
|
|
| class SimpleMathInt:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("INT", { "default": 0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 1 }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("INT",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value):
|
| return (int(value), )
|
|
|
| class SimpleMathSlider:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("FLOAT", { "display": "slider", "default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001 }),
|
| "min": ("FLOAT", { "default": 0.0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 0.001 }),
|
| "max": ("FLOAT", { "default": 1.0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 0.001 }),
|
| "rounding": ("INT", { "default": 0, "min": 0, "max": 10, "step": 1 }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("FLOAT", "INT",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value, min, max, rounding):
|
| value = min + value * (max - min)
|
|
|
| if rounding > 0:
|
| value = round(value, rounding)
|
|
|
| return (value, int(value), )
|
|
|
| class SimpleMathSliderLowRes:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("INT", { "display": "slider", "default": 5, "min": 0, "max": 10, "step": 1 }),
|
| "min": ("FLOAT", { "default": 0.0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 0.001 }),
|
| "max": ("FLOAT", { "default": 1.0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 0.001 }),
|
| "rounding": ("INT", { "default": 0, "min": 0, "max": 10, "step": 1 }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("FLOAT", "INT",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value, min, max, rounding):
|
| value = 0.1 * value
|
| value = min + value * (max - min)
|
| if rounding > 0:
|
| value = round(value, rounding)
|
|
|
| return (value, )
|
|
|
| class SimpleMathBoolean:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": ("BOOLEAN", { "default": False }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("BOOLEAN",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value):
|
| return (value, int(value), )
|
|
|
| class SimpleMath:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "optional": {
|
| "a": (any, { "default": 0.0 }),
|
| "b": (any, { "default": 0.0 }),
|
| "c": (any, { "default": 0.0 }),
|
| },
|
| "required": {
|
| "value": ("STRING", { "multiline": False, "default": "" }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("INT", "FLOAT", )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value, a = 0.0, b = 0.0, c = 0.0, d = 0.0):
|
| import ast
|
| import operator as op
|
|
|
| h, w = 0.0, 0.0
|
| if hasattr(a, 'shape'):
|
| a = list(a.shape)
|
| if hasattr(b, 'shape'):
|
| b = list(b.shape)
|
| if hasattr(c, 'shape'):
|
| c = list(c.shape)
|
| if hasattr(d, 'shape'):
|
| d = list(d.shape)
|
|
|
| if isinstance(a, str):
|
| a = float(a)
|
| if isinstance(b, str):
|
| b = float(b)
|
| if isinstance(c, str):
|
| c = float(c)
|
| if isinstance(d, str):
|
| d = float(d)
|
|
|
| operators = {
|
| ast.Add: op.add,
|
| ast.Sub: op.sub,
|
| ast.Mult: op.mul,
|
| ast.Div: op.truediv,
|
| ast.FloorDiv: op.floordiv,
|
| ast.Pow: op.pow,
|
|
|
|
|
|
|
| ast.USub: op.neg,
|
| ast.Mod: op.mod,
|
| ast.Eq: op.eq,
|
| ast.NotEq: op.ne,
|
| ast.Lt: op.lt,
|
| ast.LtE: op.le,
|
| ast.Gt: op.gt,
|
| ast.GtE: op.ge,
|
| ast.And: lambda x, y: x and y,
|
| ast.Or: lambda x, y: x or y,
|
| ast.Not: op.not_
|
| }
|
|
|
| op_functions = {
|
| 'min': min,
|
| 'max': max,
|
| 'round': round,
|
| 'sum': sum,
|
| 'len': len,
|
| }
|
|
|
| def eval_(node):
|
| if isinstance(node, ast.Num):
|
| return node.n
|
| elif isinstance(node, ast.Name):
|
| if node.id == "a":
|
| return a
|
| if node.id == "b":
|
| return b
|
| if node.id == "c":
|
| return c
|
| if node.id == "d":
|
| return d
|
| elif isinstance(node, ast.BinOp):
|
| return operators[type(node.op)](eval_(node.left), eval_(node.right))
|
| elif isinstance(node, ast.UnaryOp):
|
| return operators[type(node.op)](eval_(node.operand))
|
| elif isinstance(node, ast.Compare):
|
| left = eval_(node.left)
|
| for op, comparator in zip(node.ops, node.comparators):
|
| if not operators[type(op)](left, eval_(comparator)):
|
| return 0
|
| return 1
|
| elif isinstance(node, ast.BoolOp):
|
| values = [eval_(value) for value in node.values]
|
| return operators[type(node.op)](*values)
|
| elif isinstance(node, ast.Call):
|
| if node.func.id in op_functions:
|
| args =[eval_(arg) for arg in node.args]
|
| return op_functions[node.func.id](*args)
|
| elif isinstance(node, ast.Subscript):
|
| value = eval_(node.value)
|
| if isinstance(node.slice, ast.Constant):
|
| return value[node.slice.value]
|
| else:
|
| return 0
|
| else:
|
| return 0
|
|
|
| result = eval_(ast.parse(value, mode='eval').body)
|
|
|
| if math.isnan(result):
|
| result = 0.0
|
|
|
| return (round(result), result, )
|
|
|
| class SimpleMathDual:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "optional": {
|
| "a": (any, { "default": 0.0 }),
|
| "b": (any, { "default": 0.0 }),
|
| "c": (any, { "default": 0.0 }),
|
| "d": (any, { "default": 0.0 }),
|
| },
|
| "required": {
|
| "value_1": ("STRING", { "multiline": False, "default": "" }),
|
| "value_2": ("STRING", { "multiline": False, "default": "" }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("INT", "FLOAT", "INT", "FLOAT", )
|
| RETURN_NAMES = ("int_1", "float_1", "int_2", "float_2" )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, value_1, value_2, a = 0.0, b = 0.0, c = 0.0, d = 0.0):
|
| return SimpleMath().execute(value_1, a, b, c, d) + SimpleMath().execute(value_2, a, b, c, d)
|
|
|
| class SimpleMathCondition:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "optional": {
|
| "a": (any, { "default": 0.0 }),
|
| "b": (any, { "default": 0.0 }),
|
| "c": (any, { "default": 0.0 }),
|
| },
|
| "required": {
|
| "evaluate": (any, {"default": 0}),
|
| "on_true": ("STRING", { "multiline": False, "default": "" }),
|
| "on_false": ("STRING", { "multiline": False, "default": "" }),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("INT", "FLOAT", )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, evaluate, on_true, on_false, a = 0.0, b = 0.0, c = 0.0):
|
| return SimpleMath().execute(on_true if evaluate else on_false, a, b, c)
|
|
|
| class SimpleCondition:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(cls):
|
| return {
|
| "required": {
|
| "evaluate": (any, {"default": 0}),
|
| "on_true": (any, {"default": 0}),
|
| },
|
| "optional": {
|
| "on_false": (any, {"default": None}),
|
| },
|
| }
|
|
|
| RETURN_TYPES = (any,)
|
| RETURN_NAMES = ("result",)
|
| FUNCTION = "execute"
|
|
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, evaluate, on_true, on_false=None):
|
| from comfy_execution.graph import ExecutionBlocker
|
| if not evaluate:
|
| return (on_false if on_false is not None else ExecutionBlocker(None),)
|
|
|
| return (on_true,)
|
|
|
| class SimpleComparison:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(cls):
|
| return {
|
| "required": {
|
| "a": (any, {"default": 0}),
|
| "b": (any, {"default": 0}),
|
| "comparison": (["==", "!=", "<", "<=", ">", ">="],),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("BOOLEAN",)
|
| FUNCTION = "execute"
|
|
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, a, b, comparison):
|
| if comparison == "==":
|
| return (a == b,)
|
| elif comparison == "!=":
|
| return (a != b,)
|
| elif comparison == "<":
|
| return (a < b,)
|
| elif comparison == "<=":
|
| return (a <= b,)
|
| elif comparison == ">":
|
| return (a > b,)
|
| elif comparison == ">=":
|
| return (a >= b,)
|
|
|
| class ConsoleDebug:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "value": (any, {}),
|
| },
|
| "optional": {
|
| "prefix": ("STRING", { "multiline": False, "default": "Value:" })
|
| }
|
| }
|
|
|
| RETURN_TYPES = ()
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
| OUTPUT_NODE = True
|
|
|
| def execute(self, value, prefix):
|
| print(f"\033[96m{prefix} {value}\033[0m")
|
|
|
| return (None,)
|
|
|
| class DebugTensorShape:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "tensor": (any, {}),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ()
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
| OUTPUT_NODE = True
|
|
|
| def execute(self, tensor):
|
| shapes = []
|
| def tensorShape(tensor):
|
| if isinstance(tensor, dict):
|
| for k in tensor:
|
| tensorShape(tensor[k])
|
| elif isinstance(tensor, list):
|
| for i in range(len(tensor)):
|
| tensorShape(tensor[i])
|
| elif hasattr(tensor, 'shape'):
|
| shapes.append(list(tensor.shape))
|
|
|
| tensorShape(tensor)
|
|
|
| print(f"\033[96mShapes found: {shapes}\033[0m")
|
|
|
| return (None,)
|
|
|
| class BatchCount:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "batch": (any, {}),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("INT",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, batch):
|
| count = 0
|
| if hasattr(batch, 'shape'):
|
| count = batch.shape[0]
|
| elif isinstance(batch, dict) and 'samples' in batch:
|
| count = batch['samples'].shape[0]
|
| elif isinstance(batch, list) or isinstance(batch, dict):
|
| count = len(batch)
|
|
|
| return (count, )
|
|
|
| class ModelCompile():
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "model": ("MODEL",),
|
| "fullgraph": ("BOOLEAN", { "default": False }),
|
| "dynamic": ("BOOLEAN", { "default": False }),
|
| "mode": (["default", "reduce-overhead", "max-autotune", "max-autotune-no-cudagraphs"],),
|
| },
|
| }
|
|
|
| RETURN_TYPES = ("MODEL", )
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, model, fullgraph, dynamic, mode):
|
| work_model = model.clone()
|
| torch._dynamo.config.suppress_errors = True
|
| work_model.add_object_patch("diffusion_model", torch.compile(model=work_model.get_model_object("diffusion_model"), dynamic=dynamic, fullgraph=fullgraph, mode=mode))
|
| return (work_model, )
|
|
|
| class RemoveLatentMask:
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {"required": { "samples": ("LATENT",),}}
|
| RETURN_TYPES = ("LATENT",)
|
| FUNCTION = "execute"
|
|
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, samples):
|
| s = samples.copy()
|
| if "noise_mask" in s:
|
| del s["noise_mask"]
|
|
|
| return (s,)
|
|
|
| class SDXLEmptyLatentSizePicker:
|
| def __init__(self):
|
| self.device = comfy.model_management.intermediate_device()
|
|
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {"required": {
|
| "resolution": (["704x1408 (0.5)","704x1344 (0.52)","768x1344 (0.57)","768x1280 (0.6)","832x1216 (0.68)","832x1152 (0.72)","896x1152 (0.78)","896x1088 (0.82)","960x1088 (0.88)","960x1024 (0.94)","1024x1024 (1.0)","1024x960 (1.07)","1088x960 (1.13)","1088x896 (1.21)","1152x896 (1.29)","1152x832 (1.38)","1216x832 (1.46)","1280x768 (1.67)","1344x768 (1.75)","1344x704 (1.91)","1408x704 (2.0)","1472x704 (2.09)","1536x640 (2.4)","1600x640 (2.5)","1664x576 (2.89)","1728x576 (3.0)",], {"default": "1024x1024 (1.0)"}),
|
| "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
| "width_override": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
|
| "height_override": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}),
|
| }}
|
|
|
| RETURN_TYPES = ("LATENT","INT","INT",)
|
| RETURN_NAMES = ("LATENT","width","height",)
|
| FUNCTION = "execute"
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, resolution, batch_size, width_override=0, height_override=0):
|
| width, height = resolution.split(" ")[0].split("x")
|
| width = width_override if width_override > 0 else int(width)
|
| height = height_override if height_override > 0 else int(height)
|
|
|
| latent = torch.zeros([batch_size, 4, height // 8, width // 8], device=self.device)
|
|
|
| return ({"samples":latent}, width, height,)
|
|
|
| class DisplayAny:
|
| def __init__(self):
|
| pass
|
|
|
| @classmethod
|
| def INPUT_TYPES(s):
|
| return {
|
| "required": {
|
| "input": (("*",{})),
|
| "mode": (["raw value", "tensor shape"],),
|
| },
|
| }
|
|
|
| @classmethod
|
| def VALIDATE_INPUTS(s, input_types):
|
| return True
|
|
|
| RETURN_TYPES = ("STRING",)
|
| FUNCTION = "execute"
|
| OUTPUT_NODE = True
|
|
|
| CATEGORY = "essentials/utilities"
|
|
|
| def execute(self, input, mode):
|
| if mode == "tensor shape":
|
| text = []
|
| def tensorShape(tensor):
|
| if isinstance(tensor, dict):
|
| for k in tensor:
|
| tensorShape(tensor[k])
|
| elif isinstance(tensor, list):
|
| for i in range(len(tensor)):
|
| tensorShape(tensor[i])
|
| elif hasattr(tensor, 'shape'):
|
| text.append(list(tensor.shape))
|
|
|
| tensorShape(input)
|
| input = text
|
|
|
| text = str(input)
|
|
|
| return {"ui": {"text": text}, "result": (text,)}
|
|
|
| MISC_CLASS_MAPPINGS = {
|
| "BatchCount+": BatchCount,
|
| "ConsoleDebug+": ConsoleDebug,
|
| "DebugTensorShape+": DebugTensorShape,
|
| "DisplayAny": DisplayAny,
|
| "ModelCompile+": ModelCompile,
|
| "RemoveLatentMask+": RemoveLatentMask,
|
| "SDXLEmptyLatentSizePicker+": SDXLEmptyLatentSizePicker,
|
| "SimpleComparison+": SimpleComparison,
|
| "SimpleCondition+": SimpleCondition,
|
| "SimpleMath+": SimpleMath,
|
| "SimpleMathDual+": SimpleMathDual,
|
| "SimpleMathCondition+": SimpleMathCondition,
|
| "SimpleMathBoolean+": SimpleMathBoolean,
|
| "SimpleMathFloat+": SimpleMathFloat,
|
| "SimpleMathInt+": SimpleMathInt,
|
| "SimpleMathPercent+": SimpleMathPercent,
|
| "SimpleMathSlider+": SimpleMathSlider,
|
| "SimpleMathSliderLowRes+": SimpleMathSliderLowRes,
|
| }
|
|
|
| MISC_NAME_MAPPINGS = {
|
| "BatchCount+": "π§ Batch Count",
|
| "ConsoleDebug+": "π§ Console Debug",
|
| "DebugTensorShape+": "π§ Debug Tensor Shape",
|
| "DisplayAny": "π§ Display Any",
|
| "ModelCompile+": "π§ Model Compile",
|
| "RemoveLatentMask+": "π§ Remove Latent Mask",
|
| "SDXLEmptyLatentSizePicker+": "π§ Empty Latent Size Picker",
|
| "SimpleComparison+": "π§ Simple Comparison",
|
| "SimpleCondition+": "π§ Simple Condition",
|
| "SimpleMath+": "π§ Simple Math",
|
| "SimpleMathDual+": "π§ Simple Math Dual",
|
| "SimpleMathCondition+": "π§ Simple Math Condition",
|
| "SimpleMathBoolean+": "π§ Simple Math Boolean",
|
| "SimpleMathFloat+": "π§ Simple Math Float",
|
| "SimpleMathInt+": "π§ Simple Math Int",
|
| "SimpleMathPercent+": "π§ Simple Math Percent",
|
| "SimpleMathSlider+": "π§ Simple Math Slider",
|
| "SimpleMathSliderLowRes+": "π§ Simple Math Slider low-res",
|
| } |