Merge fbd6ba21825d81d160152abd5d0c4b70cd392973 into c661baadd9683c0033cd2a6ad90157c6d099a6c2

This commit is contained in:
sfinktah 2025-11-14 18:13:40 +01:00 committed by GitHub
commit 59e53de023
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -115,12 +115,12 @@ to a different frame count.
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"input_str": ("STRING", {"forceInput": True,"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n"}), "input_str": ("STRING", {"forceInput": True,"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n"}),
"old_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}), "old_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}),
"new_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}), "new_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}),
}, },
} }
def scaleschedule(self, old_frame_count, input_str, new_frame_count): def scaleschedule(self, old_frame_count, input_str, new_frame_count):
pattern = r'"(\d+)"\s*:\s*"(.*?)"(?:,|\Z)' pattern = r'"(\d+)"\s*:\s*"(.*?)"(?:,|\Z)'
@ -158,11 +158,11 @@ Selects and returns the latents at the specified indices as an latent batch.
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"latents": ("LATENT",), "latents": ("LATENT",),
"indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}), "indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}),
"latent_format": (["BCHW", "BTCHW", "BCTHW"], {"default": "BCHW"}), "latent_format": (["BCHW", "BTCHW", "BCTHW"], {"default": "BCHW"}),
}, },
} }
def indexedlatentsfrombatch(self, latents, indexes, latent_format): def indexedlatentsfrombatch(self, latents, indexes, latent_format):
@ -197,7 +197,7 @@ class ConditioningMultiCombine:
"conditioning_1": ("CONDITIONING", ), "conditioning_1": ("CONDITIONING", ),
"conditioning_2": ("CONDITIONING", ), "conditioning_2": ("CONDITIONING", ),
}, },
} }
RETURN_TYPES = ("CONDITIONING", "INT") RETURN_TYPES = ("CONDITIONING", "INT")
RETURN_NAMES = ("combined", "inputcount") RETURN_NAMES = ("combined", "inputcount")
@ -276,7 +276,7 @@ class JoinStringMulti:
"optional": { "optional": {
"string_2": ("STRING", {"default": '', "forceInput": True}), "string_2": ("STRING", {"default": '', "forceInput": True}),
} }
} }
RETURN_TYPES = ("STRING",) RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("string",) RETURN_NAMES = ("string",)
@ -316,7 +316,7 @@ class CondPassThrough:
"positive": ("CONDITIONING", ), "positive": ("CONDITIONING", ),
"negative": ("CONDITIONING", ), "negative": ("CONDITIONING", ),
}, },
} }
RETURN_TYPES = ("CONDITIONING", "CONDITIONING",) RETURN_TYPES = ("CONDITIONING", "CONDITIONING",)
RETURN_NAMES = ("positive", "negative") RETURN_NAMES = ("positive", "negative")
@ -339,7 +339,7 @@ class ModelPassThrough:
"optional": { "optional": {
"model": ("MODEL", ), "model": ("MODEL", ),
}, },
} }
RETURN_TYPES = ("MODEL", ) RETURN_TYPES = ("MODEL", )
RETURN_NAMES = ("model",) RETURN_NAMES = ("model",)
@ -351,15 +351,15 @@ class ModelPassThrough:
""" """
def passthrough(self, model=None): def passthrough(self, model=None):
return (model,) return (model,)
def append_helper(t, mask, c, set_area_to_bounds, strength): def append_helper(t, mask, c, set_area_to_bounds, strength):
n = [t[0], t[1].copy()] n = [t[0], t[1].copy()]
_, h, w = mask.shape _, h, w = mask.shape
n[1]['mask'] = mask n[1]['mask'] = mask
n[1]['set_area_to_bounds'] = set_area_to_bounds n[1]['set_area_to_bounds'] = set_area_to_bounds
n[1]['mask_strength'] = strength n[1]['mask_strength'] = strength
c.append(n) c.append(n)
class ConditioningSetMaskAndCombine: class ConditioningSetMaskAndCombine:
@classmethod @classmethod
@ -606,19 +606,19 @@ class VRAM_Debug:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"empty_cache": ("BOOLEAN", {"default": True}), "empty_cache": ("BOOLEAN", {"default": True}),
"gc_collect": ("BOOLEAN", {"default": True}), "gc_collect": ("BOOLEAN", {"default": True}),
"unload_all_models": ("BOOLEAN", {"default": False}), "unload_all_models": ("BOOLEAN", {"default": False}),
}, },
"optional": { "optional": {
"any_input": (IO.ANY,), "any_input": (IO.ANY,),
"image_pass": ("IMAGE",), "image_pass": ("IMAGE",),
"model_pass": ("MODEL",), "model_pass": ("MODEL",),
}
} }
}
RETURN_TYPES = (IO.ANY, "IMAGE","MODEL","INT", "INT",) RETURN_TYPES = (IO.ANY, "IMAGE","MODEL","INT", "INT",)
RETURN_NAMES = ("any_output", "image_pass", "model_pass", "freemem_before", "freemem_after") RETURN_NAMES = ("any_output", "image_pass", "model_pass", "freemem_before", "freemem_after")
@ -652,15 +652,15 @@ class SomethingToString:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"input": (IO.ANY, ), "input": (IO.ANY, ),
}, },
"optional": { "optional": {
"prefix": ("STRING", {"default": ""}), "prefix": ("STRING", {"default": ""}),
"suffix": ("STRING", {"default": ""}), "suffix": ("STRING", {"default": ""}),
} }
} }
RETURN_TYPES = ("STRING",) RETURN_TYPES = ("STRING",)
FUNCTION = "stringify" FUNCTION = "stringify"
CATEGORY = "KJNodes/text" CATEGORY = "KJNodes/text"
@ -708,31 +708,31 @@ class EmptyLatentImagePresets:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"dimensions": ( "dimensions": (
[ [
'512 x 512 (1:1)', '512 x 512 (1:1)',
'768 x 512 (1.5:1)', '768 x 512 (1.5:1)',
'960 x 512 (1.875:1)', '960 x 512 (1.875:1)',
'1024 x 512 (2:1)', '1024 x 512 (2:1)',
'1024 x 576 (1.778:1)', '1024 x 576 (1.778:1)',
'1536 x 640 (2.4:1)', '1536 x 640 (2.4:1)',
'1344 x 768 (1.75:1)', '1344 x 768 (1.75:1)',
'1216 x 832 (1.46:1)', '1216 x 832 (1.46:1)',
'1152 x 896 (1.286:1)', '1152 x 896 (1.286:1)',
'1024 x 1024 (1:1)', '1024 x 1024 (1:1)',
], ],
{ {
"default": '512 x 512 (1:1)' "default": '512 x 512 (1:1)'
}), }),
"invert": ("BOOLEAN", {"default": False}), "invert": ("BOOLEAN", {"default": False}),
"batch_size": ("INT", { "batch_size": ("INT", {
"default": 1, "default": 1,
"min": 1, "min": 1,
"max": 4096 "max": 4096
}), }),
}, },
} }
RETURN_TYPES = ("LATENT", "INT", "INT") RETURN_TYPES = ("LATENT", "INT", "INT")
@ -767,18 +767,18 @@ class EmptyLatentImageCustomPresets:
except FileNotFoundError: except FileNotFoundError:
dimensions_dict = [] dimensions_dict = []
return { return {
"required": { "required": {
"dimensions": ( "dimensions": (
[f"{d['label']} - {d['value']}" for d in dimensions_dict], [f"{d['label']} - {d['value']}" for d in dimensions_dict],
), ),
"invert": ("BOOLEAN", {"default": False}), "invert": ("BOOLEAN", {"default": False}),
"batch_size": ("INT", { "batch_size": ("INT", {
"default": 1, "default": 1,
"min": 1, "min": 1,
"max": 4096 "max": 4096
}), }),
}, },
} }
RETURN_TYPES = ("LATENT", "INT", "INT") RETURN_TYPES = ("LATENT", "INT", "INT")
@ -791,19 +791,20 @@ The choices are loaded from 'custom_dimensions.json' in the nodes folder.
""" """
def generate(self, dimensions, invert, batch_size): def generate(self, dimensions, invert, batch_size):
from nodes import EmptyLatentImage from nodes import EmptyLatentImage
# Split the string into label and value # Split the string into label and value
label, value = dimensions.split(' - ') label, value = dimensions.split(' - ')
# Split the value into width and height # Split the value into width and height
width, height = [x.strip() for x in value.split('x')] width, height = [x.strip() for x in value.split('x')]
if invert: if invert:
width, height = height, width width, height = height, width
latent = EmptyLatentImage().generate(int(width), int(height), batch_size)[0] latent = EmptyLatentImage().generate(int(width), int(height), batch_size)[0]
return (latent, int(width), int(height),) return (latent, int(width), int(height),)
# noinspection PyShadowingNames
class WidgetToString: class WidgetToString:
@classmethod @classmethod
def IS_CHANGED(cls,*,id,node_title,any_input,**kwargs): def IS_CHANGED(cls,*,id,node_title,any_input,**kwargs):
@ -819,11 +820,11 @@ class WidgetToString:
"return_all": ("BOOLEAN", {"default": False}), "return_all": ("BOOLEAN", {"default": False}),
}, },
"optional": { "optional": {
"any_input": (IO.ANY, ), "any_input": (IO.ANY, ),
"node_title": ("STRING", {"multiline": False}), "node_title": ("STRING", {"multiline": False}),
"allowed_float_decimals": ("INT", {"default": 2, "min": 0, "max": 10, "tooltip": "Number of decimal places to display for float values"}), "allowed_float_decimals": ("INT", {"default": 2, "min": 0, "max": 10, "tooltip": "Number of decimal places to display for float values"}),
}, },
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "hidden": {"extra_pnginfo": "EXTRA_PNGINFO",
"prompt": "PROMPT", "prompt": "PROMPT",
"unique_id": "UNIQUE_ID",}, "unique_id": "UNIQUE_ID",},
@ -842,54 +843,231 @@ The 'any_input' is required for making sure the node you want the value from exi
""" """
def get_widget_value(self, id, widget_name, extra_pnginfo, prompt, unique_id, return_all=False, any_input=None, node_title="", allowed_float_decimals=2): def get_widget_value(self, id, widget_name, extra_pnginfo, prompt, unique_id, return_all=False, any_input=None, node_title="", allowed_float_decimals=2):
"""
Retrieves the value of the specified widget from a node in the workflow and
returns it as a string.
If no `id` or `node_title` is provided, the method attempts to identify the
node using the `any_input` connection in the workflow. Enable node ID display
in ComfyUI's "Manager" menu to view node IDs, or use a manually edited node
title for searching. NOTE: A node does not have a title unless it is manually
edited to something other than its default value.
Args:
id (int): The unique ID of the target node. If 0, the method relies on
other methods to determine the node. TODO: change to a STRING (breaking change)
widget_name (str): The name of the widget whose value needs to be retrieved.
extra_pnginfo (dict): A dictionary containing workflow metadata, including
node connections and state.
prompt (dict): A dictionary containing node-specific data with input
settings to extract widget values.
unique_id (str): The unique identifier of the current node instance, used
to match the `any_input` connection.
return_all (bool): If True, retrieves and returns all input values from
the node, formatted as a string.
any_input (str): Optional. A link reference used to determine the node if
no `id` or `node_title` is provided.
node_title (str): Optional. The title of the node to search for. Titles
are valid only if manually assigned in ComfyUI.
allowed_float_decimals (int): The number of decimal places to which float
values should be rounded in the output.
Returns:
str or tuple:
- If `return_all` is False, returns a tuple with the value of the
specified widget.
- If `return_all` is True, returns a formatted string containing all
input values for the node.
Raises:
ValueError: If no matching node is found for the given `id`, `node_title`,
or `any_input`.
NameError: If the specified widget does not exist in the identified node.
"""
workflow = extra_pnginfo["workflow"] workflow = extra_pnginfo["workflow"]
#print(json.dumps(workflow, indent=4))
results = [] results = []
node_id = None # Initialize node_id to handle cases where no match is found target_full_node_id = None # string like "5", "5:1", "5:9:6"
link_id = None active_link_id = None
# Normalize incoming ids which may be lists/tuples (e.g., ["7:9:14", 0])
def normalize_any_id(value):
# If list/tuple, take the first element which should be the id/path
if isinstance(value, (list, tuple)) and value:
value = value[0]
# Convert ints to str
if isinstance(value, int):
return str(value)
# Pass through strings; None -> empty
return value if isinstance(value, str) else ""
id_str = normalize_any_id(id)
unique_id_str = normalize_any_id(unique_id)
# Map of (scope_key, link_id) -> full_node_id
# scope_key: '' for top-level, or the subgraph instance path for nested nodes (e.g., '5', '5:9')
link_to_node_map = {} link_to_node_map = {}
for node in workflow["nodes"]: # Build a map of subgraph id -> definition for quick lookup
if node_title: defs = workflow.get("definitions", {}) or {}
if "title" in node: subgraph_defs = {sg.get("id"): sg for sg in (defs.get("subgraphs", []) or []) if sg.get("id")}
if node["title"] == node_title:
node_id = node["id"]
break
else:
print("Node title not found.")
elif id != 0:
if node["id"] == id:
node_id = id
break
elif any_input is not None:
if node["type"] == "WidgetToString" and node["id"] == int(unique_id) and not link_id:
for node_input in node["inputs"]:
if node_input["name"] == "any_input":
link_id = node_input["link"]
# Construct a map of links to node IDs for future reference # Helper: register output links -> node map (scoped)
node_outputs = node.get("outputs", None) def register_links(scope_key, node_obj, full_node_id):
if not node_outputs: outputs = node_obj.get("outputs") or []
for out in outputs:
links = out.get("links")
if not links:
continue continue
for output in node_outputs: if isinstance(links, list):
node_links = output.get("links", None) for lid in links:
if not node_links: if lid is None:
continue continue
for link in node_links: link_to_node_map[(scope_key, lid)] = full_node_id
link_to_node_map[link] = node["id"]
if link_id and link == link_id:
break
if link_id: # Recursive emitter for a subgraph instance
node_id = link_to_node_map.get(link_id, None) # instance_path: the full path to this subgraph instance (e.g., '5' or '5:9')
def emit_subgraph_instance(sub_def, instance_path):
for snode in (sub_def.get("nodes") or []):
child_id = str(snode.get("id"))
full_id = f"{instance_path}:{child_id}"
# Yield the node with the scope of this subgraph instance
yield full_id, instance_path, snode
# If this node itself is a subgraph instance, recurse
stype = snode.get("type")
nested_def = subgraph_defs.get(stype)
if nested_def is not None:
nested_instance_path = full_id # e.g., '5:9'
for inner in emit_subgraph_instance(nested_def, nested_instance_path):
yield inner
if node_id is None: # Master iterator: yields all nodes with their full_node_id and scope
raise ValueError("No matching node found for the given title or id") def iter_all_nodes():
# 1) Top-level nodes
for node in workflow.get("nodes", []):
full_node_id = str(node.get("id"))
scope_key = "" # top-level link id space
yield full_node_id, scope_key, node
# 2) If a top-level node is an instance of a subgraph, emit its internal nodes
ntype = node.get("type")
sg_def = subgraph_defs.get(ntype)
if sg_def is not None:
instance_path = full_node_id # e.g., '5'
for item in emit_subgraph_instance(sg_def, instance_path):
yield item
# Helpers for id/unique_id handling
def match_id_with_fullness(candidate_full_id, requested_id):
# Exact match if the request is fully qualified
if ":" in requested_id:
return candidate_full_id == requested_id
# Otherwise, allow exact top-level id or ending with ":child"
return candidate_full_id == requested_id or candidate_full_id.endswith(f":{requested_id}")
def parent_scope_of(full_id):
parts = full_id.split(":")
return ":".join(parts[:-1]) if len(parts) > 1 else ""
def resolve_scope_from_unique_id(u_str):
# Fully qualified: everything before the last segment is the scope
if ":" in u_str:
return parent_scope_of(u_str)
# Not qualified: try to infer from prompt keys by suffix
suffix = f":{u_str}"
matches = [k for k in prompt.keys() if isinstance(k, str) and k.endswith(suffix)]
matches = list(dict.fromkeys(matches)) # dedupe
if len(matches) == 1:
return parent_scope_of(matches[0])
elif len(matches) == 0:
return None
else:
raise ValueError(
f"Ambiguous unique_id '{u_str}'. Multiple subgraph instances match. "
f"Use a fully qualified id like 'parentPath:{u_str}' (e.g., '5:9:{u_str}')."
)
# First: build a complete list of nodes and the scoped link map
all_nodes = []
for full_node_id, scope_key, node in iter_all_nodes():
all_nodes.append((full_node_id, scope_key, node))
register_links(scope_key, node, full_node_id)
# Try title or id first
if node_title:
for full_node_id, _, node in all_nodes:
if "title" in node and node.get("title") == node_title:
target_full_node_id = full_node_id
break
# If title matched, do not attempt any_input fallback
any_input = None
elif id_str not in ("", "0"):
matches = [fid for fid, _, _ in all_nodes if match_id_with_fullness(fid, id_str)]
if len(matches) > 1 and ":" not in id_str and any(m != id_str for m in matches):
raise ValueError(
f"Ambiguous id '{id_str}'. Multiple nodes match across (nested) subgraphs. "
f"Use a fully qualified id like '5:9:{id_str}'."
)
target_full_node_id = matches[0] if matches else None
# Resolve via any_input + unique_id if still not found
if target_full_node_id is None and any_input is not None and unique_id_str:
# If unique_id is fully qualified, select that exact node
wts_full_id = None
if ":" in unique_id_str:
for fid, _, node in all_nodes:
if fid == unique_id_str and node.get("type") == "WidgetToString":
wts_full_id = fid
break
if wts_full_id is None:
raise ValueError(f"No WidgetToString found for unique_id '{unique_id_str}'")
found_scope_key = parent_scope_of(wts_full_id)
else:
# Infer scope from prompt keys when unqualified
found_scope_key = resolve_scope_from_unique_id(unique_id_str)
candidates = []
if found_scope_key:
candidates.append(f"{found_scope_key}:{unique_id_str}")
else:
candidates.append(unique_id_str)
for fid, scope_key, node in all_nodes:
if node.get("type") == "WidgetToString" and fid in candidates:
wts_full_id = fid
if not found_scope_key:
found_scope_key = parent_scope_of(fid)
break
if wts_full_id is None:
raise ValueError(f"No WidgetToString found for unique_id '{unique_id_str}'")
# With the WidgetToString located, read its any_input link id
wts_node = next(node for fid, _, node in all_nodes if fid == wts_full_id)
for node_input in (wts_node.get("inputs") or []):
if node_input.get("name") == "any_input":
active_link_id = node_input.get("link")
break
if active_link_id is None:
raise ValueError(f"WidgetToString '{wts_full_id}' has no 'any_input' link")
# Resolve the producer of that link within the correct scope
target_full_node_id = link_to_node_map.get((found_scope_key or "", active_link_id))
if target_full_node_id is None:
raise ValueError(
f"Could not resolve link {active_link_id} in scope '{found_scope_key}'. "
f"The subgraph clones links may not have been discovered."
)
if target_full_node_id is None:
raise ValueError("No matching node found for the given title, id, or any_input")
values = prompt.get(str(target_full_node_id))
if not values:
raise ValueError(f"No prompt entry found for node id: {target_full_node_id}")
values = prompt[str(node_id)]
if "inputs" in values: if "inputs" in values:
if return_all: if return_all:
# Format items based on type
formatted_items = [] formatted_items = []
for k, v in values["inputs"].items(): for k, v in values["inputs"].items():
if isinstance(v, float): if isinstance(v, float):
@ -906,7 +1084,7 @@ The 'any_input' is required for making sure the node you want the value from exi
v = str(v) v = str(v)
return (v, ) return (v, )
else: else:
raise NameError(f"Widget not found: {node_id}.{widget_name}") raise NameError(f"Widget not found: {target_full_node_id}.{widget_name}")
return (', '.join(results).strip(', '), ) return (', '.join(results).strip(', '), )
class DummyOut: class DummyOut:
@ -915,7 +1093,7 @@ class DummyOut:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return { return {
"required": { "required": {
"any_input": (IO.ANY, ), "any_input": (IO.ANY, ),
} }
} }
@ -973,11 +1151,11 @@ class CustomSigmas:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": return {"required":
{ {
"sigmas_string" :("STRING", {"default": "14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029","multiline": True}), "sigmas_string" :("STRING", {"default": "14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029","multiline": True}),
"interpolate_to_steps": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}), "interpolate_to_steps": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}),
} }
} }
RETURN_TYPES = ("SIGMAS",) RETURN_TYPES = ("SIGMAS",)
RETURN_NAMES = ("SIGMAS",) RETURN_NAMES = ("SIGMAS",)
CATEGORY = "KJNodes/noise" CATEGORY = "KJNodes/noise"
@ -1022,10 +1200,10 @@ class StringToFloatList:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": return {"required":
{ {
"string" :("STRING", {"default": "1, 2, 3", "multiline": True}), "string" :("STRING", {"default": "1, 2, 3", "multiline": True}),
} }
} }
RETURN_TYPES = ("FLOAT",) RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("FLOAT",) RETURN_NAMES = ("FLOAT",)
CATEGORY = "KJNodes/misc" CATEGORY = "KJNodes/misc"
@ -1045,13 +1223,13 @@ class InjectNoiseToLatent:
"noise": ("LATENT",), "noise": ("LATENT",),
"normalize": ("BOOLEAN", {"default": False}), "normalize": ("BOOLEAN", {"default": False}),
"average": ("BOOLEAN", {"default": False}), "average": ("BOOLEAN", {"default": False}),
}, },
"optional":{ "optional":{
"mask": ("MASK", ), "mask": ("MASK", ),
"mix_randn_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.001}), "mix_randn_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.001}),
"seed": ("INT", {"default": 123,"min": 0, "max": 0xffffffffffffffff, "step": 1}), "seed": ("INT", {"default": 123,"min": 0, "max": 0xffffffffffffffff, "step": 1}),
} }
} }
RETURN_TYPES = ("LATENT",) RETURN_TYPES = ("LATENT",)
FUNCTION = "injectnoise" FUNCTION = "injectnoise"
@ -1092,8 +1270,8 @@ class SoundReactive:
"multiplier": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 99999, "step": 0.01}), "multiplier": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 99999, "step": 0.01}),
"smoothing_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), "smoothing_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"normalize": ("BOOLEAN", {"default": False}), "normalize": ("BOOLEAN", {"default": False}),
}, },
} }
RETURN_TYPES = ("FLOAT","INT",) RETURN_TYPES = ("FLOAT","INT",)
RETURN_NAMES =("sound_level", "sound_level_int",) RETURN_NAMES =("sound_level", "sound_level_int",)
@ -1126,7 +1304,7 @@ class GenerateNoise:
"multiplier": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 4096, "step": 0.01}), "multiplier": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 4096, "step": 0.01}),
"constant_batch_noise": ("BOOLEAN", {"default": False}), "constant_batch_noise": ("BOOLEAN", {"default": False}),
"normalize": ("BOOLEAN", {"default": False}), "normalize": ("BOOLEAN", {"default": False}),
}, },
"optional": { "optional": {
"model": ("MODEL", ), "model": ("MODEL", ),
"sigmas": ("SIGMAS", ), "sigmas": ("SIGMAS", ),
@ -1171,14 +1349,14 @@ def camera_embeddings(elevation, azimuth):
azimuth = torch.as_tensor([azimuth]) azimuth = torch.as_tensor([azimuth])
embeddings = torch.stack( embeddings = torch.stack(
[ [
torch.deg2rad( torch.deg2rad(
(90 - elevation) - (90) (90 - elevation) - (90)
), # Zero123 polar is 90-elevation ), # Zero123 polar is 90-elevation
torch.sin(torch.deg2rad(azimuth)), torch.sin(torch.deg2rad(azimuth)),
torch.cos(torch.deg2rad(azimuth)), torch.cos(torch.deg2rad(azimuth)),
torch.deg2rad( torch.deg2rad(
90 - torch.full_like(elevation, 0) 90 - torch.full_like(elevation, 0)
), ),
], dim=-1).unsqueeze(1) ], dim=-1).unsqueeze(1)
return embeddings return embeddings
@ -1204,7 +1382,7 @@ class StableZero123_BatchSchedule:
"interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],),
"azimuth_points_string": ("STRING", {"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n", "multiline": True}), "azimuth_points_string": ("STRING", {"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n", "multiline": True}),
"elevation_points_string": ("STRING", {"default": "0:(0.0),\n7:(0.0),\n15:(0.0)\n", "multiline": True}), "elevation_points_string": ("STRING", {"default": "0:(0.0),\n7:(0.0),\n15:(0.0)\n", "multiline": True}),
}} }}
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("positive", "negative", "latent") RETURN_NAMES = ("positive", "negative", "latent")
@ -1337,7 +1515,7 @@ class SV3D_BatchSchedule:
"interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],),
"azimuth_points_string": ("STRING", {"default": "0:(0.0),\n9:(180.0),\n20:(360.0)\n", "multiline": True}), "azimuth_points_string": ("STRING", {"default": "0:(0.0),\n9:(180.0),\n20:(360.0)\n", "multiline": True}),
"elevation_points_string": ("STRING", {"default": "0:(0.0),\n9:(0.0),\n20:(0.0)\n", "multiline": True}), "elevation_points_string": ("STRING", {"default": "0:(0.0),\n9:(0.0),\n20:(0.0)\n", "multiline": True}),
}} }}
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("positive", "negative", "latent") RETURN_NAMES = ("positive", "negative", "latent")
@ -1518,11 +1696,11 @@ https://huggingface.co/roborovski/superprompt-v1
checkpoint_path = os.path.join(script_directory, "models","superprompt-v1") checkpoint_path = os.path.join(script_directory, "models","superprompt-v1")
if not os.path.exists(checkpoint_path): if not os.path.exists(checkpoint_path):
print(f"Downloading model to: {checkpoint_path}") print(f"Downloading model to: {checkpoint_path}")
from huggingface_hub import snapshot_download from huggingface_hub import snapshot_download
snapshot_download(repo_id="roborovski/superprompt-v1", snapshot_download(repo_id="roborovski/superprompt-v1",
local_dir=checkpoint_path, local_dir=checkpoint_path,
local_dir_use_symlinks=False) local_dir_use_symlinks=False)
tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-small", legacy=False) tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-small", legacy=False)
model = T5ForConditionalGeneration.from_pretrained(checkpoint_path, device_map=device) model = T5ForConditionalGeneration.from_pretrained(checkpoint_path, device_map=device)
@ -1550,11 +1728,11 @@ class CameraPoseVisualizer:
"use_exact_fx": ("BOOLEAN", {"default": False}), "use_exact_fx": ("BOOLEAN", {"default": False}),
"relative_c2w": ("BOOLEAN", {"default": True}), "relative_c2w": ("BOOLEAN", {"default": True}),
"use_viewer": ("BOOLEAN", {"default": False}), "use_viewer": ("BOOLEAN", {"default": False}),
}, },
"optional": { "optional": {
"cameractrl_poses": ("CAMERACTRL_POSES", {"default": None}), "cameractrl_poses": ("CAMERACTRL_POSES", {"default": None}),
} }
} }
RETURN_TYPES = ("IMAGE",) RETURN_TYPES = ("IMAGE",)
FUNCTION = "plot" FUNCTION = "plot"
@ -1616,7 +1794,7 @@ or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot.
for frame_idx, c2w in enumerate(c2ws): for frame_idx, c2w in enumerate(c2ws):
self.extrinsic2pyramid(c2w, frame_idx / total_frames, hw_ratio=1/1, base_xval=base_xval, self.extrinsic2pyramid(c2w, frame_idx / total_frames, hw_ratio=1/1, base_xval=base_xval,
zval=(fxs[frame_idx] if use_exact_fx else zval)) zval=(fxs[frame_idx] if use_exact_fx else zval))
# Create the colorbar # Create the colorbar
cmap = mpl.cm.rainbow cmap = mpl.cm.rainbow
@ -1652,16 +1830,16 @@ or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot.
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection from mpl_toolkits.mplot3d.art3d import Poly3DCollection
vertex_std = np.array([[0, 0, 0, 1], vertex_std = np.array([[0, 0, 0, 1],
[base_xval, -base_xval * hw_ratio, zval, 1], [base_xval, -base_xval * hw_ratio, zval, 1],
[base_xval, base_xval * hw_ratio, zval, 1], [base_xval, base_xval * hw_ratio, zval, 1],
[-base_xval, base_xval * hw_ratio, zval, 1], [-base_xval, base_xval * hw_ratio, zval, 1],
[-base_xval, -base_xval * hw_ratio, zval, 1]]) [-base_xval, -base_xval * hw_ratio, zval, 1]])
vertex_transformed = vertex_std @ extrinsic.T vertex_transformed = vertex_std @ extrinsic.T
meshes = [[vertex_transformed[0, :-1], vertex_transformed[1][:-1], vertex_transformed[2, :-1]], meshes = [[vertex_transformed[0, :-1], vertex_transformed[1][:-1], vertex_transformed[2, :-1]],
[vertex_transformed[0, :-1], vertex_transformed[2, :-1], vertex_transformed[3, :-1]], [vertex_transformed[0, :-1], vertex_transformed[2, :-1], vertex_transformed[3, :-1]],
[vertex_transformed[0, :-1], vertex_transformed[3, :-1], vertex_transformed[4, :-1]], [vertex_transformed[0, :-1], vertex_transformed[3, :-1], vertex_transformed[4, :-1]],
[vertex_transformed[0, :-1], vertex_transformed[4, :-1], vertex_transformed[1, :-1]], [vertex_transformed[0, :-1], vertex_transformed[4, :-1], vertex_transformed[1, :-1]],
[vertex_transformed[1, :-1], vertex_transformed[2, :-1], vertex_transformed[3, :-1], vertex_transformed[4, :-1]]] [vertex_transformed[1, :-1], vertex_transformed[2, :-1], vertex_transformed[3, :-1], vertex_transformed[4, :-1]]]
color = color_map if isinstance(color_map, str) else plt.cm.rainbow(color_map) color = color_map if isinstance(color_map, str) else plt.cm.rainbow(color_map)
@ -1705,7 +1883,7 @@ class CheckpointPerturbWeights:
"final_layer": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 10.0, "step": 0.001}), "final_layer": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 10.0, "step": 0.001}),
"rest_of_the_blocks": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 10.0, "step": 0.001}), "rest_of_the_blocks": ("FLOAT", {"default": 0.02, "min": 0.001, "max": 10.0, "step": 0.001}),
"seed": ("INT", {"default": 123,"min": 0, "max": 0xffffffffffffffff, "step": 1}), "seed": ("INT", {"default": 123,"min": 0, "max": 0xffffffffffffffff, "step": 1}),
} }
} }
RETURN_TYPES = ("MODEL",) RETURN_TYPES = ("MODEL",)
FUNCTION = "mod" FUNCTION = "mod"
@ -1745,11 +1923,11 @@ class DifferentialDiffusionAdvanced():
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"model": ("MODEL", ), "model": ("MODEL", ),
"samples": ("LATENT",), "samples": ("LATENT",),
"mask": ("MASK",), "mask": ("MASK",),
"multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.001}), "multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.001}),
}} }}
RETURN_TYPES = ("MODEL", "LATENT") RETURN_TYPES = ("MODEL", "LATENT")
FUNCTION = "apply" FUNCTION = "apply"
CATEGORY = "_for_testing" CATEGORY = "_for_testing"
@ -1861,16 +2039,16 @@ class DiTBlockLoraLoader:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}), "model": ("MODEL", {"tooltip": "The diffusion model the LoRA will be applied to."}),
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}), "strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01, "tooltip": "How strongly to modify the diffusion model. This value can be negative."}),
}, },
"optional": { "optional": {
"lora_name": (folder_paths.get_filename_list("loras"), {"tooltip": "The name of the LoRA."}), "lora_name": (folder_paths.get_filename_list("loras"), {"tooltip": "The name of the LoRA."}),
"opt_lora_path": ("STRING", {"forceInput": True, "tooltip": "Absolute path of the LoRA."}), "opt_lora_path": ("STRING", {"forceInput": True, "tooltip": "Absolute path of the LoRA."}),
"blocks": ("SELECTEDDITBLOCKS",), "blocks": ("SELECTEDDITBLOCKS",),
} }
} }
RETURN_TYPES = ("MODEL", "STRING", ) RETURN_TYPES = ("MODEL", "STRING", )
RETURN_NAMES = ("model", "rank", ) RETURN_NAMES = ("model", "rank", )
@ -2107,12 +2285,12 @@ class AudioConcatenate:
"audio1": ("AUDIO",), "audio1": ("AUDIO",),
"audio2": ("AUDIO",), "audio2": ("AUDIO",),
"direction": ( "direction": (
[ 'right', [ 'right',
'left', 'left',
], ],
{ {
"default": 'right' "default": 'right'
}), }),
}} }}
RETURN_TYPES = ("AUDIO",) RETURN_TYPES = ("AUDIO",)
@ -2293,8 +2471,8 @@ class VAELoaderKJ:
"required": { "vae_name": (s.vae_list(), ), "required": { "vae_name": (s.vae_list(), ),
"device": (["main_device", "cpu"],), "device": (["main_device", "cpu"],),
"weight_dtype": (["bf16", "fp16", "fp32" ],), "weight_dtype": (["bf16", "fp16", "fp32" ],),
} }
} }
RETURN_TYPES = ("VAE",) RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae" FUNCTION = "load_vae"
@ -2355,14 +2533,14 @@ class ScheduledCFGGuidance:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"model": ("MODEL",), "model": ("MODEL",),
"positive": ("CONDITIONING", ), "positive": ("CONDITIONING", ),
"negative": ("CONDITIONING", ), "negative": ("CONDITIONING", ),
"cfg": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 100.0, "step": 0.01}), "cfg": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 100.0, "step": 0.01}),
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step":0.01}), "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step":0.01}),
"end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step":0.01}), "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step":0.01}),
}, },
} }
RETURN_TYPES = ("GUIDER",) RETURN_TYPES = ("GUIDER",)
FUNCTION = "get_guider" FUNCTION = "get_guider"
CATEGORY = "KJNodes/experimental" CATEGORY = "KJNodes/experimental"
@ -2408,7 +2586,7 @@ class ApplyRifleXRoPE_WanVideo:
[d - 4 * (d // 6), 2 * (d // 6), 2 * (d // 6)], [d - 4 * (d // 6), 2 * (d // 6), 2 * (d // 6)],
num_frames, num_frames,
k k
) )
model_clone.add_object_patch(f"diffusion_model.rope_embedder", rope_embedder) model_clone.add_object_patch(f"diffusion_model.rope_embedder", rope_embedder)
@ -2443,7 +2621,7 @@ class ApplyRifleXRoPE_HunuyanVideo:
model_class.params.axes_dim, model_class.params.axes_dim,
num_frames, num_frames,
k k
) )
model_clone.add_object_patch(f"diffusion_model.pe_embedder", pe_embedder) model_clone.add_object_patch(f"diffusion_model.pe_embedder", pe_embedder)
@ -2497,16 +2675,16 @@ class TimerNodeKJ:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return { return {
"required": { "required": {
"any_input": (IO.ANY, ), "any_input": (IO.ANY, ),
"mode": (["start", "stop"],), "mode": (["start", "stop"],),
"name": ("STRING", {"default": "Timer"}), "name": ("STRING", {"default": "Timer"}),
}, },
"optional": { "optional": {
"timer": ("TIMER",), "timer": ("TIMER",),
}, },
} }
RETURN_TYPES = (IO.ANY, "TIMER", "INT", ) RETURN_TYPES = (IO.ANY, "TIMER", "INT", )
RETURN_NAMES = ("any_output", "timer", "time") RETURN_NAMES = ("any_output", "timer", "time")
@ -2519,9 +2697,9 @@ class TimerNodeKJ:
timer = Timer(name=name) timer = Timer(name=name)
timer.start_time = time.time() timer.start_time = time.time()
return {"ui": { return {"ui": {
"text": [f"{timer.start_time}"]}, "text": [f"{timer.start_time}"]},
"result": (any_input, timer, 0) "result": (any_input, timer, 0)
} }
elif mode == "stop" and timer is not None: elif mode == "stop" and timer is not None:
end_time = time.time() end_time = time.time()
timer.elapsed = int((end_time - timer.start_time) * 1000) timer.elapsed = int((end_time - timer.start_time) * 1000)
@ -2532,21 +2710,21 @@ class HunyuanVideoEncodeKeyframesToCond:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"model": ("MODEL",), "model": ("MODEL",),
"positive": ("CONDITIONING", ), "positive": ("CONDITIONING", ),
"vae": ("VAE", ), "vae": ("VAE", ),
"start_frame": ("IMAGE", ), "start_frame": ("IMAGE", ),
"end_frame": ("IMAGE", ), "end_frame": ("IMAGE", ),
"num_frames": ("INT", {"default": 33, "min": 2, "max": 4096, "step": 1}), "num_frames": ("INT", {"default": 33, "min": 2, "max": 4096, "step": 1}),
"tile_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}), "tile_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}),
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}), "overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}),
"temporal_size": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to encode at a time."}), "temporal_size": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to encode at a time."}),
"temporal_overlap": ("INT", {"default": 8, "min": 4, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to overlap."}), "temporal_overlap": ("INT", {"default": 8, "min": 4, "max": 4096, "step": 4, "tooltip": "Only used for video VAEs: Amount of frames to overlap."}),
}, },
"optional": { "optional": {
"negative": ("CONDITIONING", ), "negative": ("CONDITIONING", ),
} }
} }
RETURN_TYPES = ("MODEL", "CONDITIONING","CONDITIONING","LATENT") RETURN_TYPES = ("MODEL", "CONDITIONING","CONDITIONING","LATENT")
RETURN_NAMES = ("model", "positive", "negative", "latent") RETURN_NAMES = ("model", "positive", "negative", "latent")