- Global control to enable or disable angle prompts - Added `Hollow Mode` for more intuitive visualization - Removed label quantity limit; now supports unlimited additions - Label addition button will copy parameters from the selected page - Double-clicking any face of the cube quickly switches camera angles for easier operation - 全局控制是否添加角度提示词 - 新增了`镂空模式`,可更直观地展示 - 去除标签限制个数,可添加无数个 - 标签添加按钮将复制选中页的参数 - 双击正方体的每一面可以快速切换摄像机角度,便于操作
635 lines
16 KiB
Markdown
635 lines
16 KiB
Markdown
---
|
|
applyTo: "**/*.py"
|
|
description: "ComfyUI v3 API Reference"
|
|
---
|
|
|
|
# ComfyUI v3 API Reference
|
|
|
|
Complete reference for the ComfyUI v3 node API, including all types, methods, and decorators.
|
|
|
|
## Core Classes
|
|
|
|
### ComfyNodeV3
|
|
|
|
Base class for all v3 nodes.
|
|
|
|
```python
|
|
from comfy_api.latest import io
|
|
|
|
class CustomNode(io.ComfyNode):
|
|
# Class properties set during execution
|
|
state: NodeState # Persistent state storage
|
|
resources: Resources # Resource loader with caching
|
|
hidden: HiddenHolder # Access to hidden inputs
|
|
|
|
@classmethod
|
|
@abstractmethod
|
|
def define_schema(cls) -> io.ComfyNode:
|
|
"""Define node schema. Must be overridden."""
|
|
pass
|
|
|
|
@classmethod
|
|
@abstractmethod
|
|
def execute(cls, **kwargs) -> io.NodeOutput:
|
|
"""Execute node logic. Can be async."""
|
|
pass
|
|
|
|
@classmethod
|
|
def validate_inputs(cls, **kwargs) -> bool:
|
|
"""Optional: Validate inputs before execution."""
|
|
pass
|
|
|
|
@classmethod
|
|
def fingerprint_inputs(cls, **kwargs) -> Any:
|
|
"""Optional: Generate a fingerprint for caching."""
|
|
pass
|
|
|
|
@classmethod
|
|
def GET_SERIALIZERS(cls) -> list[Serializer]:
|
|
"""Optional: Define custom serializers."""
|
|
return []
|
|
```
|
|
|
|
### io.ComfyNode
|
|
|
|
Node definition schema.
|
|
|
|
```python
|
|
@dataclass
|
|
class io.ComfyNode:
|
|
node_id: str # Globally unique ID
|
|
display_name: str = None # UI display name
|
|
category: str = "sd" # Node category
|
|
inputs: list[InputV3] = None # Input definitions
|
|
outputs: list[OutputV3] = None # Output definitions
|
|
hidden: list[Hidden] = None # Hidden inputs
|
|
description: str = "" # Tooltip description
|
|
is_input_list: bool = False # Handle list inputs
|
|
is_output_node: bool = False # Force execution
|
|
is_deprecated: bool = False # Mark as deprecated
|
|
is_experimental: bool = False # Mark as experimental
|
|
is_api_node: bool = False # API node flag
|
|
not_idempotent: bool = False # Disable caching
|
|
```
|
|
|
|
### NodeOutput
|
|
|
|
Structured return value from `execute`.
|
|
|
|
```python
|
|
class NodeOutput:
|
|
def __init__(
|
|
self,
|
|
*args: Any, # Output values
|
|
ui: UIOutput | dict = None, # UI elements
|
|
expand: dict = None, # Subgraph expansion
|
|
block_execution: str = None # Execution blocker
|
|
):
|
|
pass
|
|
```
|
|
|
|
## Input Types
|
|
|
|
### Basic Inputs
|
|
|
|
```python
|
|
# Integer input
|
|
io.Int.Input(
|
|
id: str,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
default: int = None,
|
|
min: int = None,
|
|
max: int = None,
|
|
step: int = None,
|
|
control_after_generate: bool = None,
|
|
display_mode: NumberDisplay = None,
|
|
socketless: bool = None,
|
|
force_input: bool = None
|
|
)
|
|
|
|
# Float input
|
|
io.Float.Input(
|
|
id: str,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
default: float = None,
|
|
min: float = None,
|
|
max: float = None,
|
|
step: float = None,
|
|
round: float = None,
|
|
display_mode: NumberDisplay = None,
|
|
socketless: bool = None,
|
|
force_input: bool = None
|
|
)
|
|
|
|
# String input
|
|
io.String.Input(
|
|
id: str,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
multiline: bool = False,
|
|
placeholder: str = None,
|
|
default: str = None,
|
|
dynamic_prompts: bool = None,
|
|
socketless: bool = None,
|
|
force_input: bool = None
|
|
)
|
|
|
|
# Boolean input
|
|
io.Boolean.Input(
|
|
id: str,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
default: bool = None,
|
|
label_on: str = None,
|
|
label_off: str = None,
|
|
socketless: bool = None,
|
|
force_input: bool = None
|
|
)
|
|
|
|
# Combo (dropdown) input
|
|
io.Combo.Input(
|
|
id: str,
|
|
options: list[str] = None,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
default: str = None,
|
|
control_after_generate: bool = None,
|
|
upload: UploadType = None,
|
|
image_folder: FolderType = None,
|
|
remote: RemoteOptions = None,
|
|
socketless: bool = None
|
|
)
|
|
|
|
# Multi-select combo
|
|
io.MultiCombo.Input(
|
|
id: str,
|
|
options: list[str],
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
default: list[str] = None,
|
|
placeholder: str = None,
|
|
chip: bool = None,
|
|
control_after_generate: bool = None,
|
|
socketless: bool = None
|
|
)
|
|
# cusotm type
|
|
io.Custom(io_type="MY_TYPE").Input(
|
|
id: str,
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None,
|
|
placeholder: str = None,
|
|
)
|
|
```
|
|
|
|
### ComfyUI Types
|
|
|
|
```python
|
|
# Core types
|
|
io.Image.Input(id, ...) # Type: torch.Tensor [B,H,W,C]
|
|
io.Mask.Input(id, ...) # Type: torch.Tensor [H,W] or [B,H,W]
|
|
io.Latent.Input(id, ...) # Type: dict with 'samples' tensor
|
|
io.Conditioning.Input(id, ...) # Type: list[tuple[tensor, dict]]
|
|
io.Model.Input(id, ...) # Type: ModelPatcher
|
|
io.Clip.Input(id, ...) # Type: CLIP
|
|
io.Vae.Input(id, ...) # Type: VAE
|
|
io.ControlNet.Input(id, ...) # Type: ControlNet
|
|
|
|
# Sampling types
|
|
io.Sampler.Input(id, ...) # Type: Sampler
|
|
io.Sigmas.Input(id, ...) # Type: torch.Tensor
|
|
io.Noise.Input(id, ...) # Type: torch.Tensor
|
|
io.Guider.Input(id, ...) # Type: CFGGuider
|
|
|
|
# Additional types
|
|
io.ClipVision.Input(id, ...) # Type: ClipVisionModel
|
|
io.ClipVisionOutput.Input(id, ...) # Type: ClipVisionOutput
|
|
io.StyleModel.Input(id, ...) # Type: StyleModel
|
|
io.Gligen.Input(id, ...) # Type: ModelPatcher
|
|
io.UpscaleModel.Input(id, ...) # Type: ImageModelDescriptor
|
|
io.Audio.Input(id, ...) # Type: dict with 'waveform' and 'sample_rate'
|
|
io.Video.Input(id, ...) # Type: VideoInput
|
|
io.Webcam.Input(id, ...) # Type: str (filepath)
|
|
io.WanCameraEmbedding.Input(id, ...) # Type: torch.Tensor
|
|
io.LoraModel.Input(id, ...) # Type: dict[str, Tensor]
|
|
io.Hooks.Input(id, ...) # Type: HookGroup
|
|
io.HookKeyframes.Input(id, ...) # Type: HookKeyframeGroup
|
|
io.SVG.Input(id, ...) # Type: SVG (custom class)
|
|
io.Voxel.Input(id, ...) # Type: Voxel data (custom class)
|
|
io.Mesh.Input(id, ...) # Type: Mesh data (custom class)
|
|
```
|
|
|
|
### Advanced Inputs
|
|
|
|
```python
|
|
# Multi-type input (accepts multiple types)
|
|
io.MultiType.Input(
|
|
id: str | InputV3, # Can override from existing input
|
|
types: list[type[ComfyType]],
|
|
display_name: str = None,
|
|
optional: bool = False,
|
|
tooltip: str = None,
|
|
lazy: bool = None
|
|
)
|
|
|
|
# Dynamic growing input
|
|
io.AutogrowDynamic.Input(
|
|
id: str,
|
|
template_input: InputV3, # Template for each new input
|
|
min: int = 1, # Minimum inputs
|
|
max: int = None # Maximum inputs
|
|
)
|
|
|
|
# Custom type
|
|
@io.comfytype(io_type="MY_CUSTOM")
|
|
class MyCustom:
|
|
Type = MyDataClass
|
|
class Input(io.InputV3):
|
|
...
|
|
class Output(io.OutputV3):
|
|
...
|
|
```
|
|
|
|
## Output Types
|
|
|
|
```python
|
|
# Basic output
|
|
io.Image.Output(
|
|
id: str = None,
|
|
display_name: str = None,
|
|
tooltip: str = None,
|
|
is_output_list: bool = False # Output is list
|
|
)
|
|
|
|
# All ComfyUI types have corresponding outputs
|
|
io.Mask.Output(id, ...)
|
|
io.Latent.Output(id, ...)
|
|
io.Model.Output(id, ...)
|
|
io.Clip.Output(id, ...)
|
|
io.Vae.Output(id, ...)
|
|
io.Conditioning.Output(id, ...)
|
|
io.String.Output(id, ...)
|
|
io.Int.Output(id, ...)
|
|
io.Float.Output(id, ...)
|
|
io.Boolean.Output(id, ...)
|
|
# ... etc
|
|
```
|
|
|
|
## Hidden Inputs
|
|
|
|
```python
|
|
from comfy_api.latest import Hidden
|
|
|
|
# Available hidden inputs
|
|
Hidden.unique_id # Node's unique ID
|
|
Hidden.prompt # Complete prompt
|
|
Hidden.extra_pnginfo # PNG metadata dict
|
|
Hidden.dynprompt # Dynamic prompt object
|
|
Hidden.auth_token_comfy_org # ComfyOrg auth token
|
|
Hidden.api_key_comfy_org # ComfyOrg API key
|
|
|
|
# Usage in schema
|
|
hidden=[
|
|
Hidden.unique_id,
|
|
Hidden.prompt
|
|
]
|
|
|
|
# Access in execute
|
|
unique_id = cls.hidden.unique_id
|
|
prompt = cls.hidden.prompt
|
|
```
|
|
|
|
## State Management
|
|
|
|
```python
|
|
# NodeState interface
|
|
class NodeState:
|
|
def get_value(self, key: str) -> Any
|
|
def set_value(self, key: str, value: Any)
|
|
def pop(self, key: str) -> Any
|
|
def __contains__(self, key: str) -> bool
|
|
|
|
# Attribute access
|
|
cls.state.my_value = 42
|
|
value = cls.state.my_value
|
|
|
|
# Dictionary access
|
|
cls.state["key"] = "value"
|
|
value = cls.state["key"]
|
|
```
|
|
|
|
## Practical Input/Output Examples
|
|
|
|
### Enhanced Documentation with Tooltips and Display Names
|
|
|
|
```python
|
|
# String input with full documentation
|
|
io.String.Input(
|
|
"prompt",
|
|
display_name="Text Prompt",
|
|
multiline=True,
|
|
default="A beautiful landscape",
|
|
tooltip="Enter the text description for image generation",
|
|
placeholder="Type your prompt here..."
|
|
)
|
|
|
|
# Integer with constraints and UI hints
|
|
io.Int.Input(
|
|
"steps",
|
|
display_name="Sampling Steps",
|
|
default=20,
|
|
min=1,
|
|
max=150,
|
|
tooltip="Number of denoising steps. Higher values take longer but may produce better results",
|
|
display_mode=io.NumberDisplay.slider
|
|
)
|
|
|
|
# Combo with dynamic options
|
|
io.Combo.Input(
|
|
"checkpoint",
|
|
options=folder_paths.get_filename_list("checkpoints"),
|
|
display_name="Model Checkpoint",
|
|
tooltip="Select the AI model to use for generation"
|
|
)
|
|
|
|
# Output with documentation
|
|
io.Image.Output(
|
|
"generated_image",
|
|
display_name="Generated Image",
|
|
tooltip="The final generated image based on your prompt"
|
|
)
|
|
|
|
# Combo with dynamic options and file upload
|
|
io.Combo.Input(
|
|
"audio_file",
|
|
options=sorted(folder_paths.filter_files_content_types(os.listdir(folder_paths.get_input_directory()), ["audio", "video"])),
|
|
display_name="Audio File",
|
|
tooltip="Select an audio file or upload a new one",
|
|
upload=io.UploadType.audio
|
|
)
|
|
```
|
|
|
|
### Return Pattern with NodeOutput
|
|
|
|
```python
|
|
@classmethod
|
|
def execute(cls, text: str, count: int) -> io.NodeOutput:
|
|
# Single output
|
|
result = process_text(text, count)
|
|
return io.NodeOutput(result)
|
|
|
|
# Multiple outputs
|
|
image, mask = generate_image_and_mask(text)
|
|
return io.NodeOutput(image, mask)
|
|
|
|
# With UI preview
|
|
image = generate_image(text)
|
|
return io.NodeOutput(image, ui=ui.PreviewImage(image))
|
|
|
|
# With multiple UI elements
|
|
images = batch_generate(text, count)
|
|
previews = [ui.PreviewImage(img) for img in images]
|
|
return io.NodeOutput(images, ui={"images": previews})
|
|
```
|
|
|
|
## Resource Management
|
|
|
|
```python
|
|
# Load cached resources
|
|
from comfy_api.latest import resources
|
|
|
|
# Load torch file
|
|
model = cls.resources.get(
|
|
resources.TorchDictFolderFilename(
|
|
folder_name="checkpoints", # Folder category
|
|
file_name="model.safetensors"
|
|
)
|
|
)
|
|
|
|
# With default value
|
|
model = cls.resources.get(key, default=None)
|
|
|
|
# Custom resource types (future)
|
|
class MyResourceKey(ResourceKey):
|
|
Type = MyResourceType
|
|
def __init__(self, ...):
|
|
pass
|
|
```
|
|
|
|
## UI Output Classes
|
|
|
|
```python
|
|
from comfy_api.latest import ui
|
|
|
|
# Image preview
|
|
ui.PreviewImage(
|
|
image: torch.Tensor,
|
|
animated: bool = False
|
|
)
|
|
|
|
# Mask preview
|
|
ui.PreviewMask(
|
|
mask: torch.Tensor,
|
|
animated: bool = False
|
|
)
|
|
|
|
# Audio preview
|
|
ui.PreviewAudio(
|
|
values: list[SavedResult | dict]
|
|
)
|
|
|
|
# Text output
|
|
ui.PreviewText(
|
|
value: str
|
|
)
|
|
|
|
# 3D preview
|
|
ui.PreviewUI3D(
|
|
values: list[SavedResult | dict]
|
|
)
|
|
```
|
|
|
|
## Decorators and Helpers
|
|
|
|
```python
|
|
# Create custom ComfyType
|
|
@io.comfytype(io_type="CUSTOM_TYPE")
|
|
class CustomType:
|
|
Type = CustomClass
|
|
class Input(io.InputV3):
|
|
...
|
|
class Output(io.OutputV3):
|
|
...
|
|
|
|
# Custom serializer
|
|
class MySerializer(Serializer, io_type="MY_TYPE"):
|
|
@classmethod
|
|
def serialize(cls, obj: Any) -> str:
|
|
return json.dumps(obj)
|
|
|
|
@classmethod
|
|
def deserialize(cls, s: str) -> Any:
|
|
return json.loads(s)
|
|
```
|
|
|
|
## Async Support
|
|
|
|
```python
|
|
# Async execute
|
|
class AsyncNode(io.ComfyNode):
|
|
@classmethod
|
|
async def execute(cls, **kwargs):
|
|
result = await async_operation()
|
|
return io.NodeOutput(result)
|
|
|
|
# Async validation
|
|
@classmethod
|
|
async def VALIDATE_INPUTS(cls, **kwargs):
|
|
is_valid = await check_validity()
|
|
return True if is_valid else "Error message"
|
|
|
|
# Async lazy check
|
|
async def check_lazy_status(cls, **kwargs):
|
|
needed = await determine_needed_inputs()
|
|
return needed # List of input names
|
|
```
|
|
|
|
## Complete Example
|
|
|
|
```python
|
|
from comfy_api.latest import io, ui, resources, io.ComfyNode, io.ComfyNode
|
|
import torch
|
|
|
|
class AdvancedNodeV3(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.ComfyNode(
|
|
node_id="AdvancedNode",
|
|
display_name="Advanced Node",
|
|
category="examples/advanced",
|
|
description="Demonstrates v3 features",
|
|
inputs=[
|
|
# Basic inputs
|
|
io.Image.Input("image", tooltip="Input image"),
|
|
io.Model.Input("model", tooltip="Model to use"),
|
|
|
|
# Configured inputs
|
|
io.Float.Input("strength",
|
|
default=0.75,
|
|
min=0.0,
|
|
max=1.0,
|
|
step=0.05,
|
|
display_mode=io.NumberDisplay.slider
|
|
),
|
|
|
|
# Multi-type
|
|
io.MultiType.Input("flexible",
|
|
types=[io.Image, io.Mask, io.Latent],
|
|
optional=True
|
|
),
|
|
|
|
# Dynamic
|
|
io.AutoGrowDynamic.Input("extra_images",
|
|
template_input=io.Image.Input("img"),
|
|
min=0,
|
|
max=5
|
|
)
|
|
],
|
|
outputs=[
|
|
io.Image.Output("result", tooltip="Processed image"),
|
|
io.Latent.Output("latent", is_output_list=True)
|
|
],
|
|
hidden=[
|
|
io.Hidden.unique_id,
|
|
io.Hidden.prompt
|
|
],
|
|
is_output_node=True,
|
|
is_experimental=True
|
|
)
|
|
|
|
@classmethod
|
|
async def execute(cls, image, model, strength, flexible=None, **kwargs):
|
|
# Access state
|
|
if cls.state.last_model != model:
|
|
cls.state.cache = {}
|
|
cls.state.last_model = model
|
|
|
|
# Load resources
|
|
weights = cls.resources.get(
|
|
resources.TorchDictFolderFilename("loras", "style.safetensors"),
|
|
default=None
|
|
)
|
|
|
|
# Access hidden
|
|
node_id = cls.hidden.unique_id
|
|
|
|
# Process async
|
|
result = await process_with_model(image, model, strength)
|
|
|
|
# Handle dynamic inputs
|
|
extra_images = [v for k, v in kwargs.items() if k.startswith("extra_")]
|
|
|
|
# Return with UI
|
|
return io.NodeOutput(
|
|
result,
|
|
[latent],
|
|
ui=ui.PreviewImage(result)
|
|
)
|
|
|
|
@classmethod
|
|
async def fingerprint_inputs(cls, strength, **kwargs):
|
|
if strength < 0.1:
|
|
return "Strength too low for good results"
|
|
return True
|
|
```
|
|
|
|
## Type Reference
|
|
|
|
### Type Mappings
|
|
|
|
| v3 Type | Python Type | Shape/Format |
|
|
|---------|------------|--------------|
|
|
| `io.Image.Type` | `torch.Tensor` | `[B,H,W,C]` float32 0-1 |
|
|
| `io.Mask.Type` | `torch.Tensor` | `[H,W]` or `[B,H,W]` float32 |
|
|
| `io.Latent.Type` | `dict` | `{"samples": tensor, ...}` |
|
|
| `io.Conditioning.Type` | `list` | `[(tensor, dict), ...]` |
|
|
| `io.Audio.Type` | `dict` | `{"waveform": tensor, "sample_rate": int}` |
|
|
| `io.Int.Type` | `int` | Python integer |
|
|
| `io.Float.Type` | `float` | Python float |
|
|
| `io.String.Type` | `str` | Python string |
|
|
| `io.Boolean.Type` | `bool` | Python boolean |
|
|
|
|
### Enum Types
|
|
|
|
```python
|
|
# Number display modes
|
|
io.NumberDisplay.number # Standard input
|
|
io.NumberDisplay.slider # Slider widget
|
|
io.NumberDisplay.color # Color picker widget
|
|
|
|
# Folder types
|
|
io.FolderType.input # Input folder
|
|
io.FolderType.output # Output folder
|
|
io.FolderType.temp # Temp folder
|
|
|
|
# Upload types
|
|
io.UploadType.image
|
|
io.UploadType.audio
|
|
io.UploadType.video
|
|
io.UploadType.model
|
|
``` |