2024-12-04 12:01:00 -08:00
from __future__ import annotations
2023-01-03 01:53:32 -05:00
import torch
2025-07-29 19:17:22 -07:00
2023-01-03 01:53:32 -05:00
import os
import sys
import json
2026-01-18 08:40:39 +02:00
import glob
2023-01-22 21:42:22 -05:00
import hashlib
2025-07-31 15:02:12 -07:00
import inspect
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
2023-02-17 11:19:49 -05:00
import traceback
2023-05-02 00:53:15 -04:00
import math
2023-05-13 11:54:45 -04:00
import time
2023-07-11 17:35:55 -04:00
import random
2024-03-11 00:56:41 -04:00
import logging
2023-01-03 01:53:32 -05:00
2024-12-08 08:06:00 -05:00
from PIL import Image , ImageOps , ImageSequence
2023-01-03 01:53:32 -05:00
from PIL . PngImagePlugin import PngInfo
2024-05-04 02:32:41 -05:00
2023-01-03 01:53:32 -05:00
import numpy as np
2023-05-17 23:04:40 -04:00
import safetensors . torch
2023-01-03 01:53:32 -05:00
2024-03-19 11:17:37 -04:00
sys . path . insert ( 0 , os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , " comfy " ) )
2023-05-28 01:52:09 -04:00
import comfy . diffusers_load
2023-01-03 01:53:32 -05:00
import comfy . samplers
2023-04-23 20:02:08 +02:00
import comfy . sample
2023-01-03 01:53:32 -05:00
import comfy . sd
2023-02-16 10:38:08 -05:00
import comfy . utils
2023-08-25 17:25:39 -04:00
import comfy . controlnet
2025-03-05 15:35:26 -05:00
from comfy . comfy_types import IO , ComfyNodeABC , InputTypeDict , FileLocator
2025-07-29 19:17:22 -07:00
from comfy_api . internal import register_versions , ComfyAPIWithVersion
from comfy_api . version_list import supported_versions
2026-04-28 15:15:06 -07:00
from comfy_api . latest import io , ComfyExtension , InputImpl
2023-02-16 10:38:08 -05:00
2023-04-01 23:19:15 -04:00
import comfy . clip_vision
2023-03-05 18:39:25 -05:00
2023-04-15 18:55:17 -04:00
import comfy . model_management
2023-07-28 12:31:41 -04:00
from comfy . cli_args import args
2023-02-15 21:48:10 +07:00
import importlib
2023-01-03 01:53:32 -05:00
2023-03-17 17:57:57 -04:00
import folder_paths
2023-06-06 01:26:52 -04:00
import latent_preview
2024-04-07 14:27:40 -04:00
import node_helpers
2023-06-05 18:39:56 -05:00
2025-12-02 12:32:52 +09:00
if args . enable_manager :
import comfyui_manager
2023-03-02 14:42:03 -05:00
def before_node_execution ( ) :
2023-04-15 18:55:17 -04:00
comfy . model_management . throw_exception_if_processing_interrupted ( )
2023-03-02 14:42:03 -05:00
2023-03-02 15:24:51 -05:00
def interrupt_processing ( value = True ) :
2023-04-15 18:55:17 -04:00
comfy . model_management . interrupt_current_processing ( value )
2023-03-02 14:42:03 -05:00
2024-03-26 04:00:53 -04:00
MAX_RESOLUTION = 16384
2023-03-22 12:22:48 -04:00
2024-12-04 12:01:00 -08:00
class CLIPTextEncode ( ComfyNodeABC ) :
2023-01-03 01:53:32 -05:00
@classmethod
2024-12-04 12:01:00 -08:00
def INPUT_TYPES ( s ) - > InputTypeDict :
2024-08-14 06:22:10 +01:00
return {
" required " : {
2024-12-31 03:16:37 -05:00
" text " : ( IO . STRING , { " multiline " : True , " dynamicPrompts " : True , " tooltip " : " The text to be encoded. " } ) ,
2024-12-04 12:01:00 -08:00
" clip " : ( IO . CLIP , { " tooltip " : " The CLIP model used for encoding the text. " } )
2024-08-14 06:22:10 +01:00
}
}
2024-12-04 12:01:00 -08:00
RETURN_TYPES = ( IO . CONDITIONING , )
2024-08-14 06:22:10 +01:00
OUTPUT_TOOLTIPS = ( " A conditioning containing the embedded text used to guide the diffusion model. " , )
2023-01-03 01:53:32 -05:00
FUNCTION = " encode "
2023-01-26 12:23:15 -05:00
CATEGORY = " conditioning "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Encodes a text prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " text " , " prompt " , " text prompt " , " positive prompt " , " negative prompt " , " encode text " , " text encoder " , " encode prompt " ]
2023-01-26 12:23:15 -05:00
2023-01-03 01:53:32 -05:00
def encode ( self , clip , text ) :
2025-01-26 06:04:57 -05:00
if clip is None :
raise RuntimeError ( " ERROR: clip input is invalid: None \n \n If the clip is from a checkpoint loader node your checkpoint does not contain a valid clip or text encoder model. " )
2023-06-22 13:03:50 -04:00
tokens = clip . tokenize ( text )
ModelPatcher Overhaul and Hook Support (#5583)
* Added hook_patches to ModelPatcher for weights (model)
* Initial changes to calc_cond_batch to eventually support hook_patches
* Added current_patcher property to BaseModel
* Consolidated add_hook_patches_as_diffs into add_hook_patches func, fixed fp8 support for model-as-lora feature
* Added call to initialize_timesteps on hooks in process_conds func, and added call prepare current keyframe on hooks in calc_cond_batch
* Added default_conds support in calc_cond_batch func
* Added initial set of hook-related nodes, added code to register hooks for loras/model-as-loras, small renaming/refactoring
* Made CLIP work with hook patches
* Added initial hook scheduling nodes, small renaming/refactoring
* Fixed MaxSpeed and default conds implementations
* Added support for adding weight hooks that aren't registered on the ModelPatcher at sampling time
* Made Set Clip Hooks node work with hooks from Create Hook nodes, began work on better Create Hook Model As LoRA node
* Initial work on adding 'model_as_lora' lora type to calculate_weight
* Continued work on simpler Create Hook Model As LoRA node, started to implement ModelPatcher callbacks, attachments, and additional_models
* Fix incorrect ref to create_hook_patches_clone after moving function
* Added injections support to ModelPatcher + necessary bookkeeping, added additional_models support in ModelPatcher, conds, and hooks
* Added wrappers to ModelPatcher to facilitate standardized function wrapping
* Started scaffolding for other hook types, refactored get_hooks_from_cond to organize hooks by type
* Fix skip_until_exit logic bug breaking injection after first run of model
* Updated clone_has_same_weights function to account for new ModelPatcher properties, improved AutoPatcherEjector usage in partially_load
* Added WrapperExecutor for non-classbound functions, added calc_cond_batch wrappers
* Refactored callbacks+wrappers to allow storing lists by id
* Added forward_timestep_embed_patch type, added helper functions on ModelPatcher for emb_patch and forward_timestep_embed_patch, added helper functions for removing callbacks/wrappers/additional_models by key, added custom_should_register prop to hooks
* Added get_attachment func on ModelPatcher
* Implement basic MemoryCounter system for determing with cached weights due to hooks should be offloaded in hooks_backup
* Modified ControlNet/T2IAdapter get_control function to receive transformer_options as additional parameter, made the model_options stored in extra_args in inner_sample be a clone of the original model_options instead of same ref
* Added create_model_options_clone func, modified type annotations to use __future__ so that I can use the better type annotations
* Refactored WrapperExecutor code to remove need for WrapperClassExecutor (now gone), added sampler.sample wrapper (pending review, will likely keep but will see what hacks this could currently let me get rid of in ACN/ADE)
* Added Combine versions of Cond/Cond Pair Set Props nodes, renamed Pair Cond to Cond Pair, fixed default conds never applying hooks (due to hooks key typo)
* Renamed Create Hook Model As LoRA nodes to make the test node the main one (more changes pending)
* Added uuid to conds in CFGGuider and uuids to transformer_options to allow uniquely identifying conds in batches during sampling
* Fixed models not being unloaded properly due to current_patcher reference; the current ComfyUI model cleanup code requires that nothing else has a reference to the ModelPatcher instances
* Fixed default conds not respecting hook keyframes, made keyframes not reset cache when strength is unchanged, fixed Cond Set Default Combine throwing error, fixed model-as-lora throwing error during calculate_weight after a recent ComfyUI update, small refactoring/scaffolding changes for hooks
* Changed CreateHookModelAsLoraTest to be the new CreateHookModelAsLora, rename old ones as 'direct' and will be removed prior to merge
* Added initial support within CLIP Text Encode (Prompt) node for scheduling weight hook CLIP strength via clip_start_percent/clip_end_percent on conds, added schedule_clip toggle to Set CLIP Hooks node, small cleanup/fixes
* Fix range check in get_hooks_for_clip_schedule so that proper keyframes get assigned to corresponding ranges
* Optimized CLIP hook scheduling to treat same strength as same keyframe
* Less fragile memory management.
* Make encode_from_tokens_scheduled call cleaner, rollback change in model_patcher.py for hook_patches_backup dict
* Fix issue.
* Remove useless function.
* Prevent and detect some types of memory leaks.
* Run garbage collector when switching workflow if needed.
* Moved WrappersMP/CallbacksMP/WrapperExecutor to patcher_extension.py
* Refactored code to store wrappers and callbacks in transformer_options, added apply_model and diffusion_model.forward wrappers
* Fix issue.
* Refactored hooks in calc_cond_batch to be part of get_area_and_mult tuple, added extra_hooks to ControlBase to allow custom controlnets w/ hooks, small cleanup and renaming
* Fixed inconsistency of results when schedule_clip is set to False, small renaming/typo fixing, added initial support for ControlNet extra_hooks to work in tandem with normal cond hooks, initial work on calc_cond_batch merging all subdicts in returned transformer_options
* Modified callbacks and wrappers so that unregistered types can be used, allowing custom_nodes to have their own unique callbacks/wrappers if desired
* Updated different hook types to reflect actual progress of implementation, initial scaffolding for working WrapperHook functionality
* Fixed existing weight hook_patches (pre-registered) not working properly for CLIP
* Removed Register/Direct hook nodes since they were present only for testing, removed diff-related weight hook calculation as improved_memory removes unload_model_clones and using sample time registered hooks is less hacky
* Added clip scheduling support to all other native ComfyUI text encoding nodes (sdxl, flux, hunyuan, sd3)
* Made WrapperHook functional, added another wrapper/callback getter, added ON_DETACH callback to ModelPatcher
* Made opt_hooks append by default instead of replace, renamed comfy.hooks set functions to be more accurate
* Added apply_to_conds to Set CLIP Hooks, modified relevant code to allow text encoding to automatically apply hooks to output conds when apply_to_conds is set to True
* Fix cached_hook_patches not respecting target_device/memory_counter results
* Fixed issue with setting weights from hooks instead of copying them, added additional memory_counter check when caching hook patches
* Remove unnecessary torch.no_grad calls for hook patches
* Increased MemoryCounter minimum memory to leave free by *2 until a better way to get inference memory estimate of currently loaded models exists
* For encode_from_tokens_scheduled, allow start_percent and end_percent in add_dict to limit which scheduled conds get encoded for optimization purposes
* Removed a .to call on results of calculate_weight in patch_hook_weight_to_device that was screwing up the intermediate results for fp8 prior to being passed into stochastic_rounding call
* Made encode_from_tokens_scheduled work when no hooks are set on patcher
* Small cleanup of comments
* Turn off hook patch caching when only 1 hook present in sampling, replace some current_hook = None with calls to self.patch_hooks(None) instead to avoid a potential edge case
* On Cond/Cond Pair nodes, removed opt_ prefix from optional inputs
* Allow both FLOATS and FLOAT for floats_strength input
* Revert change, does not work
* Made patch_hook_weight_to_device respect set_func and convert_func
* Make discard_model_sampling True by default
* Add changes manually from 'master' so merge conflict resolution goes more smoothly
* Cleaned up text encode nodes with just a single clip.encode_from_tokens_scheduled call
* Make sure encode_from_tokens_scheduled will respect use_clip_schedule on clip
* Made nodes in nodes_hooks be marked as experimental (beta)
* Add get_nested_additional_models for cases where additional_models could have their own additional_models, and add robustness for circular additional_models references
* Made finalize_default_conds area math consistent with other sampling code
* Changed 'opt_hooks' input of Cond/Cond Pair Set Default Combine nodes to 'hooks'
* Remove a couple old TODO's and a no longer necessary workaround
2024-12-02 13:51:02 -06:00
return ( clip . encode_from_tokens_scheduled ( tokens ) , )
2024-12-27 18:02:21 -05:00
2023-01-26 12:06:48 -05:00
class ConditioningCombine :
2026-03-15 16:18:04 -07:00
ESSENTIALS_CATEGORY = " Image Generation "
2023-01-26 12:06:48 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning_1 " : ( " CONDITIONING " , ) , " conditioning_2 " : ( " CONDITIONING " , ) } }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " combine "
2023-01-26 12:23:15 -05:00
CATEGORY = " conditioning "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " combine " , " merge conditioning " , " combine prompts " , " merge prompts " , " mix prompts " , " add prompt " ]
2023-01-26 12:23:15 -05:00
2023-01-26 12:06:48 -05:00
def combine ( self , conditioning_1 , conditioning_2 ) :
return ( conditioning_1 + conditioning_2 , )
2023-04-30 17:33:15 -04:00
class ConditioningAverage :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " blend prompts " , " interpolate conditioning " , " mix prompts " , " style fusion " , " weighted blend " ]
2023-04-30 17:33:15 -04:00
@classmethod
def INPUT_TYPES ( s ) :
2023-04-30 17:28:55 -04:00
return { " required " : { " conditioning_to " : ( " CONDITIONING " , ) , " conditioning_from " : ( " CONDITIONING " , ) ,
" conditioning_to_strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.01 } )
2023-04-30 17:33:15 -04:00
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " addWeighted "
CATEGORY = " conditioning "
2023-04-30 17:28:55 -04:00
def addWeighted ( self , conditioning_to , conditioning_from , conditioning_to_strength ) :
2023-04-30 17:33:15 -04:00
out = [ ]
2023-04-30 17:28:55 -04:00
if len ( conditioning_from ) > 1 :
2024-03-11 00:56:41 -04:00
logging . warning ( " Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to. " )
2023-04-30 17:28:55 -04:00
cond_from = conditioning_from [ 0 ] [ 0 ]
2023-07-03 21:44:37 -04:00
pooled_output_from = conditioning_from [ 0 ] [ 1 ] . get ( " pooled_output " , None )
2023-04-30 17:28:55 -04:00
for i in range ( len ( conditioning_to ) ) :
t1 = conditioning_to [ i ] [ 0 ]
2023-07-03 21:44:37 -04:00
pooled_output_to = conditioning_to [ i ] [ 1 ] . get ( " pooled_output " , pooled_output_from )
2023-04-30 17:28:55 -04:00
t0 = cond_from [ : , : t1 . shape [ 1 ] ]
if t0 . shape [ 1 ] < t1 . shape [ 1 ] :
t0 = torch . cat ( [ t0 ] + [ torch . zeros ( ( 1 , ( t1 . shape [ 1 ] - t0 . shape [ 1 ] ) , t1 . shape [ 2 ] ) ) ] , dim = 1 )
tw = torch . mul ( t1 , conditioning_to_strength ) + torch . mul ( t0 , ( 1.0 - conditioning_to_strength ) )
2023-07-03 21:44:37 -04:00
t_to = conditioning_to [ i ] [ 1 ] . copy ( )
if pooled_output_from is not None and pooled_output_to is not None :
t_to [ " pooled_output " ] = torch . mul ( pooled_output_to , conditioning_to_strength ) + torch . mul ( pooled_output_from , ( 1.0 - conditioning_to_strength ) )
elif pooled_output_from is not None :
t_to [ " pooled_output " ] = pooled_output_from
n = [ tw , t_to ]
2023-04-30 17:33:15 -04:00
out . append ( n )
return ( out , )
2023-07-05 17:40:22 -04:00
class ConditioningConcat :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : {
" conditioning_to " : ( " CONDITIONING " , ) ,
" conditioning_from " : ( " CONDITIONING " , ) ,
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " concat "
2023-07-13 21:43:22 -04:00
CATEGORY = " conditioning "
2023-07-05 17:40:22 -04:00
def concat ( self , conditioning_to , conditioning_from ) :
out = [ ]
if len ( conditioning_from ) > 1 :
2024-03-11 00:56:41 -04:00
logging . warning ( " Warning: ConditioningConcat conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to. " )
2023-07-05 17:40:22 -04:00
cond_from = conditioning_from [ 0 ] [ 0 ]
for i in range ( len ( conditioning_to ) ) :
t1 = conditioning_to [ i ] [ 0 ]
tw = torch . cat ( ( t1 , cond_from ) , 1 )
n = [ tw , conditioning_to [ i ] [ 1 ] . copy ( ) ]
out . append ( n )
return ( out , )
2023-01-26 12:06:48 -05:00
class ConditioningSetArea :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " regional prompt " , " area prompt " , " spatial conditioning " , " localized prompt " ]
2023-01-26 12:06:48 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
2023-05-02 14:16:27 -04:00
" width " : ( " INT " , { " default " : 64 , " min " : 64 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" height " : ( " INT " , { " default " : 64 , " min " : 64 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" x " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" y " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
2023-01-26 12:06:48 -05:00
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } ) ,
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " append "
2023-01-26 12:23:15 -05:00
CATEGORY = " conditioning "
2023-05-06 19:00:49 -04:00
def append ( self , conditioning , width , height , x , y , strength ) :
2024-04-07 14:27:40 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " area " : ( height / / 8 , width / / 8 , y / / 8 , x / / 8 ) ,
" strength " : strength ,
" set_area_to_bounds " : False } )
2023-01-26 12:06:48 -05:00
return ( c , )
2023-01-03 01:53:32 -05:00
2023-09-06 03:26:55 -04:00
class ConditioningSetAreaPercentage :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" width " : ( " FLOAT " , { " default " : 1.0 , " min " : 0 , " max " : 1.0 , " step " : 0.01 } ) ,
" height " : ( " FLOAT " , { " default " : 1.0 , " min " : 0 , " max " : 1.0 , " step " : 0.01 } ) ,
" x " : ( " FLOAT " , { " default " : 0 , " min " : 0 , " max " : 1.0 , " step " : 0.01 } ) ,
" y " : ( " FLOAT " , { " default " : 0 , " min " : 0 , " max " : 1.0 , " step " : 0.01 } ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } ) ,
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " append "
CATEGORY = " conditioning "
def append ( self , conditioning , width , height , x , y , strength ) :
2024-04-07 14:27:40 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " area " : ( " percentage " , height , width , y , x ) ,
" strength " : strength ,
" set_area_to_bounds " : False } )
2023-09-06 03:26:55 -04:00
return ( c , )
2024-01-29 00:24:53 -05:00
class ConditioningSetAreaStrength :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } ) ,
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " append "
CATEGORY = " conditioning "
def append ( self , conditioning , strength ) :
2024-04-07 14:27:40 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " strength " : strength } )
2024-01-29 00:24:53 -05:00
return ( c , )
2023-04-25 00:15:25 -07:00
class ConditioningSetMask :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " masked prompt " , " regional inpaint conditioning " , " mask conditioning " ]
2023-04-25 00:15:25 -07:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" mask " : ( " MASK " , ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } ) ,
2023-04-29 20:19:14 -04:00
" set_cond_area " : ( [ " default " , " mask bounds " ] , ) ,
2023-04-25 00:15:25 -07:00
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " append "
CATEGORY = " conditioning "
2023-04-29 20:19:14 -04:00
def append ( self , conditioning , mask , set_cond_area , strength ) :
set_area_to_bounds = False
if set_cond_area != " default " :
set_area_to_bounds = True
2023-04-25 00:15:25 -07:00
if len ( mask . shape ) < 3 :
mask = mask . unsqueeze ( 0 )
2024-04-07 14:27:40 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " mask " : mask ,
" set_area_to_bounds " : set_area_to_bounds ,
" mask_strength " : strength } )
2023-04-25 00:15:25 -07:00
return ( c , )
2023-06-27 23:30:52 -04:00
class ConditioningZeroOut :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " null conditioning " , " clear conditioning " ]
2023-06-27 23:30:52 -04:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) } }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " zero_out "
CATEGORY = " advanced/conditioning "
def zero_out ( self , conditioning ) :
c = [ ]
for t in conditioning :
d = t [ 1 ] . copy ( )
2024-07-09 11:52:31 -04:00
pooled_output = d . get ( " pooled_output " , None )
if pooled_output is not None :
d [ " pooled_output " ] = torch . zeros_like ( pooled_output )
2025-05-07 05:33:34 -07:00
conditioning_lyrics = d . get ( " conditioning_lyrics " , None )
if conditioning_lyrics is not None :
d [ " conditioning_lyrics " ] = torch . zeros_like ( conditioning_lyrics )
2023-06-27 23:30:52 -04:00
n = [ torch . zeros_like ( t [ 0 ] ) , d ]
c . append ( n )
return ( c , )
2023-07-24 09:25:02 -04:00
class ConditioningSetTimestepRange :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
2023-07-24 18:29:00 -04:00
" start " : ( " FLOAT " , { " default " : 0.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.001 } ) ,
" end " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.001 } )
2023-07-24 09:25:02 -04:00
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " set_range "
CATEGORY = " advanced/conditioning "
def set_range ( self , conditioning , start , end ) :
2024-04-07 14:40:43 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " start_percent " : start ,
" end_percent " : end } )
2023-07-24 09:25:02 -04:00
return ( c , )
2023-01-03 01:53:32 -05:00
class VAEDecode :
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
2024-12-31 03:16:37 -05:00
" required " : {
" samples " : ( " LATENT " , { " tooltip " : " The latent to be decoded. " } ) ,
2024-08-14 06:22:10 +01:00
" vae " : ( " VAE " , { " tooltip " : " The VAE model used for decoding the latent. " } )
}
}
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " IMAGE " , )
2024-08-14 06:22:10 +01:00
OUTPUT_TOOLTIPS = ( " The decoded image. " , )
2023-01-03 01:53:32 -05:00
FUNCTION = " decode "
2023-01-26 12:23:15 -05:00
CATEGORY = " latent "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Decodes latent images back into pixel space images. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " decode " , " decode latent " , " latent to image " , " render latent " ]
2023-01-26 12:23:15 -05:00
2023-01-03 01:53:32 -05:00
def decode ( self , vae , samples ) :
2026-01-04 22:58:59 -08:00
latent = samples [ " samples " ]
if latent . is_nested :
latent = latent . unbind ( ) [ 0 ]
images = vae . decode ( latent )
2024-10-26 06:54:00 -04:00
if len ( images . shape ) == 5 : #Combine batches
images = images . reshape ( - 1 , images . shape [ - 3 ] , images . shape [ - 2 ] , images . shape [ - 1 ] )
return ( images , )
2023-01-03 01:53:32 -05:00
2023-02-24 02:10:10 -05:00
class VAEDecodeTiled :
@classmethod
def INPUT_TYPES ( s ) :
2023-08-28 19:57:22 +05:30
return { " required " : { " samples " : ( " LATENT " , ) , " vae " : ( " VAE " , ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" tile_size " : ( " INT " , { " default " : 512 , " min " : 64 , " max " : 4096 , " step " : 32 , " advanced " : True } ) ,
" overlap " : ( " INT " , { " default " : 64 , " min " : 0 , " max " : 4096 , " step " : 32 , " advanced " : True } ) ,
" temporal_size " : ( " INT " , { " default " : 64 , " min " : 8 , " max " : 4096 , " step " : 4 , " tooltip " : " Only used for video VAEs: Amount of frames to decode at a time. " , " advanced " : True } ) ,
" temporal_overlap " : ( " INT " , { " default " : 8 , " min " : 4 , " max " : 4096 , " step " : 4 , " tooltip " : " Only used for video VAEs: Amount of frames to overlap. " , " advanced " : True } ) ,
2023-08-28 19:57:22 +05:30
} }
2023-02-24 02:10:10 -05:00
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " decode "
CATEGORY = " _for_testing "
2024-12-23 20:03:37 -05:00
def decode ( self , vae , samples , tile_size , overlap = 64 , temporal_size = 64 , temporal_overlap = 8 ) :
2024-11-07 03:47:12 -05:00
if tile_size < overlap * 4 :
overlap = tile_size / / 4
2024-12-24 07:36:30 -05:00
if temporal_size < temporal_overlap * 2 :
temporal_overlap = temporal_overlap / / 2
2024-12-23 20:03:37 -05:00
temporal_compression = vae . temporal_compression_decode ( )
if temporal_compression is not None :
temporal_size = max ( 2 , temporal_size / / temporal_compression )
2025-01-01 21:29:01 +00:00
temporal_overlap = max ( 1 , min ( temporal_size / / 2 , temporal_overlap / / temporal_compression ) )
2024-12-23 20:03:37 -05:00
else :
temporal_size = None
temporal_overlap = None
2024-11-22 18:00:34 -05:00
compression = vae . spacial_compression_decode ( )
2024-12-23 20:03:37 -05:00
images = vae . decode_tiled ( samples [ " samples " ] , tile_x = tile_size / / compression , tile_y = tile_size / / compression , overlap = overlap / / compression , tile_t = temporal_size , overlap_t = temporal_overlap )
2024-11-07 03:47:12 -05:00
if len ( images . shape ) == 5 : #Combine batches
images = images . reshape ( - 1 , images . shape [ - 3 ] , images . shape [ - 2 ] , images . shape [ - 1 ] )
return ( images , )
2023-02-24 02:10:10 -05:00
2023-01-03 01:53:32 -05:00
class VAEEncode :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " pixels " : ( " IMAGE " , ) , " vae " : ( " VAE " , ) } }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " encode "
2023-01-26 12:23:15 -05:00
CATEGORY = " latent "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " encode " , " encode image " , " image to latent " ]
2023-01-26 12:23:15 -05:00
2023-05-02 14:16:27 -04:00
def encode ( self , vae , pixels ) :
2025-12-18 15:22:38 -08:00
t = vae . encode ( pixels )
2023-02-15 16:58:55 -05:00
return ( { " samples " : t } , )
2023-01-03 01:53:32 -05:00
2023-03-11 15:28:15 -05:00
class VAEEncodeTiled :
@classmethod
def INPUT_TYPES ( s ) :
2023-08-28 19:57:22 +05:30
return { " required " : { " pixels " : ( " IMAGE " , ) , " vae " : ( " VAE " , ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" tile_size " : ( " INT " , { " default " : 512 , " min " : 64 , " max " : 4096 , " step " : 64 , " advanced " : True } ) ,
" overlap " : ( " INT " , { " default " : 64 , " min " : 0 , " max " : 4096 , " step " : 32 , " advanced " : True } ) ,
" 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. " , " advanced " : True } ) ,
" temporal_overlap " : ( " INT " , { " default " : 8 , " min " : 4 , " max " : 4096 , " step " : 4 , " tooltip " : " Only used for video VAEs: Amount of frames to overlap. " , " advanced " : True } ) ,
2023-08-28 19:57:22 +05:30
} }
2023-03-11 15:28:15 -05:00
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " encode "
CATEGORY = " _for_testing "
2024-12-24 07:10:09 -05:00
def encode ( self , vae , pixels , tile_size , overlap , temporal_size = 64 , temporal_overlap = 8 ) :
2025-12-18 15:22:38 -08:00
t = vae . encode_tiled ( pixels , tile_x = tile_size , tile_y = tile_size , overlap = overlap , tile_t = temporal_size , overlap_t = temporal_overlap )
2024-12-24 07:10:09 -05:00
return ( { " samples " : t } , )
2023-05-02 14:16:27 -04:00
2023-02-15 20:44:51 -05:00
class VAEEncodeForInpaint :
@classmethod
def INPUT_TYPES ( s ) :
2023-05-02 00:53:15 -04:00
return { " required " : { " pixels " : ( " IMAGE " , ) , " vae " : ( " VAE " , ) , " mask " : ( " MASK " , ) , " grow_mask_by " : ( " INT " , { " default " : 6 , " min " : 0 , " max " : 64 , " step " : 1 } ) , } }
2023-02-15 20:44:51 -05:00
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " encode "
CATEGORY = " latent/inpaint "
2023-05-02 00:53:15 -04:00
def encode ( self , vae , pixels , mask , grow_mask_by = 6 ) :
2026-01-08 20:34:48 -08:00
downscale_ratio = vae . spacial_compression_encode ( )
x = ( pixels . shape [ 1 ] / / downscale_ratio ) * downscale_ratio
y = ( pixels . shape [ 2 ] / / downscale_ratio ) * downscale_ratio
2023-04-25 01:12:40 -04:00
mask = torch . nn . functional . interpolate ( mask . reshape ( ( - 1 , 1 , mask . shape [ - 2 ] , mask . shape [ - 1 ] ) ) , size = ( pixels . shape [ 1 ] , pixels . shape [ 2 ] ) , mode = " bilinear " )
2023-02-27 12:02:23 -05:00
2023-03-16 17:10:08 -04:00
pixels = pixels . clone ( )
2023-02-15 20:44:51 -05:00
if pixels . shape [ 1 ] != x or pixels . shape [ 2 ] != y :
2026-01-08 20:34:48 -08:00
x_offset = ( pixels . shape [ 1 ] % downscale_ratio ) / / 2
y_offset = ( pixels . shape [ 2 ] % downscale_ratio ) / / 2
2023-05-02 14:16:27 -04:00
pixels = pixels [ : , x_offset : x + x_offset , y_offset : y + y_offset , : ]
mask = mask [ : , : , x_offset : x + x_offset , y_offset : y + y_offset ]
2023-02-15 20:44:51 -05:00
2023-02-27 12:02:23 -05:00
#grow mask by a few pixels to keep things seamless in latent space
2023-05-02 00:53:15 -04:00
if grow_mask_by == 0 :
mask_erosion = mask
else :
kernel_tensor = torch . ones ( ( 1 , 1 , grow_mask_by , grow_mask_by ) )
padding = math . ceil ( ( grow_mask_by - 1 ) / 2 )
mask_erosion = torch . clamp ( torch . nn . functional . conv2d ( mask . round ( ) , kernel_tensor , padding = padding ) , 0 , 1 )
2023-04-22 16:02:26 -07:00
m = ( 1.0 - mask . round ( ) ) . squeeze ( 1 )
2023-02-15 20:44:51 -05:00
for i in range ( 3 ) :
pixels [ : , : , : , i ] - = 0.5
2023-02-27 12:02:23 -05:00
pixels [ : , : , : , i ] * = m
2023-02-15 20:44:51 -05:00
pixels [ : , : , : , i ] + = 0.5
t = vae . encode ( pixels )
2023-04-25 01:12:40 -04:00
return ( { " samples " : t , " noise_mask " : ( mask_erosion [ : , : , : x , : y ] . round ( ) ) } , )
2023-01-03 01:53:32 -05:00
2024-01-11 03:15:27 -05:00
class InpaintModelConditioning :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " positive " : ( " CONDITIONING " , ) ,
" negative " : ( " CONDITIONING " , ) ,
" vae " : ( " VAE " , ) ,
" pixels " : ( " IMAGE " , ) ,
" mask " : ( " MASK " , ) ,
2024-11-19 15:31:09 -05:00
" noise_mask " : ( " BOOLEAN " , { " default " : True , " tooltip " : " Add a noise mask to the latent so sampling will only happen within the mask. Might improve results or completely break things depending on the model. " } ) ,
2024-01-11 03:15:27 -05:00
} }
RETURN_TYPES = ( " CONDITIONING " , " CONDITIONING " , " LATENT " )
RETURN_NAMES = ( " positive " , " negative " , " latent " )
FUNCTION = " encode "
CATEGORY = " conditioning/inpaint "
2024-11-29 10:30:28 +09:00
def encode ( self , positive , negative , pixels , vae , mask , noise_mask = True ) :
2024-01-11 03:15:27 -05:00
x = ( pixels . shape [ 1 ] / / 8 ) * 8
y = ( pixels . shape [ 2 ] / / 8 ) * 8
mask = torch . nn . functional . interpolate ( mask . reshape ( ( - 1 , 1 , mask . shape [ - 2 ] , mask . shape [ - 1 ] ) ) , size = ( pixels . shape [ 1 ] , pixels . shape [ 2 ] ) , mode = " bilinear " )
orig_pixels = pixels
pixels = orig_pixels . clone ( )
if pixels . shape [ 1 ] != x or pixels . shape [ 2 ] != y :
x_offset = ( pixels . shape [ 1 ] % 8 ) / / 2
y_offset = ( pixels . shape [ 2 ] % 8 ) / / 2
pixels = pixels [ : , x_offset : x + x_offset , y_offset : y + y_offset , : ]
mask = mask [ : , : , x_offset : x + x_offset , y_offset : y + y_offset ]
m = ( 1.0 - mask . round ( ) ) . squeeze ( 1 )
for i in range ( 3 ) :
pixels [ : , : , : , i ] - = 0.5
pixels [ : , : , : , i ] * = m
pixels [ : , : , : , i ] + = 0.5
concat_latent = vae . encode ( pixels )
orig_latent = vae . encode ( orig_pixels )
out_latent = { }
out_latent [ " samples " ] = orig_latent
2024-11-19 15:31:09 -05:00
if noise_mask :
2024-11-19 07:31:29 -05:00
out_latent [ " noise_mask " ] = mask
2024-01-11 03:15:27 -05:00
out = [ ]
for conditioning in [ positive , negative ] :
2024-04-07 14:40:43 -04:00
c = node_helpers . conditioning_set_values ( conditioning , { " concat_latent_image " : concat_latent ,
" concat_mask " : mask } )
2024-01-11 03:15:27 -05:00
out . append ( c )
return ( out [ 0 ] , out [ 1 ] , out_latent )
2023-05-18 12:40:28 +09:00
class SaveLatent :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " export latent " ]
2023-05-18 12:40:28 +09:00
def __init__ ( self ) :
2023-05-17 23:43:59 -04:00
self . output_dir = folder_paths . get_output_directory ( )
2023-05-18 12:40:28 +09:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
2023-05-17 23:43:59 -04:00
" filename_prefix " : ( " STRING " , { " default " : " latents/ComfyUI " } ) } ,
2023-05-18 12:40:28 +09:00
" hidden " : { " prompt " : " PROMPT " , " extra_pnginfo " : " EXTRA_PNGINFO " } ,
}
RETURN_TYPES = ( )
FUNCTION = " save "
OUTPUT_NODE = True
CATEGORY = " _for_testing "
def save ( self , samples , filename_prefix = " ComfyUI " , prompt = None , extra_pnginfo = None ) :
2023-05-17 23:43:59 -04:00
full_output_folder , filename , counter , subfolder , filename_prefix = folder_paths . get_save_image_path ( filename_prefix , self . output_dir )
2023-05-18 12:40:28 +09:00
# support save metadata for latent sharing
prompt_info = " "
if prompt is not None :
prompt_info = json . dumps ( prompt )
2023-07-28 12:31:41 -04:00
metadata = None
if not args . disable_metadata :
metadata = { " prompt " : prompt_info }
if extra_pnginfo is not None :
for x in extra_pnginfo :
metadata [ x ] = json . dumps ( extra_pnginfo [ x ] )
2023-05-18 12:40:28 +09:00
file = f " { filename } _ { counter : 05 } _.latent "
2023-07-30 16:36:55 -05:00
2025-03-05 15:35:26 -05:00
results : list [ FileLocator ] = [ ]
2023-07-30 16:36:55 -05:00
results . append ( {
" filename " : file ,
" subfolder " : subfolder ,
" type " : " output "
} )
2023-05-18 12:40:28 +09:00
file = os . path . join ( full_output_folder , file )
2023-05-17 23:04:40 -04:00
output = { }
2025-03-11 15:07:00 -04:00
output [ " latent_tensor " ] = samples [ " samples " ] . contiguous ( )
2023-06-23 02:14:12 -04:00
output [ " latent_format_version_0 " ] = torch . tensor ( [ ] )
2023-05-17 23:04:40 -04:00
2023-06-26 12:21:07 -04:00
comfy . utils . save_torch_file ( output , file , metadata = metadata )
2023-07-30 16:36:55 -05:00
return { " ui " : { " latents " : results } }
2023-05-18 12:40:28 +09:00
class LoadLatent :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " import latent " , " open latent " ]
2023-05-18 12:40:28 +09:00
@classmethod
def INPUT_TYPES ( s ) :
2023-05-17 23:43:59 -04:00
input_dir = folder_paths . get_input_directory ( )
files = [ f for f in os . listdir ( input_dir ) if os . path . isfile ( os . path . join ( input_dir , f ) ) and f . endswith ( " .latent " ) ]
2023-05-18 12:40:28 +09:00
return { " required " : { " latent " : [ sorted ( files ) , ] } , }
CATEGORY = " _for_testing "
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " load "
def load ( self , latent ) :
2023-05-17 23:43:59 -04:00
latent_path = folder_paths . get_annotated_filepath ( latent )
latent = safetensors . torch . load_file ( latent_path , device = " cpu " )
2023-06-23 02:14:12 -04:00
multiplier = 1.0
if " latent_format_version_0 " not in latent :
multiplier = 1.0 / 0.18215
samples = { " samples " : latent [ " latent_tensor " ] . float ( ) * multiplier }
2023-05-17 23:04:40 -04:00
return ( samples , )
2023-05-18 12:40:28 +09:00
2023-05-17 23:43:59 -04:00
@classmethod
def IS_CHANGED ( s , latent ) :
image_path = folder_paths . get_annotated_filepath ( latent )
m = hashlib . sha256 ( )
with open ( image_path , ' rb ' ) as f :
m . update ( f . read ( ) )
return m . digest ( ) . hex ( )
@classmethod
def VALIDATE_INPUTS ( s , latent ) :
if not folder_paths . exists_annotated_filepath ( latent ) :
return " Invalid latent file: {} " . format ( latent )
return True
2023-05-18 12:40:28 +09:00
2023-01-03 01:53:32 -05:00
class CheckpointLoader :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " load model " , " model loader " ]
2023-01-03 01:53:32 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2023-03-17 17:57:57 -04:00
return { " required " : { " config_name " : ( folder_paths . get_filename_list ( " configs " ) , ) ,
" ckpt_name " : ( folder_paths . get_filename_list ( " checkpoints " ) , ) } }
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " MODEL " , " CLIP " , " VAE " )
FUNCTION = " load_checkpoint "
2023-04-04 22:48:11 -04:00
CATEGORY = " advanced/loaders "
2024-09-12 20:27:07 -04:00
DEPRECATED = True
2023-01-26 12:23:15 -05:00
2024-06-06 14:49:45 -04:00
def load_checkpoint ( self , config_name , ckpt_name ) :
2023-03-17 17:57:57 -04:00
config_path = folder_paths . get_full_path ( " configs " , config_name )
2024-09-17 16:57:17 +09:00
ckpt_path = folder_paths . get_full_path_or_raise ( " checkpoints " , ckpt_name )
2023-03-18 03:08:43 -04:00
return comfy . sd . load_checkpoint ( config_path , ckpt_path , output_vae = True , output_clip = True , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) )
2023-01-03 01:53:32 -05:00
2023-03-03 03:37:35 -05:00
class CheckpointLoaderSimple :
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
2024-12-31 03:16:37 -05:00
" required " : {
2024-08-14 06:22:10 +01:00
" ckpt_name " : ( folder_paths . get_filename_list ( " checkpoints " ) , { " tooltip " : " The name of the checkpoint (model) to load. " } ) ,
}
}
2023-03-03 03:37:35 -05:00
RETURN_TYPES = ( " MODEL " , " CLIP " , " VAE " )
2024-12-31 03:16:37 -05:00
OUTPUT_TOOLTIPS = ( " The model used for denoising latents. " ,
" The CLIP model used for encoding text prompts. " ,
2024-08-14 06:22:10 +01:00
" The VAE model used for encoding and decoding images to and from latent space. " )
2023-03-03 03:37:35 -05:00
FUNCTION = " load_checkpoint "
2023-03-03 13:09:44 -05:00
CATEGORY = " loaders "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Loads a diffusion model checkpoint, diffusion models are used to denoise latents. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " load model " , " checkpoint " , " model loader " , " load checkpoint " , " ckpt " , " model " ]
2023-03-03 03:37:35 -05:00
2024-06-06 14:49:45 -04:00
def load_checkpoint ( self , ckpt_name ) :
2024-09-17 16:57:17 +09:00
ckpt_path = folder_paths . get_full_path_or_raise ( " checkpoints " , ckpt_name )
2023-03-18 03:08:43 -04:00
out = comfy . sd . load_checkpoint_guess_config ( ckpt_path , output_vae = True , output_clip = True , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) )
2023-09-02 03:34:57 -04:00
return out [ : 3 ]
2023-03-03 03:37:35 -05:00
2023-04-05 23:57:31 -07:00
class DiffusersLoader :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " load diffusers model " ]
2023-04-05 23:57:31 -07:00
@classmethod
def INPUT_TYPES ( cls ) :
2023-04-06 00:24:52 -07:00
paths = [ ]
2023-04-06 22:02:26 -07:00
for search_path in folder_paths . get_folder_paths ( " diffusers " ) :
2023-04-06 21:48:58 -07:00
if os . path . exists ( search_path ) :
2023-05-15 03:25:24 -04:00
for root , subdir , files in os . walk ( search_path , followlinks = True ) :
if " model_index.json " in files :
paths . append ( os . path . relpath ( root , start = search_path ) )
2023-04-06 00:24:52 -07:00
return { " required " : { " model_path " : ( paths , ) , } }
2023-04-05 23:57:31 -07:00
RETURN_TYPES = ( " MODEL " , " CLIP " , " VAE " )
FUNCTION = " load_checkpoint "
2023-07-05 17:34:45 -04:00
CATEGORY = " advanced/loaders/deprecated "
2023-04-05 23:57:31 -07:00
def load_checkpoint ( self , model_path , output_vae = True , output_clip = True ) :
2023-04-06 22:02:26 -07:00
for search_path in folder_paths . get_folder_paths ( " diffusers " ) :
if os . path . exists ( search_path ) :
2023-05-15 03:25:24 -04:00
path = os . path . join ( search_path , model_path )
if os . path . exists ( path ) :
model_path = path
2023-04-06 22:02:26 -07:00
break
2023-04-07 01:28:15 -04:00
2023-08-30 12:55:07 -04:00
return comfy . diffusers_load . load_diffusers ( model_path , output_vae = output_vae , output_clip = output_clip , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) )
2023-04-05 23:57:31 -07:00
2023-04-01 23:19:15 -04:00
class unCLIPCheckpointLoader :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " ckpt_name " : ( folder_paths . get_filename_list ( " checkpoints " ) , ) ,
} }
RETURN_TYPES = ( " MODEL " , " CLIP " , " VAE " , " CLIP_VISION " )
FUNCTION = " load_checkpoint "
2023-04-04 22:48:11 -04:00
CATEGORY = " loaders "
2023-04-01 23:19:15 -04:00
def load_checkpoint ( self , ckpt_name , output_vae = True , output_clip = True ) :
2024-09-17 16:57:17 +09:00
ckpt_path = folder_paths . get_full_path_or_raise ( " checkpoints " , ckpt_name )
2023-04-01 23:19:15 -04:00
out = comfy . sd . load_checkpoint_guess_config ( ckpt_path , output_vae = True , output_clip = True , output_clipvision = True , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) )
return out
2023-03-03 13:04:36 -05:00
class CLIPSetLastLayer :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " clip " : ( " CLIP " , ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" stop_at_clip_layer " : ( " INT " , { " default " : - 1 , " min " : - 24 , " max " : - 1 , " step " : 1 , " advanced " : True } ) ,
2023-03-03 13:04:36 -05:00
} }
RETURN_TYPES = ( " CLIP " , )
FUNCTION = " set_last_layer "
CATEGORY = " conditioning "
def set_last_layer ( self , clip , stop_at_clip_layer ) :
clip = clip . clone ( )
clip . clip_layer ( stop_at_clip_layer )
return ( clip , )
2023-02-03 02:06:34 -05:00
class LoraLoader :
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
ESSENTIALS_CATEGORY = " Image Generation "
2023-06-29 23:40:02 -04:00
def __init__ ( self ) :
self . loaded_lora = None
2023-02-03 02:06:34 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
2024-12-31 03:16:37 -05:00
" required " : {
2024-08-14 06:22:10 +01:00
" model " : ( " MODEL " , { " tooltip " : " The diffusion model the LoRA will be applied to. " } ) ,
" clip " : ( " CLIP " , { " tooltip " : " The CLIP model the LoRA will be applied to. " } ) ,
" lora_name " : ( folder_paths . get_filename_list ( " loras " ) , { " tooltip " : " The name of the LoRA. " } ) ,
" 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_clip " : ( " FLOAT " , { " default " : 1.0 , " min " : - 100.0 , " max " : 100.0 , " step " : 0.01 , " tooltip " : " How strongly to modify the CLIP model. This value can be negative. " } ) ,
}
}
2024-12-27 18:02:21 -05:00
2023-02-03 02:06:34 -05:00
RETURN_TYPES = ( " MODEL " , " CLIP " )
2024-08-14 06:22:10 +01:00
OUTPUT_TOOLTIPS = ( " The modified diffusion model. " , " The modified CLIP model. " )
2023-02-03 02:06:34 -05:00
FUNCTION = " load_lora "
CATEGORY = " loaders "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " LoRAs are used to modify diffusion and CLIP models, altering the way in which latents are denoised such as applying styles. Multiple LoRA nodes can be linked together. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " lora " , " load lora " , " apply lora " , " lora loader " , " lora model " ]
2023-02-03 02:06:34 -05:00
def load_lora ( self , model , clip , lora_name , strength_model , strength_clip ) :
2023-05-26 19:33:30 -05:00
if strength_model == 0 and strength_clip == 0 :
return ( model , clip )
2024-09-17 16:57:17 +09:00
lora_path = folder_paths . get_full_path_or_raise ( " loras " , lora_name )
2023-06-29 23:40:02 -04:00
lora = None
if self . loaded_lora is not None :
if self . loaded_lora [ 0 ] == lora_path :
lora = self . loaded_lora [ 1 ]
else :
2023-07-15 17:11:12 -07:00
self . loaded_lora = None
2023-06-29 23:40:02 -04:00
if lora is None :
lora = comfy . utils . load_torch_file ( lora_path , safe_load = True )
self . loaded_lora = ( lora_path , lora )
model_lora , clip_lora = comfy . sd . load_lora_for_models ( model , clip , lora , strength_model , strength_clip )
2023-02-03 02:06:34 -05:00
return ( model_lora , clip_lora )
2023-11-25 02:26:50 -05:00
class LoraLoaderModelOnly ( LoraLoader ) :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " model " : ( " MODEL " , ) ,
" lora_name " : ( folder_paths . get_filename_list ( " loras " ) , ) ,
2024-04-23 13:07:39 -04:00
" strength_model " : ( " FLOAT " , { " default " : 1.0 , " min " : - 100.0 , " max " : 100.0 , " step " : 0.01 } ) ,
2023-11-25 02:26:50 -05:00
} }
RETURN_TYPES = ( " MODEL " , )
FUNCTION = " load_lora_model_only "
def load_lora_model_only ( self , model , lora_name , strength_model ) :
return ( self . load_lora ( model , None , lora_name , strength_model , 0 ) [ 0 ] , )
2023-01-03 01:53:32 -05:00
class VAELoader :
2026-01-22 06:03:51 +02:00
video_taes = [ " taehv " , " lighttaew2_2 " , " lighttaew2_1 " , " lighttaehy1_5 " , " taeltx_2 " ]
2026-04-29 17:37:30 -06:00
image_taes = [ " taesd " , " taesdxl " , " taesd3 " , " taef1 " , " taef2 " ]
2023-11-21 12:54:19 -05:00
@staticmethod
2025-11-29 02:40:19 +02:00
def vae_list ( s ) :
2023-11-21 12:54:19 -05:00
vaes = folder_paths . get_filename_list ( " vae " )
approx_vaes = folder_paths . get_filename_list ( " vae_approx " )
2026-04-29 17:37:30 -06:00
have_img_encoder , have_img_decoder = set ( ) , set ( )
2023-11-21 12:54:19 -05:00
for v in approx_vaes :
2026-04-29 17:37:30 -06:00
parts = v . split ( " _ " , 1 )
if len ( parts ) != 2 or parts [ 0 ] not in s . image_taes :
2025-11-29 02:40:19 +02:00
for tae in s . video_taes :
if v . startswith ( tae ) :
vaes . append ( v )
2026-04-29 17:37:30 -06:00
break
continue
if parts [ 1 ] . startswith ( " encoder. " ) :
have_img_encoder . add ( parts [ 0 ] )
elif parts [ 1 ] . startswith ( " decoder. " ) :
have_img_decoder . add ( parts [ 0 ] )
vaes + = [ k for k in have_img_decoder if k in have_img_encoder ]
2025-09-13 15:03:34 -07:00
vaes . append ( " pixel_space " )
2023-11-21 12:54:19 -05:00
return vaes
@staticmethod
def load_taesd ( name ) :
sd = { }
approx_vaes = folder_paths . get_filename_list ( " vae_approx " )
encoder = next ( filter ( lambda a : a . startswith ( " {} _encoder. " . format ( name ) ) , approx_vaes ) )
decoder = next ( filter ( lambda a : a . startswith ( " {} _decoder. " . format ( name ) ) , approx_vaes ) )
2024-09-17 16:57:17 +09:00
enc = comfy . utils . load_torch_file ( folder_paths . get_full_path_or_raise ( " vae_approx " , encoder ) )
2023-11-21 12:54:19 -05:00
for k in enc :
sd [ " taesd_encoder. {} " . format ( k ) ] = enc [ k ]
2024-09-17 16:57:17 +09:00
dec = comfy . utils . load_torch_file ( folder_paths . get_full_path_or_raise ( " vae_approx " , decoder ) )
2023-11-21 12:54:19 -05:00
for k in dec :
sd [ " taesd_decoder. {} " . format ( k ) ] = dec [ k ]
if name == " taesd " :
sd [ " vae_scale " ] = torch . tensor ( 0.18215 )
2024-06-16 02:04:24 -04:00
sd [ " vae_shift " ] = torch . tensor ( 0.0 )
2023-11-21 12:54:19 -05:00
elif name == " taesdxl " :
sd [ " vae_scale " ] = torch . tensor ( 0.13025 )
2024-06-16 02:04:24 -04:00
sd [ " vae_shift " ] = torch . tensor ( 0.0 )
2024-06-16 15:03:53 +09:00
elif name == " taesd3 " :
sd [ " vae_scale " ] = torch . tensor ( 1.5305 )
2024-06-16 02:04:24 -04:00
sd [ " vae_shift " ] = torch . tensor ( 0.0609 )
2024-08-16 12:53:13 -04:00
elif name == " taef1 " :
sd [ " vae_scale " ] = torch . tensor ( 0.3611 )
sd [ " vae_shift " ] = torch . tensor ( 0.1159 )
2023-11-21 12:54:19 -05:00
return sd
2023-01-03 01:53:32 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2025-11-29 02:40:19 +02:00
return { " required " : { " vae_name " : ( s . vae_list ( s ) , ) } }
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " VAE " , )
FUNCTION = " load_vae "
2023-01-26 12:23:15 -05:00
CATEGORY = " loaders "
2023-01-03 01:53:32 -05:00
#TODO: scale factor?
def load_vae ( self , vae_name ) :
2026-01-14 10:54:50 -08:00
metadata = None
2025-09-13 15:03:34 -07:00
if vae_name == " pixel_space " :
sd = { }
sd [ " pixel_space_vae " ] = torch . tensor ( 1.0 )
2025-11-29 02:40:19 +02:00
elif vae_name in self . image_taes :
2023-11-21 12:54:19 -05:00
sd = self . load_taesd ( vae_name )
else :
2025-11-29 02:40:19 +02:00
if os . path . splitext ( vae_name ) [ 0 ] in self . video_taes :
vae_path = folder_paths . get_full_path_or_raise ( " vae_approx " , vae_name )
else :
vae_path = folder_paths . get_full_path_or_raise ( " vae " , vae_name )
2026-01-14 00:37:21 +02:00
sd , metadata = comfy . utils . load_torch_file ( vae_path , return_metadata = True )
2026-04-29 17:37:30 -06:00
if vae_name == " taef2 " :
if metadata is None :
metadata = { " tae_latent_channels " : 128 }
else :
metadata [ " tae_latent_channels " ] = 128
2026-01-14 00:37:21 +02:00
vae = comfy . sd . VAE ( sd = sd , metadata = metadata )
2025-03-15 08:26:36 -04:00
vae . throw_exception_if_invalid ( )
2023-01-03 01:53:32 -05:00
return ( vae , )
2023-02-16 10:38:08 -05:00
class ControlNetLoader :
@classmethod
def INPUT_TYPES ( s ) :
2023-03-17 17:57:57 -04:00
return { " required " : { " control_net_name " : ( folder_paths . get_filename_list ( " controlnet " ) , ) } }
2023-02-16 10:38:08 -05:00
RETURN_TYPES = ( " CONTROL_NET " , )
FUNCTION = " load_controlnet "
CATEGORY = " loaders "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " controlnet " , " control net " , " cn " , " load controlnet " , " controlnet loader " ]
2023-02-16 10:38:08 -05:00
def load_controlnet ( self , control_net_name ) :
2024-09-17 16:57:17 +09:00
controlnet_path = folder_paths . get_full_path_or_raise ( " controlnet " , control_net_name )
2023-08-25 17:25:39 -04:00
controlnet = comfy . controlnet . load_controlnet ( controlnet_path )
2025-04-08 08:11:59 -04:00
if controlnet is None :
raise RuntimeError ( " ERROR: controlnet file is invalid and does not contain a valid controlnet model. " )
2023-02-16 10:38:08 -05:00
return ( controlnet , )
2023-02-22 23:22:03 -05:00
class DiffControlNetLoader :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " model " : ( " MODEL " , ) ,
2023-03-17 17:57:57 -04:00
" control_net_name " : ( folder_paths . get_filename_list ( " controlnet " ) , ) } }
2023-02-22 23:22:03 -05:00
RETURN_TYPES = ( " CONTROL_NET " , )
FUNCTION = " load_controlnet "
CATEGORY = " loaders "
def load_controlnet ( self , model , control_net_name ) :
2024-09-17 16:57:17 +09:00
controlnet_path = folder_paths . get_full_path_or_raise ( " controlnet " , control_net_name )
2023-08-25 17:25:39 -04:00
controlnet = comfy . controlnet . load_controlnet ( controlnet_path , model )
2023-02-22 23:22:03 -05:00
return ( controlnet , )
2023-02-16 10:38:08 -05:00
class ControlNetApply :
@classmethod
def INPUT_TYPES ( s ) :
2023-02-16 18:08:01 -05:00
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" control_net " : ( " CONTROL_NET " , ) ,
" image " : ( " IMAGE " , ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } )
} }
2023-02-16 10:38:08 -05:00
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " apply_controlnet "
2024-09-22 01:24:52 -04:00
DEPRECATED = True
2024-07-16 17:08:25 -04:00
CATEGORY = " conditioning/controlnet "
2023-02-16 10:38:08 -05:00
2023-02-16 18:08:01 -05:00
def apply_controlnet ( self , conditioning , control_net , image , strength ) :
2023-05-26 19:33:30 -05:00
if strength == 0 :
return ( conditioning , )
2023-02-16 10:38:08 -05:00
c = [ ]
control_hint = image . movedim ( - 1 , 1 )
for t in conditioning :
n = [ t [ 0 ] , t [ 1 ] . copy ( ) ]
2023-02-21 01:18:53 -05:00
c_net = control_net . copy ( ) . set_cond_hint ( control_hint , strength )
if ' control ' in t [ 1 ] :
c_net . set_previous_controlnet ( t [ 1 ] [ ' control ' ] )
n [ 1 ] [ ' control ' ] = c_net
2023-07-24 13:26:07 -04:00
n [ 1 ] [ ' control_apply_to_uncond ' ] = True
2023-02-16 10:38:08 -05:00
c . append ( n )
return ( c , )
2023-07-24 13:26:07 -04:00
class ControlNetApplyAdvanced :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " positive " : ( " CONDITIONING " , ) ,
" negative " : ( " CONDITIONING " , ) ,
" control_net " : ( " CONTROL_NET " , ) ,
" image " : ( " IMAGE " , ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.01 } ) ,
2023-07-24 18:29:00 -04:00
" start_percent " : ( " FLOAT " , { " default " : 0.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.001 } ) ,
" end_percent " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.001 } )
2024-09-22 01:24:52 -04:00
} ,
" optional " : { " vae " : ( " VAE " , ) ,
}
}
2023-07-24 13:26:07 -04:00
RETURN_TYPES = ( " CONDITIONING " , " CONDITIONING " )
RETURN_NAMES = ( " positive " , " negative " )
FUNCTION = " apply_controlnet "
2024-07-16 17:08:25 -04:00
CATEGORY = " conditioning/controlnet "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " controlnet " , " apply controlnet " , " use controlnet " , " control net " ]
2023-07-24 13:26:07 -04:00
2024-09-14 09:05:16 -04:00
def apply_controlnet ( self , positive , negative , control_net , image , strength , start_percent , end_percent , vae = None , extra_concat = [ ] ) :
2023-07-24 13:26:07 -04:00
if strength == 0 :
return ( positive , negative )
control_hint = image . movedim ( - 1 , 1 )
cnets = { }
out = [ ]
for conditioning in [ positive , negative ] :
c = [ ]
for t in conditioning :
d = t [ 1 ] . copy ( )
prev_cnet = d . get ( ' control ' , None )
if prev_cnet in cnets :
c_net = cnets [ prev_cnet ]
else :
2024-09-14 09:05:16 -04:00
c_net = control_net . copy ( ) . set_cond_hint ( control_hint , strength , ( start_percent , end_percent ) , vae = vae , extra_concat = extra_concat )
2023-07-24 13:26:07 -04:00
c_net . set_previous_controlnet ( prev_cnet )
cnets [ prev_cnet ] = c_net
d [ ' control ' ] = c_net
d [ ' control_apply_to_uncond ' ] = False
n = [ t [ 0 ] , d ]
c . append ( n )
out . append ( c )
return ( out [ 0 ] , out [ 1 ] )
2023-07-05 17:34:45 -04:00
class UNETLoader :
@classmethod
def INPUT_TYPES ( s ) :
2024-08-17 21:28:36 -04:00
return { " required " : { " unet_name " : ( folder_paths . get_filename_list ( " diffusion_models " ) , ) ,
2026-03-17 07:24:00 -07:00
" weight_dtype " : ( [ " default " , " fp8_e4m3fn " , " fp8_e4m3fn_fast " , " fp8_e5m2 " ] , { " advanced " : True } )
2023-07-05 17:34:45 -04:00
} }
RETURN_TYPES = ( " MODEL " , )
FUNCTION = " load_unet "
CATEGORY = " advanced/loaders "
2024-08-01 13:28:41 -04:00
def load_unet ( self , unet_name , weight_dtype ) :
2024-08-12 23:18:54 -04:00
model_options = { }
2024-08-01 22:19:53 -04:00
if weight_dtype == " fp8_e4m3fn " :
2024-08-12 23:18:54 -04:00
model_options [ " dtype " ] = torch . float8_e4m3fn
2024-10-09 19:43:17 -04:00
elif weight_dtype == " fp8_e4m3fn_fast " :
model_options [ " dtype " ] = torch . float8_e4m3fn
model_options [ " fp8_optimizations " ] = True
2024-08-01 22:19:53 -04:00
elif weight_dtype == " fp8_e5m2 " :
2024-08-12 23:18:54 -04:00
model_options [ " dtype " ] = torch . float8_e5m2
2024-08-01 22:19:53 -04:00
2024-09-17 16:57:17 +09:00
unet_path = folder_paths . get_full_path_or_raise ( " diffusion_models " , unet_name )
2024-08-12 23:18:54 -04:00
model = comfy . sd . load_diffusion_model ( unet_path , model_options = model_options )
2023-07-05 17:34:45 -04:00
return ( model , )
2023-02-05 15:20:18 -05:00
class CLIPLoader :
@classmethod
def INPUT_TYPES ( s ) :
2024-11-02 15:35:38 -04:00
return { " required " : { " clip_name " : ( folder_paths . get_filename_list ( " text_encoders " ) , ) ,
2026-02-28 05:04:34 +01:00
" type " : ( [ " stable_diffusion " , " stable_cascade " , " sd3 " , " stable_audio " , " mochi " , " ltxv " , " pixart " , " cosmos " , " lumina2 " , " wan " , " hidream " , " chroma " , " ace " , " omnigen2 " , " qwen_image " , " hunyuan_image " , " flux2 " , " ovis " , " longcat_image " ] , ) ,
2025-01-05 04:29:36 -05:00
} ,
" optional " : {
2025-01-05 01:46:11 -05:00
" device " : ( [ " default " , " cpu " ] , { " advanced " : True } ) ,
2023-02-05 15:20:18 -05:00
} }
RETURN_TYPES = ( " CLIP " , )
FUNCTION = " load_clip "
2023-06-25 01:40:38 -04:00
CATEGORY = " advanced/loaders "
2023-02-05 15:20:18 -05:00
2025-06-25 16:35:57 -07:00
DESCRIPTION = " [Recipes] \n \n stable_diffusion: clip-l \n stable_cascade: clip-g \n sd3: t5 xxl/ clip-g / clip-l \n stable_audio: t5 base \n mochi: t5 xxl \n cosmos: old t5 xxl \n lumina2: gemma 2 2B \n wan: umt5 xxl \n hidream: llama-3.1 (Recommend) or t5 \n omnigen2: qwen vl 2.5 3B "
2024-11-11 19:37:23 +09:00
2025-01-05 01:46:11 -05:00
def load_clip ( self , clip_name , type = " stable_diffusion " , device = " default " ) :
2025-04-18 08:53:36 +02:00
clip_type = getattr ( comfy . sd . CLIPType , type . upper ( ) , comfy . sd . CLIPType . STABLE_DIFFUSION )
2024-02-16 13:29:04 -05:00
2025-01-05 01:46:11 -05:00
model_options = { }
if device == " cpu " :
model_options [ " load_device " ] = model_options [ " offload_device " ] = torch . device ( " cpu " )
2024-11-02 15:35:38 -04:00
clip_path = folder_paths . get_full_path_or_raise ( " text_encoders " , clip_name )
2025-01-05 01:46:11 -05:00
clip = comfy . sd . load_clip ( ckpt_paths = [ clip_path ] , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) , clip_type = clip_type , model_options = model_options )
2023-06-25 01:40:38 -04:00
return ( clip , )
class DualCLIPLoader :
@classmethod
def INPUT_TYPES ( s ) :
2024-11-02 15:35:38 -04:00
return { " required " : { " clip_name1 " : ( folder_paths . get_filename_list ( " text_encoders " ) , ) ,
" clip_name2 " : ( folder_paths . get_filename_list ( " text_encoders " ) , ) ,
2026-02-02 21:06:18 -08:00
" type " : ( [ " sdxl " , " sd3 " , " flux " , " hunyuan_video " , " hidream " , " hunyuan_image " , " hunyuan_video_15 " , " kandinsky5 " , " kandinsky5_image " , " ltxv " , " newbie " , " ace " ] , ) ,
2025-01-05 04:29:36 -05:00
} ,
" optional " : {
2025-01-05 01:46:11 -05:00
" device " : ( [ " default " , " cpu " ] , { " advanced " : True } ) ,
2023-06-25 01:40:38 -04:00
} }
RETURN_TYPES = ( " CLIP " , )
FUNCTION = " load_clip "
CATEGORY = " advanced/loaders "
2025-12-20 13:57:22 +08:00
DESCRIPTION = " [Recipes] \n \n sdxl: clip-l, clip-g \n sd3: clip-l, clip-g / clip-l, t5 / clip-g, t5 \n flux: clip-l, t5 \n hidream: at least one of t5 or llama, recommended t5 and llama \n hunyuan_image: qwen2.5vl 7b and byt5 small \n newbie: gemma-3-4b-it, jina clip v2 "
2024-11-11 19:37:23 +09:00
2025-01-05 01:46:11 -05:00
def load_clip ( self , clip_name1 , clip_name2 , type , device = " default " ) :
2025-04-18 08:53:36 +02:00
clip_type = getattr ( comfy . sd . CLIPType , type . upper ( ) , comfy . sd . CLIPType . STABLE_DIFFUSION )
2024-11-02 15:35:38 -04:00
clip_path1 = folder_paths . get_full_path_or_raise ( " text_encoders " , clip_name1 )
clip_path2 = folder_paths . get_full_path_or_raise ( " text_encoders " , clip_name2 )
2024-06-11 23:27:39 -04:00
2025-01-05 01:46:11 -05:00
model_options = { }
if device == " cpu " :
model_options [ " load_device " ] = model_options [ " offload_device " ] = torch . device ( " cpu " )
clip = comfy . sd . load_clip ( ckpt_paths = [ clip_path1 , clip_path2 ] , embedding_directory = folder_paths . get_folder_paths ( " embeddings " ) , clip_type = clip_type , model_options = model_options )
2023-02-05 15:20:18 -05:00
return ( clip , )
2023-03-05 18:39:25 -05:00
class CLIPVisionLoader :
@classmethod
def INPUT_TYPES ( s ) :
2023-03-17 17:57:57 -04:00
return { " required " : { " clip_name " : ( folder_paths . get_filename_list ( " clip_vision " ) , ) ,
2023-03-05 18:39:25 -05:00
} }
RETURN_TYPES = ( " CLIP_VISION " , )
FUNCTION = " load_clip "
CATEGORY = " loaders "
def load_clip ( self , clip_name ) :
2024-09-17 16:57:17 +09:00
clip_path = folder_paths . get_full_path_or_raise ( " clip_vision " , clip_name )
2023-04-01 23:19:15 -04:00
clip_vision = comfy . clip_vision . load ( clip_path )
2025-04-06 22:43:56 -04:00
if clip_vision is None :
raise RuntimeError ( " ERROR: clip vision file is invalid and does not contain a valid vision model. " )
2023-03-05 18:39:25 -05:00
return ( clip_vision , )
class CLIPVisionEncode :
@classmethod
def INPUT_TYPES ( s ) :
2025-09-04 17:39:02 -07:00
return { " required " : { " clip_vision " : ( " CLIP_VISION " , ) ,
" image " : ( " IMAGE " , ) ,
" crop " : ( [ " center " , " none " ] , )
} }
2023-03-06 01:30:17 -05:00
RETURN_TYPES = ( " CLIP_VISION_OUTPUT " , )
2023-03-05 18:39:25 -05:00
FUNCTION = " encode "
2023-04-01 23:19:15 -04:00
CATEGORY = " conditioning "
2023-03-05 18:39:25 -05:00
2025-09-04 17:39:02 -07:00
def encode ( self , clip_vision , image , crop ) :
crop_image = True
if crop != " center " :
crop_image = False
output = clip_vision . encode_image ( image , crop = crop_image )
2023-03-05 18:39:25 -05:00
return ( output , )
class StyleModelLoader :
@classmethod
def INPUT_TYPES ( s ) :
2023-03-17 17:57:57 -04:00
return { " required " : { " style_model_name " : ( folder_paths . get_filename_list ( " style_models " ) , ) } }
2023-03-05 18:39:25 -05:00
RETURN_TYPES = ( " STYLE_MODEL " , )
FUNCTION = " load_style_model "
CATEGORY = " loaders "
def load_style_model ( self , style_model_name ) :
2024-09-17 16:57:17 +09:00
style_model_path = folder_paths . get_full_path_or_raise ( " style_models " , style_model_name )
2023-03-05 18:39:25 -05:00
style_model = comfy . sd . load_style_model ( style_model_path )
return ( style_model , )
class StyleModelApply :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " style transfer " ]
2023-03-05 18:39:25 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2023-03-06 01:48:18 -05:00
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" style_model " : ( " STYLE_MODEL " , ) ,
" clip_vision_output " : ( " CLIP_VISION_OUTPUT " , ) ,
2024-11-30 07:27:11 -05:00
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 10.0 , " step " : 0.001 } ) ,
2024-12-17 00:21:17 +01:00
" strength_type " : ( [ " multiply " , " attn_bias " ] , ) ,
2023-03-05 18:39:25 -05:00
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " apply_stylemodel "
2023-03-06 01:30:17 -05:00
CATEGORY = " conditioning/style_model "
2023-03-05 18:39:25 -05:00
2024-12-17 00:21:17 +01:00
def apply_stylemodel ( self , conditioning , style_model , clip_vision_output , strength , strength_type ) :
2023-08-14 16:54:05 -04:00
cond = style_model . get_cond ( clip_vision_output ) . flatten ( start_dim = 0 , end_dim = 1 ) . unsqueeze ( dim = 0 )
2024-11-30 07:27:11 -05:00
if strength_type == " multiply " :
cond * = strength
2024-12-17 00:21:17 +01:00
n = cond . shape [ 1 ]
c_out = [ ]
2023-03-06 01:48:18 -05:00
for t in conditioning :
2024-12-17 00:21:17 +01:00
( txt , keys ) = t
keys = keys . copy ( )
2025-02-06 22:51:16 +01:00
# even if the strength is 1.0 (i.e, no change), if there's already a mask, we have to add to it
2025-02-07 20:44:43 +01:00
if " attention_mask " in keys or ( strength_type == " attn_bias " and strength != 1.0 ) :
2024-12-17 00:21:17 +01:00
# math.log raises an error if the argument is zero
# torch.log returns -inf, which is what we want
2025-02-07 20:44:43 +01:00
attn_bias = torch . log ( torch . Tensor ( [ strength if strength_type == " attn_bias " else 1.0 ] ) )
2024-12-17 00:21:17 +01:00
# get the size of the mask image
mask_ref_size = keys . get ( " attention_mask_img_shape " , ( 1 , 1 ) )
n_ref = mask_ref_size [ 0 ] * mask_ref_size [ 1 ]
n_txt = txt . shape [ 1 ]
# grab the existing mask
mask = keys . get ( " attention_mask " , None )
# create a default mask if it doesn't exist
if mask is None :
mask = torch . zeros ( ( txt . shape [ 0 ] , n_txt + n_ref , n_txt + n_ref ) , dtype = torch . float16 )
# convert the mask dtype, because it might be boolean
# we want it to be interpreted as a bias
if mask . dtype == torch . bool :
# log(True) = log(1) = 0
# log(False) = log(0) = -inf
mask = torch . log ( mask . to ( dtype = torch . float16 ) )
# now we make the mask bigger to add space for our new tokens
new_mask = torch . zeros ( ( txt . shape [ 0 ] , n_txt + n + n_ref , n_txt + n + n_ref ) , dtype = torch . float16 )
# copy over the old mask, in quandrants
new_mask [ : , : n_txt , : n_txt ] = mask [ : , : n_txt , : n_txt ]
new_mask [ : , : n_txt , n_txt + n : ] = mask [ : , : n_txt , n_txt : ]
new_mask [ : , n_txt + n : , : n_txt ] = mask [ : , n_txt : , : n_txt ]
new_mask [ : , n_txt + n : , n_txt + n : ] = mask [ : , n_txt : , n_txt : ]
# now fill in the attention bias to our redux tokens
new_mask [ : , : n_txt , n_txt : n_txt + n ] = attn_bias
new_mask [ : , n_txt + n : , n_txt : n_txt + n ] = attn_bias
keys [ " attention_mask " ] = new_mask . to ( txt . device )
keys [ " attention_mask_img_shape " ] = mask_ref_size
c_out . append ( [ torch . cat ( ( txt , cond ) , dim = 1 ) , keys ] )
return ( c_out , )
2023-03-05 18:39:25 -05:00
2023-04-01 23:19:15 -04:00
class unCLIPConditioning :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning " : ( " CONDITIONING " , ) ,
" clip_vision_output " : ( " CLIP_VISION_OUTPUT " , ) ,
" strength " : ( " FLOAT " , { " default " : 1.0 , " min " : - 10.0 , " max " : 10.0 , " step " : 0.01 } ) ,
2023-04-03 13:50:29 -04:00
" noise_augmentation " : ( " FLOAT " , { " default " : 0.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.01 } ) ,
2023-04-01 23:19:15 -04:00
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " apply_adm "
2023-04-04 22:48:11 -04:00
CATEGORY = " conditioning "
2023-04-01 23:19:15 -04:00
2023-04-03 13:50:29 -04:00
def apply_adm ( self , conditioning , clip_vision_output , strength , noise_augmentation ) :
2023-05-26 19:33:30 -05:00
if strength == 0 :
return ( conditioning , )
2025-05-22 05:11:13 -07:00
c = node_helpers . conditioning_set_values ( conditioning , { " unclip_conditioning " : [ { " clip_vision_output " : clip_vision_output , " strength " : strength , " noise_augmentation " : noise_augmentation } ] } , append = True )
2023-04-01 23:19:15 -04:00
return ( c , )
2023-04-19 09:36:19 -04:00
class GLIGENLoader :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " gligen_name " : ( folder_paths . get_filename_list ( " gligen " ) , ) } }
RETURN_TYPES = ( " GLIGEN " , )
FUNCTION = " load_gligen "
2023-04-20 17:30:10 -04:00
CATEGORY = " loaders "
2023-04-19 09:36:19 -04:00
def load_gligen ( self , gligen_name ) :
2024-09-17 16:57:17 +09:00
gligen_path = folder_paths . get_full_path_or_raise ( " gligen " , gligen_name )
2023-04-19 09:36:19 -04:00
gligen = comfy . sd . load_gligen ( gligen_path )
return ( gligen , )
class GLIGENTextBoxApply :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " conditioning_to " : ( " CONDITIONING " , ) ,
" clip " : ( " CLIP " , ) ,
" gligen_textbox_model " : ( " GLIGEN " , ) ,
2024-04-13 16:12:09 -04:00
" text " : ( " STRING " , { " multiline " : True , " dynamicPrompts " : True } ) ,
2023-04-19 09:36:19 -04:00
" width " : ( " INT " , { " default " : 64 , " min " : 8 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" height " : ( " INT " , { " default " : 64 , " min " : 8 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" x " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" y " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
} }
RETURN_TYPES = ( " CONDITIONING " , )
FUNCTION = " append "
2023-04-20 17:30:10 -04:00
CATEGORY = " conditioning/gligen "
2023-04-19 09:36:19 -04:00
def append ( self , conditioning_to , clip , gligen_textbox_model , text , width , height , x , y ) :
c = [ ]
2024-02-25 07:20:31 -05:00
cond , cond_pooled = clip . encode_from_tokens ( clip . tokenize ( text ) , return_pooled = " unprojected " )
2023-04-19 09:36:19 -04:00
for t in conditioning_to :
n = [ t [ 0 ] , t [ 1 ] . copy ( ) ]
position_params = [ ( cond_pooled , height / / 8 , width / / 8 , y / / 8 , x / / 8 ) ]
prev = [ ]
if " gligen " in n [ 1 ] :
prev = n [ 1 ] [ ' gligen ' ] [ 2 ]
n [ 1 ] [ ' gligen ' ] = ( " position " , gligen_textbox_model , prev + position_params )
c . append ( n )
return ( c , )
2023-04-01 23:19:15 -04:00
2023-01-03 01:53:32 -05:00
class EmptyLatentImage :
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
2024-12-31 03:16:37 -05:00
" required " : {
2024-08-14 06:22:10 +01:00
" width " : ( " INT " , { " default " : 512 , " min " : 16 , " max " : MAX_RESOLUTION , " step " : 8 , " tooltip " : " The width of the latent images in pixels. " } ) ,
" height " : ( " INT " , { " default " : 512 , " min " : 16 , " max " : MAX_RESOLUTION , " step " : 8 , " tooltip " : " The height of the latent images in pixels. " } ) ,
" batch_size " : ( " INT " , { " default " : 1 , " min " : 1 , " max " : 4096 , " tooltip " : " The number of latent images in the batch. " } )
}
}
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " LATENT " , )
2024-08-14 06:22:10 +01:00
OUTPUT_TOOLTIPS = ( " The empty latent image batch. " , )
2023-01-03 01:53:32 -05:00
FUNCTION = " generate "
2023-01-26 12:23:15 -05:00
CATEGORY = " latent "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Create a new batch of empty latent images to be denoised via sampling. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " empty " , " empty latent " , " new latent " , " create latent " , " blank latent " , " blank " ]
2023-01-26 12:23:15 -05:00
2023-01-03 01:53:32 -05:00
def generate ( self , width , height , batch_size = 1 ) :
2026-03-15 12:37:27 -07:00
latent = torch . zeros ( [ batch_size , 4 , height / / 8 , width / / 8 ] , device = comfy . model_management . intermediate_device ( ) , dtype = comfy . model_management . intermediate_dtype ( ) )
2026-01-23 16:50:48 -08:00
return ( { " samples " : latent , " downscale_ratio_spacial " : 8 } , )
2023-01-03 01:53:32 -05:00
2023-02-16 10:38:08 -05:00
2023-04-17 17:24:58 -04:00
class LatentFromBatch :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " select from batch " , " pick latent " , " batch subset " ]
2023-04-17 17:24:58 -04:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
" batch_index " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : 63 } ) ,
2023-05-13 17:15:45 +02:00
" length " : ( " INT " , { " default " : 1 , " min " : 1 , " max " : 64 } ) ,
2023-04-17 17:24:58 -04:00
} }
RETURN_TYPES = ( " LATENT " , )
2023-05-13 17:15:45 +02:00
FUNCTION = " frombatch "
2023-04-17 17:24:58 -04:00
2023-05-13 17:15:45 +02:00
CATEGORY = " latent/batch "
2023-04-17 17:24:58 -04:00
2023-05-13 17:15:45 +02:00
def frombatch ( self , samples , batch_index , length ) :
2023-04-17 17:24:58 -04:00
s = samples . copy ( )
s_in = samples [ " samples " ]
batch_index = min ( s_in . shape [ 0 ] - 1 , batch_index )
2023-05-13 17:15:45 +02:00
length = min ( s_in . shape [ 0 ] - batch_index , length )
s [ " samples " ] = s_in [ batch_index : batch_index + length ] . clone ( )
if " noise_mask " in samples :
masks = samples [ " noise_mask " ]
if masks . shape [ 0 ] == 1 :
s [ " noise_mask " ] = masks . clone ( )
else :
if masks . shape [ 0 ] < s_in . shape [ 0 ] :
masks = masks . repeat ( math . ceil ( s_in . shape [ 0 ] / masks . shape [ 0 ] ) , 1 , 1 , 1 ) [ : s_in . shape [ 0 ] ]
s [ " noise_mask " ] = masks [ batch_index : batch_index + length ] . clone ( )
if " batch_index " not in s :
s [ " batch_index " ] = [ x for x in range ( batch_index , batch_index + length ) ]
else :
s [ " batch_index " ] = samples [ " batch_index " ] [ batch_index : batch_index + length ]
return ( s , )
2024-12-27 18:02:21 -05:00
2023-05-13 17:15:45 +02:00
class RepeatLatentBatch :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " duplicate latent " , " clone latent " ]
2023-05-13 17:15:45 +02:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
" amount " : ( " INT " , { " default " : 1 , " min " : 1 , " max " : 64 } ) ,
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " repeat "
CATEGORY = " latent/batch "
def repeat ( self , samples , amount ) :
s = samples . copy ( )
s_in = samples [ " samples " ]
2024-12-27 18:02:21 -05:00
2025-08-07 08:20:40 -07:00
s [ " samples " ] = s_in . repeat ( ( amount , ) + ( ( 1 , ) * ( s_in . ndim - 1 ) ) )
2023-05-13 17:15:45 +02:00
if " noise_mask " in samples and samples [ " noise_mask " ] . shape [ 0 ] > 1 :
masks = samples [ " noise_mask " ]
if masks . shape [ 0 ] < s_in . shape [ 0 ] :
2025-08-07 08:20:40 -07:00
masks = masks . repeat ( ( math . ceil ( s_in . shape [ 0 ] / masks . shape [ 0 ] ) , ) + ( ( 1 , ) * ( masks . ndim - 1 ) ) ) [ : s_in . shape [ 0 ] ]
s [ " noise_mask " ] = samples [ " noise_mask " ] . repeat ( ( amount , ) + ( ( 1 , ) * ( samples [ " noise_mask " ] . ndim - 1 ) ) )
2023-05-13 17:15:45 +02:00
if " batch_index " in s :
offset = max ( s [ " batch_index " ] ) - min ( s [ " batch_index " ] ) + 1
s [ " batch_index " ] = s [ " batch_index " ] + [ x + ( i * offset ) for i in range ( 1 , amount ) for x in s [ " batch_index " ] ]
2023-04-17 17:24:58 -04:00
return ( s , )
2023-02-04 15:53:29 -05:00
2023-01-03 01:53:32 -05:00
class LatentUpscale :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " enlarge latent " , " resize latent " ]
2023-06-17 01:54:33 -04:00
upscale_methods = [ " nearest-exact " , " bilinear " , " area " , " bicubic " , " bislerp " ]
2023-01-24 17:26:11 -05:00
crop_methods = [ " disabled " , " center " ]
2023-01-03 01:53:32 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) , " upscale_method " : ( s . upscale_methods , ) ,
2023-09-24 12:08:54 -03:00
" width " : ( " INT " , { " default " : 512 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" height " : ( " INT " , { " default " : 512 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
2023-01-24 17:26:11 -05:00
" crop " : ( s . crop_methods , ) } }
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " upscale "
2023-01-27 14:11:57 -05:00
CATEGORY = " latent "
2023-01-24 17:26:11 -05:00
def upscale ( self , samples , upscale_method , width , height , crop ) :
2023-09-24 12:08:54 -03:00
if width == 0 and height == 0 :
s = samples
else :
s = samples . copy ( )
if width == 0 :
height = max ( 64 , height )
2024-10-26 01:50:51 -04:00
width = max ( 64 , round ( samples [ " samples " ] . shape [ - 1 ] * height / samples [ " samples " ] . shape [ - 2 ] ) )
2023-09-24 12:08:54 -03:00
elif height == 0 :
width = max ( 64 , width )
2024-10-26 01:50:51 -04:00
height = max ( 64 , round ( samples [ " samples " ] . shape [ - 2 ] * width / samples [ " samples " ] . shape [ - 1 ] ) )
2023-09-24 12:08:54 -03:00
else :
width = max ( 64 , width )
height = max ( 64 , height )
s [ " samples " ] = comfy . utils . common_upscale ( samples [ " samples " ] , width / / 8 , height / / 8 , upscale_method , crop )
2023-01-03 01:53:32 -05:00
return ( s , )
2023-05-23 12:53:38 -04:00
class LatentUpscaleBy :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " enlarge latent " , " resize latent " , " scale latent " ]
2023-06-17 01:54:33 -04:00
upscale_methods = [ " nearest-exact " , " bilinear " , " area " , " bicubic " , " bislerp " ]
2023-05-23 12:53:38 -04:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) , " upscale_method " : ( s . upscale_methods , ) ,
" scale_by " : ( " FLOAT " , { " default " : 1.5 , " min " : 0.01 , " max " : 8.0 , " step " : 0.01 } ) , } }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " upscale "
CATEGORY = " latent "
def upscale ( self , samples , upscale_method , scale_by ) :
s = samples . copy ( )
2024-10-26 01:50:51 -04:00
width = round ( samples [ " samples " ] . shape [ - 1 ] * scale_by )
height = round ( samples [ " samples " ] . shape [ - 2 ] * scale_by )
2023-05-23 12:53:38 -04:00
s [ " samples " ] = comfy . utils . common_upscale ( samples [ " samples " ] , width , height , upscale_method , " disabled " )
return ( s , )
2023-01-31 02:28:07 -05:00
class LatentRotate :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
" rotation " : ( [ " none " , " 90 degrees " , " 180 degrees " , " 270 degrees " ] , ) ,
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " rotate "
2023-03-06 01:30:17 -05:00
CATEGORY = " latent/transform "
2023-01-31 02:28:07 -05:00
def rotate ( self , samples , rotation ) :
2023-02-15 16:58:55 -05:00
s = samples . copy ( )
2023-01-31 02:28:07 -05:00
rotate_by = 0
if rotation . startswith ( " 90 " ) :
rotate_by = 1
elif rotation . startswith ( " 180 " ) :
rotate_by = 2
elif rotation . startswith ( " 270 " ) :
rotate_by = 3
2023-02-15 16:58:55 -05:00
s [ " samples " ] = torch . rot90 ( samples [ " samples " ] , k = rotate_by , dims = [ 3 , 2 ] )
2023-01-31 02:28:07 -05:00
return ( s , )
2023-01-31 03:28:38 -05:00
class LatentFlip :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " mirror latent " ]
2023-01-31 03:28:38 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
" flip_method " : ( [ " x-axis: vertically " , " y-axis: horizontally " ] , ) ,
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " flip "
2023-03-06 01:30:17 -05:00
CATEGORY = " latent/transform "
2023-01-31 03:28:38 -05:00
def flip ( self , samples , flip_method ) :
2023-02-15 16:58:55 -05:00
s = samples . copy ( )
2023-01-31 03:28:38 -05:00
if flip_method . startswith ( " x " ) :
2023-02-15 16:58:55 -05:00
s [ " samples " ] = torch . flip ( samples [ " samples " ] , dims = [ 2 ] )
2023-01-31 03:28:38 -05:00
elif flip_method . startswith ( " y " ) :
2023-02-15 16:58:55 -05:00
s [ " samples " ] = torch . flip ( samples [ " samples " ] , dims = [ 3 ] )
2023-01-31 03:28:38 -05:00
return ( s , )
2023-01-31 03:35:03 -05:00
class LatentComposite :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " overlay latent " , " layer latent " , " paste latent " ]
2023-01-31 03:35:03 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2023-04-14 00:14:35 -04:00
return { " required " : { " samples_to " : ( " LATENT " , ) ,
" samples_from " : ( " LATENT " , ) ,
" x " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" y " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" feather " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
} }
2023-01-31 03:35:03 -05:00
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " composite "
CATEGORY = " latent "
2023-04-14 00:14:35 -04:00
def composite ( self , samples_to , samples_from , x , y , composite_method = " normal " , feather = 0 ) :
x = x / / 8
y = y / / 8
2023-02-12 13:01:52 -05:00
feather = feather / / 8
2023-04-14 00:14:35 -04:00
samples_out = samples_to . copy ( )
s = samples_to [ " samples " ] . clone ( )
samples_to = samples_to [ " samples " ]
samples_from = samples_from [ " samples " ]
if feather == 0 :
s [ : , : , y : y + samples_from . shape [ 2 ] , x : x + samples_from . shape [ 3 ] ] = samples_from [ : , : , : samples_to . shape [ 2 ] - y , : samples_to . shape [ 3 ] - x ]
else :
samples_from = samples_from [ : , : , : samples_to . shape [ 2 ] - y , : samples_to . shape [ 3 ] - x ]
mask = torch . ones_like ( samples_from )
for t in range ( feather ) :
if y != 0 :
mask [ : , : , t : 1 + t , : ] * = ( ( 1.0 / feather ) * ( t + 1 ) )
if y + samples_from . shape [ 2 ] < samples_to . shape [ 2 ] :
mask [ : , : , mask . shape [ 2 ] - 1 - t : mask . shape [ 2 ] - t , : ] * = ( ( 1.0 / feather ) * ( t + 1 ) )
if x != 0 :
mask [ : , : , : , t : 1 + t ] * = ( ( 1.0 / feather ) * ( t + 1 ) )
if x + samples_from . shape [ 3 ] < samples_to . shape [ 3 ] :
mask [ : , : , : , mask . shape [ 3 ] - 1 - t : mask . shape [ 3 ] - t ] * = ( ( 1.0 / feather ) * ( t + 1 ) )
rev_mask = torch . ones_like ( mask ) - mask
s [ : , : , y : y + samples_from . shape [ 2 ] , x : x + samples_from . shape [ 3 ] ] = samples_from [ : , : , : samples_to . shape [ 2 ] - y , : samples_to . shape [ 3 ] - x ] * mask + s [ : , : , y : y + samples_from . shape [ 2 ] , x : x + samples_from . shape [ 3 ] ] * rev_mask
samples_out [ " samples " ] = s
return ( samples_out , )
2023-01-31 03:35:03 -05:00
2023-08-01 01:23:14 -05:00
class LatentBlend :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " mix latents " , " interpolate latents " ]
2023-08-01 01:23:14 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : {
2023-08-04 02:51:28 -04:00
" samples1 " : ( " LATENT " , ) ,
" samples2 " : ( " LATENT " , ) ,
2023-08-01 01:23:14 -05:00
" blend_factor " : ( " FLOAT " , {
" default " : 0.5 ,
" min " : 0 ,
" max " : 1 ,
" step " : 0.01
} ) ,
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " blend "
CATEGORY = " _for_testing "
2023-08-04 02:51:28 -04:00
def blend ( self , samples1 , samples2 , blend_factor : float , blend_mode : str = " normal " ) :
2023-08-01 01:23:14 -05:00
2023-08-04 02:51:28 -04:00
samples_out = samples1 . copy ( )
samples1 = samples1 [ " samples " ]
samples2 = samples2 [ " samples " ]
2023-08-01 01:23:14 -05:00
2023-08-04 02:51:28 -04:00
if samples1 . shape != samples2 . shape :
samples2 . permute ( 0 , 3 , 1 , 2 )
samples2 = comfy . utils . common_upscale ( samples2 , samples1 . shape [ 3 ] , samples1 . shape [ 2 ] , ' bicubic ' , crop = ' center ' )
samples2 . permute ( 0 , 2 , 3 , 1 )
2023-08-01 01:23:14 -05:00
2023-08-04 02:51:28 -04:00
samples_blended = self . blend_mode ( samples1 , samples2 , blend_mode )
samples_blended = samples1 * blend_factor + samples_blended * ( 1 - blend_factor )
2023-08-01 01:23:14 -05:00
samples_out [ " samples " ] = samples_blended
return ( samples_out , )
def blend_mode ( self , img1 , img2 , mode ) :
if mode == " normal " :
return img2
else :
raise ValueError ( f " Unsupported blend mode: { mode } " )
2023-02-04 15:21:46 -05:00
class LatentCrop :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " trim latent " , " cut latent " ]
2023-02-04 15:21:46 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
2023-05-02 14:16:27 -04:00
" width " : ( " INT " , { " default " : 512 , " min " : 64 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" height " : ( " INT " , { " default " : 512 , " min " : 64 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
2023-03-22 12:22:48 -04:00
" x " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" y " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
2023-02-04 15:21:46 -05:00
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " crop "
2023-03-06 01:30:17 -05:00
CATEGORY = " latent/transform "
2023-02-04 15:21:46 -05:00
def crop ( self , samples , width , height , x , y ) :
2023-02-15 16:58:55 -05:00
s = samples . copy ( )
samples = samples [ ' samples ' ]
2023-02-04 15:21:46 -05:00
x = x / / 8
y = y / / 8
#enfonce minimum size of 64
if x > ( samples . shape [ 3 ] - 8 ) :
x = samples . shape [ 3 ] - 8
if y > ( samples . shape [ 2 ] - 8 ) :
y = samples . shape [ 2 ] - 8
new_height = height / / 8
new_width = width / / 8
to_x = new_width + x
to_y = new_height + y
2023-02-15 16:58:55 -05:00
s [ ' samples ' ] = samples [ : , : , y : to_y , x : to_x ]
2023-02-04 15:21:46 -05:00
return ( s , )
2023-02-15 16:58:55 -05:00
class SetLatentNoiseMask :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " samples " : ( " LATENT " , ) ,
" mask " : ( " MASK " , ) ,
} }
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " set_mask "
2023-02-15 20:44:51 -05:00
CATEGORY = " latent/inpaint "
2023-02-15 16:58:55 -05:00
def set_mask ( self , samples , mask ) :
s = samples . copy ( )
2023-05-12 20:34:48 -04:00
s [ " noise_mask " ] = mask . reshape ( ( - 1 , 1 , mask . shape [ - 2 ] , mask . shape [ - 1 ] ) )
2023-02-15 16:58:55 -05:00
return ( s , )
2023-06-05 13:19:02 -05:00
def common_ksampler ( model , seed , steps , cfg , sampler_name , scheduler , positive , negative , latent , denoise = 1.0 , disable_noise = False , start_step = None , last_step = None , force_full_denoise = False ) :
2023-04-23 20:02:08 +02:00
latent_image = latent [ " samples " ]
2026-01-23 16:50:48 -08:00
latent_image = comfy . sample . fix_empty_latent_channels ( model , latent_image , latent . get ( " downscale_ratio_spacial " , None ) )
2024-06-08 02:16:55 -04:00
2023-01-31 03:09:38 -05:00
if disable_noise :
noise = torch . zeros ( latent_image . size ( ) , dtype = latent_image . dtype , layout = latent_image . layout , device = " cpu " )
else :
2023-05-13 17:15:45 +02:00
batch_inds = latent [ " batch_index " ] if " batch_index " in latent else None
noise = comfy . sample . prepare_noise ( latent_image , seed , batch_inds )
2023-01-31 03:09:38 -05:00
2023-04-24 12:53:10 +02:00
noise_mask = None
2023-02-15 16:58:55 -05:00
if " noise_mask " in latent :
2023-04-24 23:25:51 -04:00
noise_mask = latent [ " noise_mask " ]
2023-01-31 03:09:38 -05:00
2023-09-27 16:45:22 -04:00
callback = latent_preview . prepare_callback ( model , steps )
2023-10-11 20:35:50 -04:00
disable_pbar = not comfy . utils . PROGRESS_BAR_ENABLED
2023-04-24 23:25:51 -04:00
samples = comfy . sample . sample ( model , noise , steps , cfg , sampler_name , scheduler , positive , negative , latent_image ,
denoise = denoise , disable_noise = disable_noise , start_step = start_step , last_step = last_step ,
2023-09-27 22:21:18 -04:00
force_full_denoise = force_full_denoise , noise_mask = noise_mask , callback = callback , disable_pbar = disable_pbar , seed = seed )
2023-02-15 16:58:55 -05:00
out = latent . copy ( )
2026-01-23 16:50:48 -08:00
out . pop ( " downscale_ratio_spacial " , None )
2023-02-15 16:58:55 -05:00
out [ " samples " ] = samples
return ( out , )
2023-01-31 03:09:38 -05:00
2023-01-03 01:53:32 -05:00
class KSampler :
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
" required " : {
" model " : ( " MODEL " , { " tooltip " : " The model used for denoising the input latent. " } ) ,
2025-03-05 15:33:23 -05:00
" seed " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : 0xffffffffffffffff , " control_after_generate " : True , " tooltip " : " The random seed used for creating the noise. " } ) ,
2024-08-14 06:22:10 +01:00
" steps " : ( " INT " , { " default " : 20 , " min " : 1 , " max " : 10000 , " tooltip " : " The number of steps used in the denoising process. " } ) ,
" cfg " : ( " FLOAT " , { " default " : 8.0 , " min " : 0.0 , " max " : 100.0 , " step " : 0.1 , " round " : 0.01 , " tooltip " : " The Classifier-Free Guidance scale balances creativity and adherence to the prompt. Higher values result in images more closely matching the prompt however too high values will negatively impact quality. " } ) ,
" sampler_name " : ( comfy . samplers . KSampler . SAMPLERS , { " tooltip " : " The algorithm used when sampling, this can affect the quality, speed, and style of the generated output. " } ) ,
" scheduler " : ( comfy . samplers . KSampler . SCHEDULERS , { " tooltip " : " The scheduler controls how noise is gradually removed to form the image. " } ) ,
" positive " : ( " CONDITIONING " , { " tooltip " : " The conditioning describing the attributes you want to include in the image. " } ) ,
" negative " : ( " CONDITIONING " , { " tooltip " : " The conditioning describing the attributes you want to exclude from the image. " } ) ,
" latent_image " : ( " LATENT " , { " tooltip " : " The latent image to denoise. " } ) ,
" denoise " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.0 , " max " : 1.0 , " step " : 0.01 , " tooltip " : " The amount of denoising applied, lower values will maintain the structure of the initial image allowing for image to image sampling. " } ) ,
}
}
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( " LATENT " , )
2024-08-14 06:22:10 +01:00
OUTPUT_TOOLTIPS = ( " The denoised latent. " , )
2023-01-03 01:53:32 -05:00
FUNCTION = " sample "
2023-01-26 12:23:15 -05:00
CATEGORY = " sampling "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Uses the provided model, positive and negative conditioning to denoise the latent image. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " sampler " , " sample " , " generate " , " denoise " , " diffuse " , " txt2img " , " img2img " ]
2023-01-26 12:23:15 -05:00
2023-06-05 13:19:02 -05:00
def sample ( self , model , seed , steps , cfg , sampler_name , scheduler , positive , negative , latent_image , denoise = 1.0 ) :
return common_ksampler ( model , seed , steps , cfg , sampler_name , scheduler , positive , negative , latent_image , denoise = denoise )
2023-01-03 01:53:32 -05:00
2023-01-31 03:09:38 -05:00
class KSamplerAdvanced :
@classmethod
def INPUT_TYPES ( s ) :
return { " required " :
{ " model " : ( " MODEL " , ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" add_noise " : ( [ " enable " , " disable " ] , { " advanced " : True } ) ,
2025-03-05 15:33:23 -05:00
" noise_seed " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : 0xffffffffffffffff , " control_after_generate " : True } ) ,
2023-01-31 03:09:38 -05:00
" steps " : ( " INT " , { " default " : 20 , " min " : 1 , " max " : 10000 } ) ,
2023-11-09 17:35:17 -05:00
" cfg " : ( " FLOAT " , { " default " : 8.0 , " min " : 0.0 , " max " : 100.0 , " step " : 0.1 , " round " : 0.01 } ) ,
2023-01-31 03:09:38 -05:00
" sampler_name " : ( comfy . samplers . KSampler . SAMPLERS , ) ,
" scheduler " : ( comfy . samplers . KSampler . SCHEDULERS , ) ,
" positive " : ( " CONDITIONING " , ) ,
" negative " : ( " CONDITIONING " , ) ,
" latent_image " : ( " LATENT " , ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" start_at_step " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : 10000 , " advanced " : True } ) ,
" end_at_step " : ( " INT " , { " default " : 10000 , " min " : 0 , " max " : 10000 , " advanced " : True } ) ,
" return_with_leftover_noise " : ( [ " disable " , " enable " ] , { " advanced " : True } ) ,
2023-06-05 13:19:02 -05:00
}
}
2023-01-31 03:09:38 -05:00
RETURN_TYPES = ( " LATENT " , )
FUNCTION = " sample "
CATEGORY = " sampling "
2023-01-03 01:53:32 -05:00
2023-06-05 13:19:02 -05:00
def sample ( self , model , add_noise , noise_seed , steps , cfg , sampler_name , scheduler , positive , negative , latent_image , start_at_step , end_at_step , return_with_leftover_noise , denoise = 1.0 ) :
2023-01-31 03:09:38 -05:00
force_full_denoise = True
if return_with_leftover_noise == " enable " :
force_full_denoise = False
disable_noise = False
if add_noise == " disable " :
disable_noise = True
2023-06-05 13:19:02 -05:00
return common_ksampler ( model , noise_seed , steps , cfg , sampler_name , scheduler , positive , negative , latent_image , denoise = denoise , disable_noise = disable_noise , start_step = start_at_step , last_step = end_at_step , force_full_denoise = force_full_denoise )
2023-01-03 01:53:32 -05:00
class SaveImage :
def __init__ ( self ) :
2023-04-05 14:01:01 -04:00
self . output_dir = folder_paths . get_output_directory ( )
2023-03-19 12:54:29 +01:00
self . type = " output "
2023-07-11 17:35:55 -04:00
self . prefix_append = " "
2023-11-28 04:57:59 -05:00
self . compress_level = 4
2023-01-03 01:53:32 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2024-08-14 06:22:10 +01:00
return {
" required " : {
" images " : ( " IMAGE " , { " tooltip " : " The images to save. " } ) ,
" filename_prefix " : ( " STRING " , { " default " : " ComfyUI " , " tooltip " : " The prefix for the file to save. This may include formatting information such as %d ate:yyyy-MM-dd % o r %E mpty Latent Image.width % to include values from nodes. " } )
} ,
" hidden " : {
" prompt " : " PROMPT " , " extra_pnginfo " : " EXTRA_PNGINFO "
} ,
}
2023-01-03 01:53:32 -05:00
RETURN_TYPES = ( )
FUNCTION = " save_images "
OUTPUT_NODE = True
2023-01-26 12:23:15 -05:00
CATEGORY = " image "
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
ESSENTIALS_CATEGORY = " Basics "
2024-08-14 06:22:10 +01:00
DESCRIPTION = " Saves the input images to your ComfyUI output directory. "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " save " , " save image " , " export image " , " output image " , " write image " , " download " ]
2023-01-26 12:23:15 -05:00
2023-03-14 19:42:28 +00:00
def save_images ( self , images , filename_prefix = " ComfyUI " , prompt = None , extra_pnginfo = None ) :
2023-07-11 17:35:55 -04:00
filename_prefix + = self . prefix_append
2023-05-17 23:43:59 -04:00
full_output_folder , filename , counter , subfolder , filename_prefix = folder_paths . get_save_image_path ( filename_prefix , self . output_dir , images [ 0 ] . shape [ 1 ] , images [ 0 ] . shape [ 0 ] )
2023-03-19 12:54:29 +01:00
results = list ( )
2024-02-08 22:01:56 +10:00
for ( batch_number , image ) in enumerate ( images ) :
2023-01-03 01:53:32 -05:00
i = 255. * image . cpu ( ) . numpy ( )
2023-03-11 12:48:28 -05:00
img = Image . fromarray ( np . clip ( i , 0 , 255 ) . astype ( np . uint8 ) )
2023-07-28 12:31:41 -04:00
metadata = None
if not args . disable_metadata :
metadata = PngInfo ( )
if prompt is not None :
metadata . add_text ( " prompt " , json . dumps ( prompt ) )
if extra_pnginfo is not None :
for x in extra_pnginfo :
metadata . add_text ( x , json . dumps ( extra_pnginfo [ x ] ) )
2023-03-15 10:48:15 +00:00
2024-02-08 22:01:56 +10:00
filename_with_batch_num = filename . replace ( " % batch_num % " , str ( batch_number ) )
file = f " { filename_with_batch_num } _ { counter : 05 } _.png "
2023-11-28 04:57:59 -05:00
img . save ( os . path . join ( full_output_folder , file ) , pnginfo = metadata , compress_level = self . compress_level )
2023-03-19 12:54:29 +01:00
results . append ( {
" filename " : file ,
" subfolder " : subfolder ,
" type " : self . type
2023-04-13 16:38:02 -04:00
} )
2023-01-24 02:17:18 -05:00
counter + = 1
2023-03-20 14:55:28 -04:00
2023-03-19 12:54:29 +01:00
return { " ui " : { " images " : results } }
2023-01-03 01:53:32 -05:00
2023-03-14 19:28:07 +00:00
class PreviewImage ( SaveImage ) :
def __init__ ( self ) :
2023-04-05 14:01:01 -04:00
self . output_dir = folder_paths . get_temp_directory ( )
2023-03-19 12:54:29 +01:00
self . type = " temp "
2023-07-11 17:35:55 -04:00
self . prefix_append = " _temp_ " + ' ' . join ( random . choice ( " abcdefghijklmnopqrstupvxyz " ) for x in range ( 5 ) )
2023-11-28 04:57:59 -05:00
self . compress_level = 1
2023-03-14 19:28:07 +00:00
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " preview " , " preview image " , " show image " , " view image " , " display image " , " image viewer " ]
2023-03-14 19:28:07 +00:00
@classmethod
def INPUT_TYPES ( s ) :
2023-03-14 19:08:23 -04:00
return { " required " :
2023-03-14 19:28:07 +00:00
{ " images " : ( " IMAGE " , ) , } ,
" hidden " : { " prompt " : " PROMPT " , " extra_pnginfo " : " EXTRA_PNGINFO " } ,
}
2023-03-14 19:08:23 -04:00
2023-01-22 14:59:34 -05:00
class LoadImage :
@classmethod
def INPUT_TYPES ( s ) :
2023-04-05 14:01:01 -04:00
input_dir = folder_paths . get_input_directory ( )
2023-05-08 14:13:06 -04:00
files = [ f for f in os . listdir ( input_dir ) if os . path . isfile ( os . path . join ( input_dir , f ) ) ]
2025-04-13 06:27:59 +08:00
files = folder_paths . filter_files_content_types ( files , [ " image " ] )
2023-01-22 14:59:34 -05:00
return { " required " :
2023-08-22 19:41:49 -04:00
{ " image " : ( sorted ( files ) , { " image_upload " : True } ) } ,
2023-01-22 14:59:34 -05:00
}
2023-01-26 12:23:15 -05:00
CATEGORY = " image "
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
ESSENTIALS_CATEGORY = " Basics "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " load image " , " open image " , " import image " , " image input " , " upload image " , " read image " , " image loader " ]
2023-01-22 14:59:34 -05:00
2023-03-09 14:07:55 -05:00
RETURN_TYPES = ( " IMAGE " , " MASK " )
2023-01-22 14:59:34 -05:00
FUNCTION = " load_image "
2026-05-02 17:34:27 -07:00
2023-01-22 14:59:34 -05:00
def load_image ( self , image ) :
2023-04-23 16:03:26 -04:00
image_path = folder_paths . get_annotated_filepath ( image )
2024-12-27 18:02:21 -05:00
2026-05-02 17:34:27 -07:00
dtype = comfy . model_management . intermediate_dtype ( )
device = comfy . model_management . intermediate_device ( )
2026-04-28 15:15:06 -07:00
components = InputImpl . VideoFromFile ( image_path ) . get_components ( )
if components . images . shape [ 0 ] > 0 :
2026-05-02 17:34:27 -07:00
return ( components . images . to ( device = device , dtype = dtype ) , ( 1.0 - components . alpha [ . . . , - 1 ] ) . to ( device = device , dtype = dtype ) if components . alpha is not None else torch . zeros ( ( components . images . shape [ 0 ] , 64 , 64 ) , dtype = dtype , device = device ) )
2026-04-28 15:15:06 -07:00
2026-05-02 17:34:27 -07:00
# This code is left here to handle animated webp which pyav does not support loading
2024-05-09 02:38:00 -07:00
img = node_helpers . pillow ( Image . open , image_path )
2024-12-27 18:02:21 -05:00
2023-12-20 16:39:09 -05:00
output_images = [ ]
output_masks = [ ]
2024-05-12 04:07:38 -07:00
w , h = None , None
2023-12-20 16:39:09 -05:00
for i in ImageSequence . Iterator ( img ) :
2024-05-09 02:38:00 -07:00
i = node_helpers . pillow ( ImageOps . exif_transpose , i )
2024-05-07 05:41:06 -04:00
2023-12-20 16:39:09 -05:00
image = i . convert ( " RGB " )
2024-05-12 04:07:38 -07:00
if len ( output_images ) == 0 :
w = image . size [ 0 ]
h = image . size [ 1 ]
2024-12-27 18:02:21 -05:00
2024-05-12 04:07:38 -07:00
if image . size [ 0 ] != w or image . size [ 1 ] != h :
continue
2024-12-27 18:02:21 -05:00
2023-12-20 16:39:09 -05:00
image = np . array ( image ) . astype ( np . float32 ) / 255.0
image = torch . from_numpy ( image ) [ None , ]
if ' A ' in i . getbands ( ) :
mask = np . array ( i . getchannel ( ' A ' ) ) . astype ( np . float32 ) / 255.0
mask = 1. - torch . from_numpy ( mask )
else :
2026-05-02 17:34:27 -07:00
mask = torch . zeros ( ( 64 , 64 ) , dtype = torch . float32 , device = " cpu " )
2026-03-14 16:18:19 -07:00
output_images . append ( image . to ( dtype = dtype ) )
output_masks . append ( mask . unsqueeze ( 0 ) . to ( dtype = dtype ) )
2023-12-20 16:39:09 -05:00
2026-05-02 17:34:27 -07:00
output_image = torch . cat ( output_images , dim = 0 )
output_mask = torch . cat ( output_masks , dim = 0 )
2023-12-20 16:39:09 -05:00
2026-05-02 17:34:27 -07:00
return ( output_image . to ( device = device , dtype = dtype ) , output_mask . to ( device = device , dtype = dtype ) )
2023-01-22 14:59:34 -05:00
2023-01-22 21:42:22 -05:00
@classmethod
def IS_CHANGED ( s , image ) :
2023-04-23 16:03:26 -04:00
image_path = folder_paths . get_annotated_filepath ( image )
2023-01-22 21:42:22 -05:00
m = hashlib . sha256 ( )
with open ( image_path , ' rb ' ) as f :
m . update ( f . read ( ) )
return m . digest ( ) . hex ( )
2023-03-09 18:18:08 +00:00
2023-04-23 16:03:26 -04:00
@classmethod
def VALIDATE_INPUTS ( s , image ) :
if not folder_paths . exists_annotated_filepath ( image ) :
return " Invalid image file: {} " . format ( image )
return True
2026-05-03 13:18:27 -07:00
class LoadImageMask ( LoadImage ) :
2026-03-15 16:18:04 -07:00
ESSENTIALS_CATEGORY = " Image Tools "
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " import mask " , " alpha mask " , " channel mask " ]
2023-04-23 16:03:26 -04:00
_color_channels = [ " alpha " , " red " , " green " , " blue " ]
2026-05-03 13:18:27 -07:00
2023-02-15 17:39:42 -05:00
@classmethod
def INPUT_TYPES ( s ) :
2026-05-03 13:18:27 -07:00
types = super ( ) . INPUT_TYPES ( )
return {
" required " : {
* * types [ " required " ] ,
" channel " : ( s . _color_channels , )
}
}
2023-02-15 17:39:42 -05:00
2023-04-05 19:52:39 -04:00
CATEGORY = " mask "
2023-02-15 17:39:42 -05:00
RETURN_TYPES = ( " MASK " , )
2026-05-03 13:18:27 -07:00
FUNCTION = " load_image_mask "
def load_image_mask ( self , image , channel ) :
image_tensor , mask_tensor = super ( ) . load_image ( image )
2023-02-15 17:39:42 -05:00
c = channel [ 0 ] . upper ( )
2026-05-03 13:18:27 -07:00
if c == ' A ' :
return ( mask_tensor , )
channel_idx = { ' R ' : 0 , ' G ' : 1 , ' B ' : 2 } . get ( c , 0 )
if channel_idx < image_tensor . shape [ - 1 ] :
return ( image_tensor [ . . . , channel_idx ] . clone ( ) , )
2023-02-15 17:39:42 -05:00
else :
2026-05-03 13:18:27 -07:00
empty_mask = torch . zeros (
image_tensor . shape [ : - 1 ] ,
dtype = image_tensor . dtype ,
device = image_tensor . device
)
return ( empty_mask , )
2023-02-15 17:39:42 -05:00
@classmethod
def IS_CHANGED ( s , image , channel ) :
2026-05-03 13:18:27 -07:00
return super ( ) . IS_CHANGED ( image )
2023-04-23 16:03:26 -04:00
2025-02-18 15:53:01 -07:00
class LoadImageOutput ( LoadImage ) :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " output image " , " previous generation " ]
2025-02-18 15:53:01 -07:00
@classmethod
def INPUT_TYPES ( s ) :
return {
" required " : {
" image " : ( " COMBO " , {
" image_upload " : True ,
" image_folder " : " output " ,
" remote " : {
" route " : " /internal/files/output " ,
" refresh_button " : True ,
" control_after_refresh " : " first " ,
} ,
} ) ,
}
}
DESCRIPTION = " Load an image from the output folder. When the refresh button is clicked, the node will update the image list and automatically select the first image, allowing for easy iteration. "
EXPERIMENTAL = True
2025-03-09 03:46:08 -07:00
FUNCTION = " load_image "
2025-02-18 15:53:01 -07:00
2023-02-04 15:53:29 -05:00
class ImageScale :
2023-09-19 10:40:38 +02:00
upscale_methods = [ " nearest-exact " , " bilinear " , " area " , " bicubic " , " lanczos " ]
2023-02-04 15:53:29 -05:00
crop_methods = [ " disabled " , " center " ]
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " image " : ( " IMAGE " , ) , " upscale_method " : ( s . upscale_methods , ) ,
2023-09-24 12:08:54 -03:00
" width " : ( " INT " , { " default " : 512 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 1 } ) ,
" height " : ( " INT " , { " default " : 512 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 1 } ) ,
2023-02-04 15:53:29 -05:00
" crop " : ( s . crop_methods , ) } }
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " upscale "
2023-03-11 18:10:36 -05:00
CATEGORY = " image/upscaling "
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
ESSENTIALS_CATEGORY = " Image Tools "
2026-01-21 15:36:02 -08:00
SEARCH_ALIASES = [ " resize " , " resize image " , " scale image " , " image resize " , " zoom " , " zoom in " , " change size " ]
2023-01-22 14:59:34 -05:00
2023-02-04 15:53:29 -05:00
def upscale ( self , image , upscale_method , width , height , crop ) :
2023-09-24 12:08:54 -03:00
if width == 0 and height == 0 :
s = image
else :
samples = image . movedim ( - 1 , 1 )
if width == 0 :
width = max ( 1 , round ( samples . shape [ 3 ] * height / samples . shape [ 2 ] ) )
elif height == 0 :
height = max ( 1 , round ( samples . shape [ 2 ] * width / samples . shape [ 3 ] ) )
s = comfy . utils . common_upscale ( samples , width , height , upscale_method , crop )
s = s . movedim ( 1 , - 1 )
2023-02-04 15:53:29 -05:00
return ( s , )
2023-01-03 01:53:32 -05:00
2023-06-12 01:14:04 -04:00
class ImageScaleBy :
2026-03-15 16:18:04 -07:00
ESSENTIALS_CATEGORY = " Image Tools "
2023-09-19 10:40:38 +02:00
upscale_methods = [ " nearest-exact " , " bilinear " , " area " , " bicubic " , " lanczos " ]
2023-06-12 01:14:04 -04:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " image " : ( " IMAGE " , ) , " upscale_method " : ( s . upscale_methods , ) ,
" scale_by " : ( " FLOAT " , { " default " : 1.0 , " min " : 0.01 , " max " : 8.0 , " step " : 0.01 } ) , } }
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " upscale "
CATEGORY = " image/upscaling "
def upscale ( self , image , upscale_method , scale_by ) :
samples = image . movedim ( - 1 , 1 )
width = round ( samples . shape [ 3 ] * scale_by )
height = round ( samples . shape [ 2 ] * scale_by )
s = comfy . utils . common_upscale ( samples , width , height , upscale_method , " disabled " )
s = s . movedim ( 1 , - 1 )
return ( s , )
2023-02-22 21:57:56 -05:00
class ImageInvert :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " reverse colors " ]
feat: add essentials_category (#12357)
* feat: add essentials_category field to node schema
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* feat: add ESSENTIALS_CATEGORY to core nodes
Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3
* Add ESSENTIALS_CATEGORY to more nodes
- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)
Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910
* fix: correct essentials category for Canny node
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: replace essentials_category string literals with constants
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* refactor: revert constants, use string literals for essentials_category
Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>
* fix: update basics
---------
Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-20 11:00:26 +08:00
ESSENTIALS_CATEGORY = " Image Tools "
2023-02-22 21:57:56 -05:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " image " : ( " IMAGE " , ) } }
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " invert "
2026-05-05 08:37:25 +08:00
CATEGORY = " image/color "
2023-02-22 21:57:56 -05:00
def invert ( self , image ) :
s = 1.0 - image
return ( s , )
2023-08-14 20:23:38 -04:00
class ImageBatch :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " combine images " , " merge images " , " stack images " ]
2023-08-14 20:23:38 -04:00
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " image1 " : ( " IMAGE " , ) , " image2 " : ( " IMAGE " , ) } }
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " batch "
2026-05-05 08:37:25 +08:00
CATEGORY = " image/batch "
V3 Improvements + DynamicCombo + Autogrow exposed in public API (#11345)
* Support Combo outputs in a more sane way
* Remove test validate_inputs function on test node
* Make curr_prefix be a list of strings instead of string for easier parsing as keys get added to dynamic types
* Start to account for id prefixes from frontend, need to fix bug with nested dynamics
* Ensure inputs/outputs/hidden are lists in schema finalize function, remove no longer needed 'is not None' checks
* Add raw_link and extra_dict to all relevant Inputs
* Make nested DynamicCombos work properly with prefixed keys on latest frontend; breaks old Autogrow, but is pretty much ready for upcoming Autogrow keys
* Replace ... usage with a MISSING sentinel for clarity in nodes_logic.py
* Added CustomCombo node in backend to reflect frontend node
* Prepare Autogrow's expand_schema_for_dynamic to work with upcoming frontend changes
* Prepare for look up table for dynamic input stuff
* More progress towards dynamic input lookup function stuff
* Finished converting _expand_schema_for_dynamic to be done via lookup instead of OOP to guarantee working with process isolation, did refactoring to remove old implementation + cleaning INPUT_TYPES definition including v3 hidden definition
* Change order of functions
* Removed some unneeded functions after dynamic refactor
* Make MatchType's output default displayname "MATCHTYPE"
* Fix DynamicSlot get_all
* Removed redundant code - dynamic stuff no longer happens in OOP way
* Natively support AnyType (*) without __ne__ hacks
* Remove stray code that made it in
* Remove expand_schema_for_dynamic left over on DynamicInput class
* get_dynamic() on DynamicInput/Output was not doing anything anymore, so removed it
* Make validate_inputs validate combo input correctly
* Temporarily comment out conversion to 'new' (9 month old) COMBO format in get_input_info
* Remove refrences to resources feature scrapped from V3
* Expose DynamicCombo in public API
* satisfy ruff after some code got commented out
* Make missing input error prettier for dynamic types
* Created a Switch2 node as a side-by-side test, will likely go with Switch2 as the initial switch node
* Figured out Switch situation
* Pass in v3_data in IsChangedCache.get function's fingerprint_inputs, add a from_v3_data helper method to HiddenHolder
* Switch order of Switch and Soft Switch nodes in file
* Temp test node for MatchType
* Fix missing v3_data for v1 nodes in validation
* For now, remove chacking duplicate id's for dynamic types
* Add Resize Image/Mask node that thanks to MatchType+DynamicCombo is 16-nodes-in-1
* Made DynamicCombo references in DCTestNode use public interface
* Add an AnyTypeTestNode
* Make lazy status for specific inputs on DynamicInputs work by having the values of the dictionary for check_lazy_status be a tuple, where the second element is the key of the input that can be returned
* Comment out test logic nodes
* Make primitive float's step make more sense
* Add (and leave commented out) some potential logic nodes
* Change default crop option to "center" on Resize Image/Mask node
* Changed copy.copy(d) to d.copy()
* Autogrow is available in stable frontend, so exposing it in public API
* Use outputs id as display_name if no display_name present, remove v3 outputs id restriction that made them have to have unique IDs from the inputs
* Enable Custom Combo node as stable frontend now supports it
* Make id properly act like display_name on outputs
* Add Batch Images/Masks/Latents node
* Comment out Batch Images/Masks/Latents node for now, as Autogrow has a bug with MatchType where top connection is disconnected upon refresh
* Removed code for a couple test nodes in nodes_logic.py
* Add Batch Images, Batch Masks, and Batch Latents nodes with Autogrow, deprecate old Batch Images + LatentBatch nodes
2025-12-30 20:09:55 -08:00
DEPRECATED = True
2023-08-14 20:23:38 -04:00
def batch ( self , image1 , image2 ) :
2025-11-20 12:08:03 -08:00
if image1 . shape [ - 1 ] != image2 . shape [ - 1 ] :
2025-11-20 14:42:46 -08:00
if image1 . shape [ - 1 ] > image2 . shape [ - 1 ] :
image2 = torch . nn . functional . pad ( image2 , ( 0 , 1 ) , mode = ' constant ' , value = 1.0 )
else :
image1 = torch . nn . functional . pad ( image1 , ( 0 , 1 ) , mode = ' constant ' , value = 1.0 )
2023-08-14 20:23:38 -04:00
if image1 . shape [ 1 : ] != image2 . shape [ 1 : ] :
image2 = comfy . utils . common_upscale ( image2 . movedim ( - 1 , 1 ) , image1 . shape [ 2 ] , image1 . shape [ 1 ] , " bilinear " , " center " ) . movedim ( 1 , - 1 )
s = torch . cat ( ( image1 , image2 ) , dim = 0 )
return ( s , )
2023-02-22 21:57:56 -05:00
2023-08-15 17:53:10 -04:00
class EmptyImage :
def __init__ ( self , device = " cpu " ) :
self . device = device
@classmethod
def INPUT_TYPES ( s ) :
return { " required " : { " width " : ( " INT " , { " default " : 512 , " min " : 1 , " max " : MAX_RESOLUTION , " step " : 1 } ) ,
" height " : ( " INT " , { " default " : 512 , " min " : 1 , " max " : MAX_RESOLUTION , " step " : 1 } ) ,
2023-09-25 01:46:44 -04:00
" batch_size " : ( " INT " , { " default " : 1 , " min " : 1 , " max " : 4096 } ) ,
2023-08-15 17:53:10 -04:00
" color " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : 0xFFFFFF , " step " : 1 , " display " : " color " } ) ,
} }
RETURN_TYPES = ( " IMAGE " , )
FUNCTION = " generate "
CATEGORY = " image "
def generate ( self , width , height , batch_size = 1 , color = 0 ) :
2026-03-20 13:08:26 -07:00
dtype = comfy . model_management . intermediate_dtype ( )
device = comfy . model_management . intermediate_device ( )
r = torch . full ( [ batch_size , height , width , 1 ] , ( ( color >> 16 ) & 0xFF ) / 0xFF , device = device , dtype = dtype )
g = torch . full ( [ batch_size , height , width , 1 ] , ( ( color >> 8 ) & 0xFF ) / 0xFF , device = device , dtype = dtype )
b = torch . full ( [ batch_size , height , width , 1 ] , ( ( color ) & 0xFF ) / 0xFF , device = device , dtype = dtype )
2023-08-15 17:53:10 -04:00
return ( torch . cat ( ( r , g , b ) , dim = - 1 ) , )
2023-03-23 23:33:35 +08:00
class ImagePadForOutpaint :
add search aliases to all nodes (#12035)
* feat: Add search_aliases field to node schema
Adds `search_aliases` field to improve node discoverability. Users can define alternative search terms for nodes (e.g., "text concat" → StringConcatenate).
Changes:
- Add `search_aliases: list[str]` to V3 Schema
- Add `SEARCH_ALIASES` support for V1 nodes
- Include field in `/object_info` response
- Add aliases to high-priority core nodes
V1 usage:
```python
class MyNode:
SEARCH_ALIASES = ["alt name", "synonym"]
```
V3 usage:
```python
io.Schema(
node_id="MyNode",
search_aliases=["alt name", "synonym"],
...
)
```
## Related PRs
- Frontend: Comfy-Org/ComfyUI_frontend#XXXX (draft - merge after this)
- Docs: Comfy-Org/docs#XXXX (draft - merge after stable)
* Propagate search_aliases through V3 Schema.get_v1_info to NodeInfoV1
* feat: add SEARCH_ALIASES for core nodes (#12016)
Add search aliases to 22 core nodes in nodes.py to improve node discoverability:
- Checkpoint/model loaders: CheckpointLoader, DiffusersLoader
- Conditioning nodes: ConditioningAverage, ConditioningSetArea, ConditioningSetMask, ConditioningZeroOut
- Style nodes: StyleModelApply
- Image nodes: LoadImageMask, LoadImageOutput, ImageBatch, ImageInvert, ImagePadForOutpaint
- Latent nodes: LoadLatent, SaveLatent, LatentBlend, LatentComposite, LatentCrop, LatentFlip, LatentFromBatch, LatentUpscale, LatentUpscaleBy, RepeatLatentBatch
* feat: add SEARCH_ALIASES for image, mask, and string nodes (#12017)
Add search aliases to nodes in comfy_extras for better discoverability:
- nodes_mask.py: mask manipulation nodes
- nodes_images.py: image processing nodes
- nodes_post_processing.py: post-processing effect nodes
- nodes_string.py: string manipulation nodes
- nodes_compositing.py: compositing nodes
- nodes_morphology.py: morphological operation nodes
- nodes_latent.py: latent space nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for audio and video nodes (#12018)
Add search aliases to audio and video nodes for better discoverability:
- nodes_audio.py: audio loading, saving, and processing nodes
- nodes_video.py: video loading and processing nodes
- nodes_wan.py: WAN model nodes
Uses search_aliases parameter in io.Schema() for v3 nodes.
* feat: add SEARCH_ALIASES for model and misc nodes (#12019)
Add search aliases to model-related and miscellaneous nodes:
- Model nodes: nodes_model_merging.py, nodes_model_advanced.py, nodes_lora_extract.py
- Sampler nodes: nodes_custom_sampler.py, nodes_align_your_steps.py
- Control nodes: nodes_controlnet.py, nodes_attention_multiply.py, nodes_hooks.py
- Training nodes: nodes_train.py, nodes_dataset.py
- Utility nodes: nodes_logic.py, nodes_canny.py, nodes_differential_diffusion.py
- Architecture-specific: nodes_sd3.py, nodes_pixart.py, nodes_lumina2.py, nodes_kandinsky5.py, nodes_hidream.py, nodes_fresca.py, nodes_hunyuan3d.py
- Media nodes: nodes_load_3d.py, nodes_webcam.py, nodes_preview_any.py, nodes_wanmove.py
Uses search_aliases parameter in io.Schema() for v3 nodes, SEARCH_ALIASES class attribute for legacy nodes.
2026-01-22 18:36:58 -08:00
SEARCH_ALIASES = [ " extend canvas " , " expand image " ]
2023-03-23 23:33:35 +08:00
@classmethod
def INPUT_TYPES ( s ) :
return {
" required " : {
" image " : ( " IMAGE " , ) ,
2023-05-02 14:16:27 -04:00
" left " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" top " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" right " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
" bottom " : ( " INT " , { " default " : 0 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 8 } ) ,
feat: mark 429 widgets as advanced for collapsible UI (#12197)
* feat: mark 429 widgets as advanced for collapsible UI
Mark widgets as advanced across core, comfy_extras, and comfy_api_nodes
to support the new collapsible advanced inputs section in the frontend.
Changes:
- 267 advanced markers in comfy_extras/
- 162 advanced markers in comfy_api_nodes/
- All files pass python3 -m py_compile verification
Widgets marked advanced (hidden by default):
- Scheduler internals: sigma_max, sigma_min, rho, mu, beta, alpha
- Sampler internals: eta, s_noise, order, rtol, atol, h_init, pcoeff, etc.
- Memory optimization: tile_size, overlap, temporal_size, temporal_overlap
- Pipeline controls: add_noise, start_at_step, end_at_step
- Timing controls: start_percent, end_percent
- Layer selection: stop_at_clip_layer, layers, block_number
- Video encoding: codec, crf, format
- Device/dtype: device, noise_device, dtype, weight_dtype
Widgets kept basic (always visible):
- Core params: strength, steps, cfg, denoise, seed, width, height
- Model selectors: ckpt_name, lora_name, vae_name, sampler_name
- Common controls: upscale_method, crop, batch_size, fps, opacity
Related: frontend PR #11939
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: remove advanced=True from DynamicCombo.Input (unsupported)
Amp-Thread-ID: https://ampcode.com/threads/T-019c1734-6b61-702e-b333-f02c399963fc
* fix: address review - un-mark model merge, video, image, and training node widgets as advanced
Per comfyanonymous review:
- Model merge arguments should not be advanced (all 14 model-specific merge classes)
- SaveAnimatedWEBP lossless/quality/method should not be advanced
- SaveWEBM/SaveVideo codec/crf/format should not be advanced
- TrainLoraNode options should not be advanced (7 inputs)
Amp-Thread-ID: https://ampcode.com/threads/T-019c322b-a3a8-71b7-9962-d44573ca6352
* fix: un-mark batch_size and webcam width/height as advanced (should stay basic)
Amp-Thread-ID: https://ampcode.com/threads/T-019c3236-1417-74aa-82a3-bcb365fbe9d1
---------
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
2026-02-19 19:20:02 -08:00
" feathering " : ( " INT " , { " default " : 40 , " min " : 0 , " max " : MAX_RESOLUTION , " step " : 1 , " advanced " : True } ) ,
2023-03-23 23:33:35 +08:00
}
}
RETURN_TYPES = ( " IMAGE " , " MASK " )
FUNCTION = " expand_image "
2026-05-05 08:37:25 +08:00
CATEGORY = " image/transform "
2023-03-23 23:33:35 +08:00
2023-03-24 22:39:33 +08:00
def expand_image ( self , image , left , top , right , bottom , feathering ) :
2023-03-23 23:33:35 +08:00
d1 , d2 , d3 , d4 = image . size ( )
2024-01-11 03:15:27 -05:00
new_image = torch . ones (
2023-03-23 23:33:35 +08:00
( d1 , d2 + top + bottom , d3 + left + right , d4 ) ,
dtype = torch . float32 ,
2024-01-11 03:15:27 -05:00
) * 0.5
2023-03-23 23:33:35 +08:00
new_image [ : , top : top + d2 , left : left + d3 , : ] = image
mask = torch . ones (
( d2 + top + bottom , d3 + left + right ) ,
dtype = torch . float32 ,
)
2023-03-24 22:39:33 +08:00
2023-03-25 16:27:47 +08:00
t = torch . zeros (
( d2 , d3 ) ,
dtype = torch . float32
)
2023-03-24 22:39:33 +08:00
if feathering > 0 and feathering * 2 < d2 and feathering * 2 < d3 :
2023-03-25 16:27:47 +08:00
for i in range ( d2 ) :
for j in range ( d3 ) :
dt = i if top != 0 else d2
db = d2 - i if bottom != 0 else d2
dl = j if left != 0 else d3
dr = d3 - j if right != 0 else d3
d = min ( dt , db , dl , dr )
if d > = feathering :
continue
v = ( feathering - d ) / feathering
t [ i , j ] = v * v
mask [ top : top + d2 , left : left + d3 ] = t
2023-03-24 22:39:33 +08:00
2025-05-16 12:15:55 -07:00
return ( new_image , mask . unsqueeze ( 0 ) )
2023-03-23 23:33:35 +08:00
2023-01-03 01:53:32 -05:00
NODE_CLASS_MAPPINGS = {
" KSampler " : KSampler ,
2023-03-03 13:09:44 -05:00
" CheckpointLoaderSimple " : CheckpointLoaderSimple ,
2023-01-03 01:53:32 -05:00
" CLIPTextEncode " : CLIPTextEncode ,
2023-03-03 13:04:36 -05:00
" CLIPSetLastLayer " : CLIPSetLastLayer ,
2023-01-03 01:53:32 -05:00
" VAEDecode " : VAEDecode ,
" VAEEncode " : VAEEncode ,
2023-02-15 20:44:51 -05:00
" VAEEncodeForInpaint " : VAEEncodeForInpaint ,
2023-01-03 01:53:32 -05:00
" VAELoader " : VAELoader ,
" EmptyLatentImage " : EmptyLatentImage ,
" LatentUpscale " : LatentUpscale ,
2023-05-23 12:53:38 -04:00
" LatentUpscaleBy " : LatentUpscaleBy ,
2023-04-17 17:24:58 -04:00
" LatentFromBatch " : LatentFromBatch ,
2023-05-13 17:15:45 +02:00
" RepeatLatentBatch " : RepeatLatentBatch ,
2023-01-03 01:53:32 -05:00
" SaveImage " : SaveImage ,
2023-03-14 19:28:07 +00:00
" PreviewImage " : PreviewImage ,
2023-01-26 12:06:48 -05:00
" LoadImage " : LoadImage ,
2023-02-15 17:39:42 -05:00
" LoadImageMask " : LoadImageMask ,
2025-02-18 15:53:01 -07:00
" LoadImageOutput " : LoadImageOutput ,
2023-02-04 15:53:29 -05:00
" ImageScale " : ImageScale ,
2023-06-12 01:14:04 -04:00
" ImageScaleBy " : ImageScaleBy ,
2023-02-22 21:57:56 -05:00
" ImageInvert " : ImageInvert ,
2023-08-14 20:23:38 -04:00
" ImageBatch " : ImageBatch ,
2023-03-23 23:33:35 +08:00
" ImagePadForOutpaint " : ImagePadForOutpaint ,
2023-08-15 17:53:10 -04:00
" EmptyImage " : EmptyImage ,
2023-09-24 13:27:57 -04:00
" ConditioningAverage " : ConditioningAverage ,
2023-01-26 12:06:48 -05:00
" ConditioningCombine " : ConditioningCombine ,
2023-07-13 21:43:22 -04:00
" ConditioningConcat " : ConditioningConcat ,
2023-01-26 12:06:48 -05:00
" ConditioningSetArea " : ConditioningSetArea ,
2023-09-06 03:26:55 -04:00
" ConditioningSetAreaPercentage " : ConditioningSetAreaPercentage ,
2024-01-29 00:24:53 -05:00
" ConditioningSetAreaStrength " : ConditioningSetAreaStrength ,
2023-04-25 00:15:25 -07:00
" ConditioningSetMask " : ConditioningSetMask ,
2023-01-31 03:09:38 -05:00
" KSamplerAdvanced " : KSamplerAdvanced ,
2023-02-15 16:58:55 -05:00
" SetLatentNoiseMask " : SetLatentNoiseMask ,
2023-01-31 03:35:03 -05:00
" LatentComposite " : LatentComposite ,
2023-08-01 01:23:14 -05:00
" LatentBlend " : LatentBlend ,
2023-01-31 02:28:07 -05:00
" LatentRotate " : LatentRotate ,
2023-01-31 03:28:38 -05:00
" LatentFlip " : LatentFlip ,
2023-02-04 15:21:46 -05:00
" LatentCrop " : LatentCrop ,
2023-02-03 02:06:34 -05:00
" LoraLoader " : LoraLoader ,
2023-02-05 15:20:18 -05:00
" CLIPLoader " : CLIPLoader ,
2023-07-05 17:34:45 -04:00
" UNETLoader " : UNETLoader ,
2023-06-25 01:40:38 -04:00
" DualCLIPLoader " : DualCLIPLoader ,
2023-03-05 18:39:25 -05:00
" CLIPVisionEncode " : CLIPVisionEncode ,
2023-03-06 01:48:18 -05:00
" StyleModelApply " : StyleModelApply ,
2023-04-01 23:19:15 -04:00
" unCLIPConditioning " : unCLIPConditioning ,
2023-02-16 10:38:08 -05:00
" ControlNetApply " : ControlNetApply ,
2023-07-24 13:26:07 -04:00
" ControlNetApplyAdvanced " : ControlNetApplyAdvanced ,
2023-02-16 10:38:08 -05:00
" ControlNetLoader " : ControlNetLoader ,
2023-02-22 23:22:03 -05:00
" DiffControlNetLoader " : DiffControlNetLoader ,
2023-03-06 01:30:17 -05:00
" StyleModelLoader " : StyleModelLoader ,
" CLIPVisionLoader " : CLIPVisionLoader ,
2023-02-24 02:10:10 -05:00
" VAEDecodeTiled " : VAEDecodeTiled ,
2023-03-11 15:28:15 -05:00
" VAEEncodeTiled " : VAEEncodeTiled ,
2023-04-01 23:19:15 -04:00
" unCLIPCheckpointLoader " : unCLIPCheckpointLoader ,
2023-04-19 09:36:19 -04:00
" GLIGENLoader " : GLIGENLoader ,
" GLIGENTextBoxApply " : GLIGENTextBoxApply ,
2024-01-11 03:15:27 -05:00
" InpaintModelConditioning " : InpaintModelConditioning ,
2023-04-19 09:36:19 -04:00
2023-04-04 22:48:11 -04:00
" CheckpointLoader " : CheckpointLoader ,
2023-04-05 23:57:31 -07:00
" DiffusersLoader " : DiffusersLoader ,
2023-05-18 12:40:28 +09:00
" LoadLatent " : LoadLatent ,
2023-06-22 13:03:50 -04:00
" SaveLatent " : SaveLatent ,
2023-06-27 23:30:52 -04:00
" ConditioningZeroOut " : ConditioningZeroOut ,
2023-07-24 09:25:02 -04:00
" ConditioningSetTimestepRange " : ConditioningSetTimestepRange ,
2023-11-25 02:26:50 -05:00
" LoraLoaderModelOnly " : LoraLoaderModelOnly ,
2023-01-03 01:53:32 -05:00
}
2023-03-30 23:13:58 +02:00
NODE_DISPLAY_NAME_MAPPINGS = {
# Sampling
" KSampler " : " KSampler " ,
" KSamplerAdvanced " : " KSampler (Advanced) " ,
# Loaders
2023-10-15 02:22:22 -04:00
" CheckpointLoader " : " Load Checkpoint With Config (DEPRECATED) " ,
2023-04-08 15:53:01 -04:00
" CheckpointLoaderSimple " : " Load Checkpoint " ,
2023-03-30 23:13:58 +02:00
" VAELoader " : " Load VAE " ,
2026-01-25 18:01:55 -08:00
" LoraLoader " : " Load LoRA (Model and CLIP) " ,
" LoraLoaderModelOnly " : " Load LoRA " ,
2023-03-30 23:13:58 +02:00
" CLIPLoader " : " Load CLIP " ,
" ControlNetLoader " : " Load ControlNet Model " ,
" DiffControlNetLoader " : " Load ControlNet Model (diff) " ,
" StyleModelLoader " : " Load Style Model " ,
" CLIPVisionLoader " : " Load CLIP Vision " ,
2024-08-01 13:33:30 -04:00
" UNETLoader " : " Load Diffusion Model " ,
2023-03-30 23:13:58 +02:00
# Conditioning
" CLIPVisionEncode " : " CLIP Vision Encode " ,
" StyleModelApply " : " Apply Style Model " ,
" CLIPTextEncode " : " CLIP Text Encode (Prompt) " ,
" CLIPSetLastLayer " : " CLIP Set Last Layer " ,
" ConditioningCombine " : " Conditioning (Combine) " ,
2023-04-30 17:33:15 -04:00
" ConditioningAverage " : " Conditioning (Average) " ,
2023-07-13 21:43:22 -04:00
" ConditioningConcat " : " Conditioning (Concat) " ,
2023-03-30 23:13:58 +02:00
" ConditioningSetArea " : " Conditioning (Set Area) " ,
2023-09-06 03:26:55 -04:00
" ConditioningSetAreaPercentage " : " Conditioning (Set Area with Percentage) " ,
2023-04-25 00:15:25 -07:00
" ConditioningSetMask " : " Conditioning (Set Mask) " ,
2026-05-05 08:37:25 +08:00
" ControlNetApply " : " Apply ControlNet (DEPRECATED) " ,
2024-09-22 01:24:52 -04:00
" ControlNetApplyAdvanced " : " Apply ControlNet " ,
2023-03-30 23:13:58 +02:00
# Latent
" VAEEncodeForInpaint " : " VAE Encode (for Inpainting) " ,
" SetLatentNoiseMask " : " Set Latent Noise Mask " ,
" VAEDecode " : " VAE Decode " ,
" VAEEncode " : " VAE Encode " ,
" LatentRotate " : " Rotate Latent " ,
" LatentFlip " : " Flip Latent " ,
" LatentCrop " : " Crop Latent " ,
" EmptyLatentImage " : " Empty Latent Image " ,
" LatentUpscale " : " Upscale Latent " ,
2023-05-23 12:53:38 -04:00
" LatentUpscaleBy " : " Upscale Latent By " ,
2023-03-30 23:13:58 +02:00
" LatentComposite " : " Latent Composite " ,
2023-08-01 01:23:14 -05:00
" LatentBlend " : " Latent Blend " ,
2023-05-13 17:15:45 +02:00
" LatentFromBatch " : " Latent From Batch " ,
" RepeatLatentBatch " : " Repeat Latent Batch " ,
2023-03-30 23:13:58 +02:00
# Image
2026-05-05 08:37:25 +08:00
" EmptyImage " : " Empty Image " ,
2023-03-30 23:13:58 +02:00
" SaveImage " : " Save Image " ,
" PreviewImage " : " Preview Image " ,
" LoadImage " : " Load Image " ,
" LoadImageMask " : " Load Image (as Mask) " ,
2025-02-18 15:53:01 -07:00
" LoadImageOutput " : " Load Image (from Outputs) " ,
2023-03-30 23:13:58 +02:00
" ImageScale " : " Upscale Image " ,
2023-06-12 01:14:04 -04:00
" ImageScaleBy " : " Upscale Image By " ,
2026-05-05 08:37:25 +08:00
" ImageInvert " : " Invert Image Colors " ,
2023-03-30 23:13:58 +02:00
" ImagePadForOutpaint " : " Pad Image for Outpainting " ,
2026-05-05 08:37:25 +08:00
" ImageBatch " : " Batch Images (DEPRECATED) " ,
" ImageCrop " : " Crop Image " ,
" ImageStitch " : " Stitch Images " ,
" ImageBlend " : " Blend Images " ,
" ImageBlur " : " Blur Image " ,
" ImageQuantize " : " Quantize Image " ,
" ImageSharpen " : " Sharpen Image " ,
2024-10-31 13:18:05 -06:00
" ImageScaleToTotalPixels " : " Scale Image to Total Pixels " ,
2025-06-02 18:57:50 -07:00
" GetImageSize " : " Get Image Size " ,
2023-03-30 23:13:58 +02:00
# _for_testing
" VAEDecodeTiled " : " VAE Decode (Tiled) " ,
" VAEEncodeTiled " : " VAE Encode (Tiled) " ,
}
2023-08-20 19:55:48 +01:00
EXTENSION_WEB_DIRS = { }
2024-12-28 11:30:04 +01:00
# Dictionary of successfully loaded module names and associated directories.
LOADED_MODULE_DIRS = { }
2024-07-04 20:49:07 -04:00
2024-07-15 20:36:03 -04:00
def get_module_name ( module_path : str ) - > str :
2024-07-09 17:07:15 -04:00
"""
Returns the module name based on the given module path .
Examples :
2024-07-15 20:36:03 -04:00
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node.py " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node/ " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node/__init__.py " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node/__init__ " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node/__init__/ " ) - > " my_custom_node "
get_module_name ( " C:/Users/username/ComfyUI/custom_nodes/my_custom_node.disabled " ) - > " custom_nodes
2024-07-09 17:07:15 -04:00
Args :
module_path ( str ) : The path of the module .
Returns :
str : The module name .
"""
2024-07-15 20:36:03 -04:00
base_path = os . path . basename ( module_path )
2024-07-09 17:07:15 -04:00
if os . path . isfile ( module_path ) :
2024-07-15 20:36:03 -04:00
base_path = os . path . splitext ( base_path ) [ 0 ]
return base_path
2024-07-09 17:07:15 -04:00
2025-07-29 19:17:22 -07:00
async def load_custom_node ( module_path : str , ignore = set ( ) , module_parent = " custom_nodes " ) - > bool :
2025-04-09 09:08:57 -04:00
module_name = get_module_name ( module_path )
2024-07-04 21:49:50 -04:00
if os . path . isfile ( module_path ) :
sp = os . path . splitext ( module_path )
module_name = sp [ 0 ]
2025-04-09 09:08:57 -04:00
sys_module_name = module_name
elif os . path . isdir ( module_path ) :
2025-04-10 03:37:27 -04:00
sys_module_name = module_path . replace ( " . " , " _x_ " )
2025-04-09 09:08:57 -04:00
2023-03-11 12:49:41 -05:00
try :
2024-03-30 11:52:11 -04:00
logging . debug ( " Trying to load custom node {} " . format ( module_path ) )
2023-03-11 12:49:41 -05:00
if os . path . isfile ( module_path ) :
2025-04-09 09:08:57 -04:00
module_spec = importlib . util . spec_from_file_location ( sys_module_name , module_path )
2023-08-20 19:55:48 +01:00
module_dir = os . path . split ( module_path ) [ 0 ]
2023-03-11 12:49:41 -05:00
else :
2025-04-09 09:08:57 -04:00
module_spec = importlib . util . spec_from_file_location ( sys_module_name , os . path . join ( module_path , " __init__.py " ) )
2023-08-20 19:55:48 +01:00
module_dir = module_path
2023-03-11 12:49:41 -05:00
module = importlib . util . module_from_spec ( module_spec )
2025-04-09 09:08:57 -04:00
sys . modules [ sys_module_name ] = module
2023-03-11 12:49:41 -05:00
module_spec . loader . exec_module ( module )
2023-08-20 19:55:48 +01:00
2024-12-28 11:30:04 +01:00
LOADED_MODULE_DIRS [ module_name ] = os . path . abspath ( module_dir )
2025-06-12 16:24:39 -04:00
try :
from comfy_config import config_parser
project_config = config_parser . extract_node_configuration ( module_path )
web_dir_name = project_config . tool_comfy . web
if web_dir_name :
web_dir_path = os . path . join ( module_path , web_dir_name )
if os . path . isdir ( web_dir_path ) :
project_name = project_config . project . name
EXTENSION_WEB_DIRS [ project_name ] = web_dir_path
logging . info ( " Automatically register web folder {} for {} " . format ( web_dir_name , project_name ) )
except Exception as e :
2025-06-12 14:14:59 -07:00
logging . warning ( f " Unable to parse pyproject.toml due to lack dependency pydantic-settings, please run ' pip install -r requirements.txt ' : { e } " )
2025-06-12 16:24:39 -04:00
2023-08-20 19:55:48 +01:00
if hasattr ( module , " WEB_DIRECTORY " ) and getattr ( module , " WEB_DIRECTORY " ) is not None :
web_dir = os . path . abspath ( os . path . join ( module_dir , getattr ( module , " WEB_DIRECTORY " ) ) )
if os . path . isdir ( web_dir ) :
EXTENSION_WEB_DIRS [ module_name ] = web_dir
2025-07-31 15:02:12 -07:00
# V1 node definition
2023-03-11 12:49:41 -05:00
if hasattr ( module , " NODE_CLASS_MAPPINGS " ) and getattr ( module , " NODE_CLASS_MAPPINGS " ) is not None :
2024-07-09 17:07:15 -04:00
for name , node_cls in module . NODE_CLASS_MAPPINGS . items ( ) :
2023-07-13 12:52:42 -04:00
if name not in ignore :
2024-07-09 17:07:15 -04:00
NODE_CLASS_MAPPINGS [ name ] = node_cls
2024-07-15 20:36:03 -04:00
node_cls . RELATIVE_PYTHON_MODULE = " {} . {} " . format ( module_parent , get_module_name ( module_path ) )
2023-03-31 07:05:17 +02:00
if hasattr ( module , " NODE_DISPLAY_NAME_MAPPINGS " ) and getattr ( module , " NODE_DISPLAY_NAME_MAPPINGS " ) is not None :
NODE_DISPLAY_NAME_MAPPINGS . update ( module . NODE_DISPLAY_NAME_MAPPINGS )
2023-05-13 13:23:42 -04:00
return True
2025-07-31 15:02:12 -07:00
# V3 Extension Definition
elif hasattr ( module , " comfy_entrypoint " ) :
entrypoint = getattr ( module , " comfy_entrypoint " )
if not callable ( entrypoint ) :
logging . warning ( f " comfy_entrypoint in { module_path } is not callable, skipping. " )
return False
try :
if inspect . iscoroutinefunction ( entrypoint ) :
extension = await entrypoint ( )
else :
extension = entrypoint ( )
if not isinstance ( extension , ComfyExtension ) :
logging . warning ( f " comfy_entrypoint in { module_path } did not return a ComfyExtension, skipping. " )
return False
2026-02-15 02:12:30 -08:00
await extension . on_load ( )
2025-07-31 15:02:12 -07:00
node_list = await extension . get_node_list ( )
if not isinstance ( node_list , list ) :
logging . warning ( f " comfy_entrypoint in { module_path } did not return a list of nodes, skipping. " )
return False
for node_cls in node_list :
node_cls : io . ComfyNode
schema = node_cls . GET_SCHEMA ( )
if schema . node_id not in ignore :
NODE_CLASS_MAPPINGS [ schema . node_id ] = node_cls
node_cls . RELATIVE_PYTHON_MODULE = " {} . {} " . format ( module_parent , get_module_name ( module_path ) )
if schema . display_name is not None :
NODE_DISPLAY_NAME_MAPPINGS [ schema . node_id ] = schema . display_name
return True
except Exception as e :
logging . warning ( f " Error while calling comfy_entrypoint in { module_path } : { e } " )
return False
2023-03-11 12:49:41 -05:00
else :
2025-07-31 15:02:12 -07:00
logging . warning ( f " Skip { module_path } module for custom nodes due to the lack of NODE_CLASS_MAPPINGS or NODES_LIST (need one). " )
2023-05-13 13:23:42 -04:00
return False
2023-03-11 12:49:41 -05:00
except Exception as e :
2024-03-11 00:56:41 -04:00
logging . warning ( traceback . format_exc ( ) )
2024-03-11 16:24:47 -04:00
logging . warning ( f " Cannot import { module_path } module for custom nodes: { e } " )
2023-05-13 13:23:42 -04:00
return False
2023-03-11 12:49:41 -05:00
2025-07-29 19:17:22 -07:00
async def init_external_custom_nodes ( ) :
2024-07-01 17:54:03 -04:00
"""
Initializes the external custom nodes .
This function loads custom nodes from the specified folder paths and imports them into the application .
It measures the import times for each custom node and logs the results .
Returns :
None
"""
2023-07-13 12:52:42 -04:00
base_node_names = set ( NODE_CLASS_MAPPINGS . keys ( ) )
2023-04-16 01:36:15 -04:00
node_paths = folder_paths . get_folder_paths ( " custom_nodes " )
2023-05-13 11:54:45 -04:00
node_import_times = [ ]
2023-04-16 01:36:15 -04:00
for custom_node_path in node_paths :
2023-11-23 22:24:58 +01:00
possible_modules = os . listdir ( os . path . realpath ( custom_node_path ) )
2023-04-16 01:36:15 -04:00
if " __pycache__ " in possible_modules :
possible_modules . remove ( " __pycache__ " )
for possible_module in possible_modules :
module_path = os . path . join ( custom_node_path , possible_module )
2026-01-01 19:06:14 -08:00
if os . path . isfile ( module_path ) and os . path . splitext ( module_path ) [ 1 ] != " .py " :
continue
if module_path . endswith ( " .disabled " ) :
continue
2025-06-29 03:24:02 +08:00
if args . disable_all_custom_nodes and possible_module not in args . whitelist_custom_nodes :
logging . info ( f " Skipping { possible_module } due to disable_all_custom_nodes and whitelist_custom_nodes " )
continue
2025-12-02 12:32:52 +09:00
if args . enable_manager :
if comfyui_manager . should_be_disabled ( module_path ) :
logging . info ( f " Blocked by policy: { module_path } " )
continue
2023-05-13 15:31:22 -04:00
time_before = time . perf_counter ( )
2025-07-29 19:17:22 -07:00
success = await load_custom_node ( module_path , base_node_names , module_parent = " custom_nodes " )
2023-05-13 15:31:22 -04:00
node_import_times . append ( ( time . perf_counter ( ) - time_before , module_path , success ) )
2023-05-13 11:54:45 -04:00
2023-05-13 13:15:31 -04:00
if len ( node_import_times ) > 0 :
2024-03-11 13:54:56 -04:00
logging . info ( " \n Import times for custom nodes: " )
2023-05-13 13:15:31 -04:00
for n in sorted ( node_import_times ) :
2023-05-13 13:23:42 -04:00
if n [ 2 ] :
import_message = " "
else :
import_message = " (IMPORT FAILED) "
2024-03-11 13:54:56 -04:00
logging . info ( " {:6.1f} seconds {} : {} " . format ( n [ 0 ] , import_message , n [ 1 ] ) )
logging . info ( " " )
2023-02-17 11:19:49 -05:00
2025-07-29 19:17:22 -07:00
async def init_builtin_extra_nodes ( ) :
2024-07-01 17:54:03 -04:00
"""
Initializes the built - in extra nodes in ComfyUI .
This function loads the extra node files located in the " comfy_extras " directory and imports them into ComfyUI .
If any of the extra node files fail to import , a warning message is logged .
Returns :
None
"""
2023-10-02 17:26:59 -04:00
extras_dir = os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , " comfy_extras " )
extras_files = [
" nodes_latent.py " ,
" nodes_hypernetwork.py " ,
" nodes_upscale_model.py " ,
" nodes_post_processing.py " ,
" nodes_mask.py " ,
2023-09-22 23:03:22 +02:00
" nodes_compositing.py " ,
2023-10-02 17:26:59 -04:00
" nodes_rebatch.py " ,
" nodes_model_merging.py " ,
" nodes_tomesd.py " ,
" nodes_clip_sdxl.py " ,
" nodes_canny.py " ,
" nodes_freelunch.py " ,
2023-10-21 05:16:38 -04:00
" nodes_custom_sampler.py " ,
" nodes_hypertile.py " ,
2023-11-07 03:28:53 -05:00
" nodes_model_advanced.py " ,
2023-11-16 13:23:25 -05:00
" nodes_model_downscale.py " ,
2023-11-18 04:44:17 -05:00
" nodes_images.py " ,
2023-11-23 19:43:09 -05:00
" nodes_video_model.py " ,
2025-06-14 07:25:59 +08:00
" nodes_train.py " ,
2025-11-27 08:18:08 +08:00
" nodes_dataset.py " ,
2023-12-13 21:52:11 +01:00
" nodes_sag.py " ,
2023-12-16 00:28:16 +05:30
" nodes_perpneg.py " ,
2023-12-18 03:18:40 -05:00
" nodes_stable3d.py " ,
2024-01-03 03:30:39 -05:00
" nodes_sdupscale.py " ,
2024-01-24 09:49:57 -05:00
" nodes_photomaker.py " ,
2024-12-20 21:25:00 +01:00
" nodes_pixart.py " ,
2024-02-10 08:27:05 -05:00
" nodes_cond.py " ,
2024-03-04 18:50:28 +01:00
" nodes_morphology.py " ,
2024-02-16 12:56:11 -05:00
" nodes_stable_cascade.py " ,
2024-03-03 12:34:13 -08:00
" nodes_differential_diffusion.py " ,
2024-04-04 15:06:17 -04:00
" nodes_ip2p.py " ,
2024-04-09 04:25:45 -04:00
" nodes_model_merging_model_specific.py " ,
2024-04-14 23:34:25 -04:00
" nodes_pag.py " ,
2024-04-20 04:31:49 -04:00
" nodes_align_your_steps.py " ,
2024-04-28 12:50:22 -04:00
" nodes_attention_multiply.py " ,
2024-04-29 20:00:47 -04:00
" nodes_advanced_samplers.py " ,
2024-05-17 18:16:08 +01:00
" nodes_webcam.py " ,
2024-06-15 12:14:56 -04:00
" nodes_audio.py " ,
2024-06-10 13:26:25 -04:00
" nodes_sd3.py " ,
2024-06-20 20:12:15 +08:00
" nodes_gits.py " ,
2024-07-16 17:01:40 -04:00
" nodes_controlnet.py " ,
2024-07-26 13:04:48 -04:00
" nodes_hunyuan.py " ,
2025-10-02 00:59:07 +03:00
" nodes_eps.py " ,
2024-08-01 18:53:25 -04:00
" nodes_flux.py " ,
2024-09-04 16:38:38 -04:00
" nodes_lora_extract.py " ,
2024-09-12 05:23:32 -04:00
" nodes_torch_compile.py " ,
2024-10-26 06:54:00 -04:00
" nodes_mochi.py " ,
2024-11-18 02:20:43 -05:00
" nodes_slg.py " ,
2024-12-11 13:51:51 -08:00
" nodes_mahiro.py " ,
2026-01-04 22:58:59 -08:00
" nodes_lt_upsampler.py " ,
" nodes_lt_audio.py " ,
2024-11-22 08:44:42 -05:00
" nodes_lt.py " ,
ModelPatcher Overhaul and Hook Support (#5583)
* Added hook_patches to ModelPatcher for weights (model)
* Initial changes to calc_cond_batch to eventually support hook_patches
* Added current_patcher property to BaseModel
* Consolidated add_hook_patches_as_diffs into add_hook_patches func, fixed fp8 support for model-as-lora feature
* Added call to initialize_timesteps on hooks in process_conds func, and added call prepare current keyframe on hooks in calc_cond_batch
* Added default_conds support in calc_cond_batch func
* Added initial set of hook-related nodes, added code to register hooks for loras/model-as-loras, small renaming/refactoring
* Made CLIP work with hook patches
* Added initial hook scheduling nodes, small renaming/refactoring
* Fixed MaxSpeed and default conds implementations
* Added support for adding weight hooks that aren't registered on the ModelPatcher at sampling time
* Made Set Clip Hooks node work with hooks from Create Hook nodes, began work on better Create Hook Model As LoRA node
* Initial work on adding 'model_as_lora' lora type to calculate_weight
* Continued work on simpler Create Hook Model As LoRA node, started to implement ModelPatcher callbacks, attachments, and additional_models
* Fix incorrect ref to create_hook_patches_clone after moving function
* Added injections support to ModelPatcher + necessary bookkeeping, added additional_models support in ModelPatcher, conds, and hooks
* Added wrappers to ModelPatcher to facilitate standardized function wrapping
* Started scaffolding for other hook types, refactored get_hooks_from_cond to organize hooks by type
* Fix skip_until_exit logic bug breaking injection after first run of model
* Updated clone_has_same_weights function to account for new ModelPatcher properties, improved AutoPatcherEjector usage in partially_load
* Added WrapperExecutor for non-classbound functions, added calc_cond_batch wrappers
* Refactored callbacks+wrappers to allow storing lists by id
* Added forward_timestep_embed_patch type, added helper functions on ModelPatcher for emb_patch and forward_timestep_embed_patch, added helper functions for removing callbacks/wrappers/additional_models by key, added custom_should_register prop to hooks
* Added get_attachment func on ModelPatcher
* Implement basic MemoryCounter system for determing with cached weights due to hooks should be offloaded in hooks_backup
* Modified ControlNet/T2IAdapter get_control function to receive transformer_options as additional parameter, made the model_options stored in extra_args in inner_sample be a clone of the original model_options instead of same ref
* Added create_model_options_clone func, modified type annotations to use __future__ so that I can use the better type annotations
* Refactored WrapperExecutor code to remove need for WrapperClassExecutor (now gone), added sampler.sample wrapper (pending review, will likely keep but will see what hacks this could currently let me get rid of in ACN/ADE)
* Added Combine versions of Cond/Cond Pair Set Props nodes, renamed Pair Cond to Cond Pair, fixed default conds never applying hooks (due to hooks key typo)
* Renamed Create Hook Model As LoRA nodes to make the test node the main one (more changes pending)
* Added uuid to conds in CFGGuider and uuids to transformer_options to allow uniquely identifying conds in batches during sampling
* Fixed models not being unloaded properly due to current_patcher reference; the current ComfyUI model cleanup code requires that nothing else has a reference to the ModelPatcher instances
* Fixed default conds not respecting hook keyframes, made keyframes not reset cache when strength is unchanged, fixed Cond Set Default Combine throwing error, fixed model-as-lora throwing error during calculate_weight after a recent ComfyUI update, small refactoring/scaffolding changes for hooks
* Changed CreateHookModelAsLoraTest to be the new CreateHookModelAsLora, rename old ones as 'direct' and will be removed prior to merge
* Added initial support within CLIP Text Encode (Prompt) node for scheduling weight hook CLIP strength via clip_start_percent/clip_end_percent on conds, added schedule_clip toggle to Set CLIP Hooks node, small cleanup/fixes
* Fix range check in get_hooks_for_clip_schedule so that proper keyframes get assigned to corresponding ranges
* Optimized CLIP hook scheduling to treat same strength as same keyframe
* Less fragile memory management.
* Make encode_from_tokens_scheduled call cleaner, rollback change in model_patcher.py for hook_patches_backup dict
* Fix issue.
* Remove useless function.
* Prevent and detect some types of memory leaks.
* Run garbage collector when switching workflow if needed.
* Moved WrappersMP/CallbacksMP/WrapperExecutor to patcher_extension.py
* Refactored code to store wrappers and callbacks in transformer_options, added apply_model and diffusion_model.forward wrappers
* Fix issue.
* Refactored hooks in calc_cond_batch to be part of get_area_and_mult tuple, added extra_hooks to ControlBase to allow custom controlnets w/ hooks, small cleanup and renaming
* Fixed inconsistency of results when schedule_clip is set to False, small renaming/typo fixing, added initial support for ControlNet extra_hooks to work in tandem with normal cond hooks, initial work on calc_cond_batch merging all subdicts in returned transformer_options
* Modified callbacks and wrappers so that unregistered types can be used, allowing custom_nodes to have their own unique callbacks/wrappers if desired
* Updated different hook types to reflect actual progress of implementation, initial scaffolding for working WrapperHook functionality
* Fixed existing weight hook_patches (pre-registered) not working properly for CLIP
* Removed Register/Direct hook nodes since they were present only for testing, removed diff-related weight hook calculation as improved_memory removes unload_model_clones and using sample time registered hooks is less hacky
* Added clip scheduling support to all other native ComfyUI text encoding nodes (sdxl, flux, hunyuan, sd3)
* Made WrapperHook functional, added another wrapper/callback getter, added ON_DETACH callback to ModelPatcher
* Made opt_hooks append by default instead of replace, renamed comfy.hooks set functions to be more accurate
* Added apply_to_conds to Set CLIP Hooks, modified relevant code to allow text encoding to automatically apply hooks to output conds when apply_to_conds is set to True
* Fix cached_hook_patches not respecting target_device/memory_counter results
* Fixed issue with setting weights from hooks instead of copying them, added additional memory_counter check when caching hook patches
* Remove unnecessary torch.no_grad calls for hook patches
* Increased MemoryCounter minimum memory to leave free by *2 until a better way to get inference memory estimate of currently loaded models exists
* For encode_from_tokens_scheduled, allow start_percent and end_percent in add_dict to limit which scheduled conds get encoded for optimization purposes
* Removed a .to call on results of calculate_weight in patch_hook_weight_to_device that was screwing up the intermediate results for fp8 prior to being passed into stochastic_rounding call
* Made encode_from_tokens_scheduled work when no hooks are set on patcher
* Small cleanup of comments
* Turn off hook patch caching when only 1 hook present in sampling, replace some current_hook = None with calls to self.patch_hooks(None) instead to avoid a potential edge case
* On Cond/Cond Pair nodes, removed opt_ prefix from optional inputs
* Allow both FLOATS and FLOAT for floats_strength input
* Revert change, does not work
* Made patch_hook_weight_to_device respect set_func and convert_func
* Make discard_model_sampling True by default
* Add changes manually from 'master' so merge conflict resolution goes more smoothly
* Cleaned up text encode nodes with just a single clip.encode_from_tokens_scheduled call
* Make sure encode_from_tokens_scheduled will respect use_clip_schedule on clip
* Made nodes in nodes_hooks be marked as experimental (beta)
* Add get_nested_additional_models for cases where additional_models could have their own additional_models, and add robustness for circular additional_models references
* Made finalize_default_conds area math consistent with other sampling code
* Changed 'opt_hooks' input of Cond/Cond Pair Set Default Combine nodes to 'hooks'
* Remove a couple old TODO's and a no longer necessary workaround
2024-12-02 13:51:02 -06:00
" nodes_hooks.py " ,
2024-12-13 18:13:52 -05:00
" nodes_load_3d.py " ,
2025-01-10 09:11:57 -05:00
" nodes_cosmos.py " ,
2025-02-19 07:11:49 -05:00
" nodes_video.py " ,
2025-02-17 07:15:43 +08:00
" nodes_lumina2.py " ,
2025-02-26 01:49:43 -05:00
" nodes_wan.py " ,
2025-03-21 11:04:15 -07:00
" nodes_lotus.py " ,
2025-03-19 16:19:50 -04:00
" nodes_hunyuan3d.py " ,
2025-03-21 01:47:18 -04:00
" nodes_primitive.py " ,
2025-03-26 05:08:49 -04:00
" nodes_cfg.py " ,
2025-04-15 17:35:05 -04:00
" nodes_optimalsteps.py " ,
2025-04-18 00:54:33 +05:30
" nodes_hidream.py " ,
" nodes_fresca.py " ,
2025-05-12 18:10:24 -07:00
" nodes_apg.py " ,
2025-05-02 13:15:54 -04:00
" nodes_preview_any.py " ,
2025-05-07 05:33:34 -07:00
" nodes_ace.py " ,
2025-05-12 16:29:32 -04:00
" nodes_string.py " ,
2025-05-15 19:00:43 -04:00
" nodes_camera_trajectory.py " ,
2025-06-25 16:35:57 -07:00
" nodes_edit_model.py " ,
2025-07-31 15:02:12 -07:00
" nodes_tcfg.py " ,
2025-08-13 18:33:05 -07:00
" nodes_context_windows.py " ,
2025-08-18 19:38:34 -07:00
" nodes_qwen.py " ,
2025-09-13 15:58:43 -06:00
" nodes_chroma_radiance.py " ,
Implement EasyCache and Invent LazyCache (#9496)
* Attempting a universal implementation of EasyCache, starting with flux as test; I screwed up the math a bit, but when I set it just right it works.
* Fixed math to make threshold work as expected, refactored code to use EasyCacheHolder instead of a dict wrapped by object
* Use sigmas from transformer_options instead of timesteps to be compatible with a greater amount of models, make end_percent work
* Make log statement when not skipping useful, preparing for per-cond caching
* Added DIFFUSION_MODEL wrapper around forward function for wan model
* Add subsampling for heuristic inputs
* Add subsampling to output_prev (output_prev_subsampled now)
* Properly consider conds in EasyCache logic
* Created SuperEasyCache to test what happens if caching and reuse is moved outside the scope of conds, added PREDICT_NOISE wrapper to facilitate this test
* Change max reuse_threshold to 3.0
* Mark EasyCache/SuperEasyCache as experimental (beta)
* Make Lumina2 compatible with EasyCache
* Add EasyCache support for Qwen Image
* Fix missing comma, curse you Cursor
* Add EasyCache support to AceStep
* Add EasyCache support to Chroma
* Added EasyCache support to Cosmos Predict t2i
* Make EasyCache not crash with Cosmos Predict ImagToVideo latents, but does not work well at all
* Add EasyCache support to hidream
* Added EasyCache support to hunyuan video
* Added EasyCache support to hunyuan3d
* Added EasyCache support to LTXV (not very good, but does not crash)
* Implemented EasyCache for aura_flow
* Renamed SuperEasyCache to LazyCache, hardcoded subsample_factor to 8 on nodes
* Eatra logging when verbose is true for EasyCache
2025-08-22 19:41:08 -07:00
" nodes_model_patch.py " ,
" nodes_easycache.py " ,
2025-08-25 20:26:47 -07:00
" nodes_audio_encoder.py " ,
2025-10-30 19:11:38 -07:00
" nodes_rope.py " ,
Add MatchType, DynamicCombo, and Autogrow support to V3 Schema (#10832)
* Added output_matchtypes to generated json for v3, initial backend support for MatchType, created nodes_logic.py and added SwitchNode
* Fixed providing list of allowed_types
* Add workaround in validation.py for V3 Combo outputs not working as Combo inputs
* Make match type receive_type pass validation
* Also add MatchType check to input_type in validation - will likely trigger when connecting to non-lazy stuff
* Make sure this PR only has MatchType stuff
* Initial work on DynamicCombo
* Add get_dynamic function, not yet filled out correctly
* Mark Switch node as Beta
* Make sure other unfinished dynamic types are not accidentally used
* Send DynamicCombo.Option inputs in the same format as normal v1 inputs
* add dynamic combo test node
* Support validation of inputs and outputs
* Add missing input params to DynamicCombo.Input
* Add get_all function to inputs for id validation purposes
* Fix imports for v3 returning everything when doing io/ui/IO/UI instead of what is in __all__ of _io.py and _ui.py
* Modifying behavior of get_dynamic in V3 + serialization so can be used in execution code
* Fix v3 schema validation code after changes
* Refactor hidden_values for v3 in execution.py to be more general v3_data, add helper functions for dynamic behavior, preparing for restructuring dynamic type into object (not finished yet)
* Add nesting of inputs on DynamicCombo during execution
* Work with latest frontend commits
* Fix cringe arrows
* frontend will no longer namespace dynamic inputs widgets so reflect that in code, refactor build_nested_inputs
* Prepare Autogrow support for the love of the game
* satisfy ruff
* Create test nodes for Autogrow to collab with frontend development
* Add nested combo to DCTestNode
* Remove array support from build_nested_inputs, properly handle missing expected values
* Make execution.validate_inputs properly validate required dynamic inputs, renamed dynamic_data to dynamic_paths for clarity
* MatchType does not need any DynamicInput/Output features on backend; will increase compatibility with dynamic types
* Probably need this for ruff check
* Change MatchType to have template be the first and only required param; output id's do nothing right now, so no need
* Fix merge regression with LatentUpscaleModel type not being put in __all__ for _io.py, fix invalid type hint for validate_inputs
* Make Switch node inputs optional, disallow both inputs from being missing, and still work properly with lazy; when one input is missing, use the other no matter what the switch is set to
* Satisfy ruff
* Move MatchType code above the types that inherit from DynamicInput
* Add DynamicSlot type, awaiting frontend support
* Make curr_prefix creation happen in Autogrow, move curr_prefix in DynamicCombo to only be created if input exists in live_inputs
* I was confused, fixing accidentally redundant curr_prefix addition in Autogrow
* Make sure Autogrow inputs are force_input = True when WidgetInput, fix runtime validation by removing original input from expected inputs, fix min/max bounds, change test nodes slightly
* Remove unnecessary id usage in Autogrow test node outputs
* Commented out Switch node + test nodes
* Remove commented out code from Autogrow
* Make TemplatePrefix max more clear, allow max == 1
* Replace all dict[str] with dict[str, Any]
* Renamed add_to_dict_live_inputs to expand_schema_for_dynamic
* Fixed typo in DynamicSlot input code
* note about live_inputs not being present soon in get_v1_info (internal function anyway)
* For now, hide DynamicCombo and Autogrow from public interface
* Removed comment
2025-12-02 21:17:13 -08:00
" nodes_logic.py " ,
2026-02-27 09:13:57 -08:00
" nodes_resolution.py " ,
2025-11-17 21:26:44 -08:00
" nodes_nop.py " ,
2025-12-06 05:20:22 +02:00
" nodes_kandinsky5.py " ,
2025-12-12 05:29:34 +02:00
" nodes_wanmove.py " ,
2026-01-09 00:31:19 -05:00
" nodes_image_compare.py " ,
2026-01-19 20:17:38 -08:00
" nodes_zimage.py " ,
2026-02-20 04:22:13 +00:00
" nodes_glsl.py " ,
2026-01-30 15:01:33 -08:00
" nodes_lora_debug.py " ,
2026-02-19 03:49:43 +02:00
" nodes_textgen.py " ,
2026-02-04 22:18:21 -08:00
" nodes_color.py " ,
" nodes_toolkit.py " ,
2026-02-15 02:12:30 -08:00
" nodes_replacements.py " ,
2026-02-16 20:30:34 -08:00
" nodes_nag.py " ,
2026-02-27 02:59:05 +02:00
" nodes_sdpose.py " ,
feat: add Math Expression node with simpleeval evaluation (#12687)
* feat: add EagerEval dataclass for frontend-side node evaluation
Add EagerEval to the V3 API schema, enabling nodes to declare
frontend-evaluated JSONata expressions. The frontend uses this to
display computation results as badges without a backend round-trip.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Math Expression node with JSONata evaluation
Add ComfyMathExpression node that evaluates JSONata expressions against
dynamically-grown numeric inputs using Autogrow + MatchType. Sends
input context via ui output so the frontend can re-evaluate when
the expression changes without a backend round-trip.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: register nodes_math.py in extras_files loader list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address CodeRabbit review feedback
- Harden EagerEval.validate with type checks and strip() for empty strings
- Add _positional_alias for spreadsheet-style names beyond z (aa, ab...)
- Validate JSONata result is numeric before returning
- Add jsonata to requirements.txt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove EagerEval, scope PR to math node only
Remove EagerEval dataclass from _io.py and eager_eval usage from
nodes_math.py. Eager execution will be designed as a general-purpose
system in a separate effort.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use TemplateNames, cap inputs at 26, improve error message
Address Kosinkadink review feedback:
- Switch from Autogrow.TemplatePrefix to Autogrow.TemplateNames so input
slots are named a-z, matching expression variables directly
- Cap max inputs at 26 (a-z) instead of 100
- Simplify execute() by removing dual-mapping hack
- Include expression and result value in error message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add unit tests for Math Expression node
Add tests for _positional_alias (a-z mapping) and execute() covering
arithmetic operations, float inputs, $sum(values), and error cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace jsonata with simpleeval for math evaluation
jsonata PyPI package has critical issues: no Python 3.12/3.13 wheels,
no ARM/Apple Silicon wheels, abandoned (last commit 2023), C extension.
Replace with simpleeval (pure Python, 3.4M downloads/month, MIT,
AST-based security). Add math module functions (sqrt, ceil, floor,
log, sin, cos, tan) and variadic sum() supporting both sum(values)
and sum(a, b, c). Pin version to >=1.0,<2.0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: update tests for simpleeval migration
Update JSONata syntax to Python syntax ($sum -> sum, $string -> str),
add tests for math functions (sqrt, ceil, floor, sin, log10) and
variadic sum(a, b, c).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace MatchType with MultiType inputs and dual FLOAT/INT outputs
Allow mixing INT and FLOAT connections on the same node by switching
from MatchType (which forces all inputs to the same type) to MultiType.
Output both FLOAT and INT so users can pick the type they need.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: update tests for mixed INT/FLOAT inputs and dual outputs
Add assertions for both FLOAT (result[0]) and INT (result[1]) outputs.
Add test_mixed_int_float_inputs and test_mixed_resolution_scale to
verify the primary use case of multiplying resolutions by a float factor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: make expression input multiline and validate empty expression
- Add multiline=True to expression input for better UX with longer expressions
- Add empty expression validation with clear "Expression cannot be empty." message
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for empty expression validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review feedback — safe pow, isfinite guard, test coverage
- Wrap pow() with _safe_pow to prevent DoS via huge exponents
(pow() bypasses simpleeval's safe_power guard on **)
- Add math.isfinite() check to catch inf/nan before int() conversion
- Add int/float converters to MATH_FUNCTIONS for explicit casting
- Add "calculator" search alias
- Replace _positional_alias helper with string.ascii_lowercase
- Narrow test assertions and add error path + function coverage tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update requirements.txt
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
Co-authored-by: Christian Byrne <abolkonsky.rem@gmail.com>
2026-03-06 11:51:28 +09:00
" nodes_math.py " ,
Add Number Convert node (#13041)
* Add Number Convert node for unified numeric type conversion
Consolidates fragmented IntToFloat/FloatToInt nodes (previously only
available via third-party packs like ComfyMath, FillNodes, etc.) into
a single core node.
- Single input accepting INT, FLOAT, STRING, and BOOL types
- Two outputs: FLOAT and INT
- Conversion: bool→0/1, string→parsed number, float↔int standard cast
- Follows Math Expression node patterns (comfy_api, io.Schema, etc.)
Refs: COM-16925
* Register nodes_number_convert.py in extras_files list
Without this entry in nodes.py, the Number Convert node file
would not be discovered and loaded at startup.
* Add isfinite guard, exception chaining, and unit tests for Number Convert node
- Add math.isfinite() check to prevent int() crash on inf/nan string inputs
- Use 'from None' for cleaner exception chaining on string parse failure
- Add 21 unit tests covering all input types and error paths
2026-03-25 07:38:08 +09:00
" nodes_number_convert.py " ,
2026-03-12 09:55:29 -07:00
" nodes_painter.py " ,
2026-03-24 17:47:28 -04:00
" nodes_curve.py " ,
2026-04-22 14:16:02 +03:00
" nodes_rtdetr.py " ,
" nodes_frame_interpolation.py " ,
2026-04-30 01:30:08 +02:00
" nodes_sam3.py " ,
2023-10-02 17:26:59 -04:00
]
2025-05-06 01:53:53 -07:00
import_failed = [ ]
for node_file in extras_files :
2025-07-29 19:17:22 -07:00
if not await load_custom_node ( os . path . join ( extras_dir , node_file ) , module_parent = " comfy_extras " ) :
2025-05-06 01:53:53 -07:00
import_failed . append ( node_file )
return import_failed
2025-07-29 19:17:22 -07:00
async def init_builtin_api_nodes ( ) :
2025-04-23 12:38:34 -07:00
api_nodes_dir = os . path . join ( os . path . dirname ( os . path . realpath ( __file__ ) ) , " comfy_api_nodes " )
2026-01-18 08:40:39 +02:00
api_nodes_files = sorted ( glob . glob ( os . path . join ( api_nodes_dir , " nodes_*.py " ) ) )
2025-04-23 12:38:34 -07:00
2024-03-04 13:24:08 -05:00
import_failed = [ ]
2025-04-23 12:38:34 -07:00
for node_file in api_nodes_files :
2026-01-18 08:40:39 +02:00
if not await load_custom_node ( node_file , module_parent = " comfy_api_nodes " ) :
import_failed . append ( os . path . basename ( node_file ) )
2025-04-23 12:38:34 -07:00
2024-07-04 21:43:23 -04:00
return import_failed
2025-07-29 19:17:22 -07:00
async def init_public_apis ( ) :
register_versions ( [
ComfyAPIWithVersion (
version = getattr ( v , " VERSION " ) ,
api_class = v
) for v in supported_versions
] )
async def init_extra_nodes ( init_custom_nodes = True , init_api_nodes = True ) :
await init_public_apis ( )
2024-07-04 21:43:23 -04:00
2025-07-29 19:17:22 -07:00
import_failed = await init_builtin_extra_nodes ( )
2024-07-04 21:43:23 -04:00
2025-05-06 01:53:53 -07:00
import_failed_api = [ ]
if init_api_nodes :
2025-07-29 19:17:22 -07:00
import_failed_api = await init_builtin_api_nodes ( )
2025-05-06 01:53:53 -07:00
2024-07-04 21:43:23 -04:00
if init_custom_nodes :
2025-07-29 19:17:22 -07:00
await init_external_custom_nodes ( )
2024-07-04 21:43:23 -04:00
else :
logging . info ( " Skipping loading of custom nodes " )
2025-05-06 01:53:53 -07:00
if len ( import_failed_api ) > 0 :
logging . warning ( " WARNING: some comfy_api_nodes/ nodes did not import correctly. This may be because they are missing some dependencies. \n " )
for node in import_failed_api :
logging . warning ( " IMPORT FAILED: {} " . format ( node ) )
logging . warning ( " \n This issue might be caused by new missing dependencies added the last time you updated ComfyUI. " )
if args . windows_standalone_build :
logging . warning ( " Please run the update script: update/update_comfyui.bat " )
else :
logging . warning ( " Please do a: pip install -r requirements.txt " )
logging . warning ( " " )
2024-03-04 13:24:08 -05:00
if len ( import_failed ) > 0 :
2024-03-11 00:56:41 -04:00
logging . warning ( " WARNING: some comfy_extras/ nodes did not import correctly. This may be because they are missing some dependencies. \n " )
2024-03-04 13:24:08 -05:00
for node in import_failed :
2024-03-11 00:56:41 -04:00
logging . warning ( " IMPORT FAILED: {} " . format ( node ) )
logging . warning ( " \n This issue might be caused by new missing dependencies added the last time you updated ComfyUI. " )
2024-03-04 13:24:08 -05:00
if args . windows_standalone_build :
2024-03-11 00:56:41 -04:00
logging . warning ( " Please run the update script: update/update_comfyui.bat " )
2024-03-04 13:24:08 -05:00
else :
2024-03-11 00:56:41 -04:00
logging . warning ( " Please do a: pip install -r requirements.txt " )
logging . warning ( " " )
2024-12-27 18:02:21 -05:00
2024-08-31 02:26:47 +03:00
return import_failed