- Update pyproject.toml to use black's default line-length of 88 - This matches what the CI workflow expects (black --check without args) - Reformat all Python files to comply with the new line length - This will prevent CI failures due to formatting discrepancies
292 lines
9.3 KiB
Python
292 lines
9.3 KiB
Python
"""Sampler Combo node for ComfyUI."""
|
|
|
|
from typing import Tuple
|
|
from ...base.base_node import ComfyAssetsBaseNode
|
|
from .logic import (
|
|
get_sampler_combo,
|
|
validate_sampler_settings,
|
|
get_compatible_scheduler_suggestions,
|
|
get_recommended_steps_range,
|
|
get_recommended_cfg_range,
|
|
SAMPLERS,
|
|
SCHEDULERS,
|
|
)
|
|
|
|
|
|
class SamplerComboNode(ComfyAssetsBaseNode):
|
|
"""
|
|
Sampler Combo node for selecting sampling configuration.
|
|
|
|
Provides a unified interface for selecting sampler, scheduler, steps,
|
|
and CFG settings in a single node, reducing workflow complexity and
|
|
ensuring compatible parameter combinations.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
"""Define the input types for the ComfyUI node."""
|
|
return {
|
|
"required": {
|
|
"sampler_name": (
|
|
SAMPLERS,
|
|
{
|
|
"default": "euler",
|
|
"tooltip": "Sampling algorithm",
|
|
},
|
|
),
|
|
"scheduler": (
|
|
SCHEDULERS,
|
|
{
|
|
"default": "normal",
|
|
"tooltip": "Step distribution schedule",
|
|
},
|
|
),
|
|
"steps": (
|
|
"INT",
|
|
{
|
|
"default": 20,
|
|
"min": 1,
|
|
"max": 100,
|
|
"step": 1,
|
|
"tooltip": "Sampling steps (1-100)",
|
|
},
|
|
),
|
|
"cfg": (
|
|
"FLOAT",
|
|
{
|
|
"default": 7.0,
|
|
"min": 0.0,
|
|
"max": 20.0,
|
|
"step": 0.5,
|
|
"display": "slider",
|
|
"tooltip": "CFG scale (0-20)",
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SAMPLER", SCHEDULERS, "INT", "FLOAT")
|
|
RETURN_NAMES = ("sampler_name", "scheduler", "steps", "cfg")
|
|
FUNCTION = "get_sampler_combo"
|
|
CATEGORY = "ComfyAssets"
|
|
|
|
def get_sampler_combo(
|
|
self, sampler_name: str, scheduler: str, steps: int, cfg: float
|
|
) -> Tuple[object, str, int, float]:
|
|
"""
|
|
Get sampler combo configuration.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
scheduler: The scheduler algorithm name
|
|
steps: Number of sampling steps
|
|
cfg: CFG scale value
|
|
|
|
Returns:
|
|
Tuple of (sampler_object, scheduler, steps, cfg)
|
|
"""
|
|
try:
|
|
# Validate inputs
|
|
if not validate_sampler_settings(sampler_name, scheduler, steps, cfg):
|
|
# Log the validation error but don't raise
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(
|
|
f"{self.__class__.__name__}: Invalid sampler settings: "
|
|
f"sampler={sampler_name}, scheduler={scheduler}, "
|
|
f"steps={steps}, cfg={cfg}. "
|
|
f"Using safe defaults: euler, normal, 20 steps, CFG 7.0"
|
|
)
|
|
try:
|
|
import comfy.samplers
|
|
|
|
sampler = comfy.samplers.sampler_object("euler")
|
|
except ImportError:
|
|
# Return mock object for testing
|
|
sampler = "euler"
|
|
return (sampler, "normal", 20, 7.0)
|
|
|
|
# Process and return the combo
|
|
result = get_sampler_combo(sampler_name, scheduler, steps, cfg)
|
|
|
|
# Create the sampler object
|
|
try:
|
|
import comfy.samplers
|
|
|
|
sampler = comfy.samplers.sampler_object(result[0])
|
|
except ImportError:
|
|
# Return sampler name for testing
|
|
sampler = result[0]
|
|
|
|
self.log_info(
|
|
f"Configured sampler combo: {result[0]}, {result[1]}, "
|
|
f"{result[2]} steps, CFG {result[3]}"
|
|
)
|
|
|
|
return (sampler, result[1], result[2], result[3])
|
|
|
|
except Exception as e:
|
|
# Handle any unexpected errors gracefully
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.error(
|
|
f"{self.__class__.__name__}: Error processing sampler combo: {str(e)}. "
|
|
f"Using safe defaults: euler, normal, 20 steps, CFG 7.0"
|
|
)
|
|
try:
|
|
import comfy.samplers
|
|
|
|
sampler = comfy.samplers.sampler_object("euler")
|
|
except ImportError:
|
|
# Return mock object for testing
|
|
sampler = "euler"
|
|
return (sampler, "normal", 20, 7.0)
|
|
|
|
def validate_inputs(
|
|
self, sampler_name: str, scheduler: str, steps: int, cfg: float
|
|
) -> None:
|
|
"""
|
|
Validate sampler combo inputs.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
scheduler: The scheduler algorithm name
|
|
steps: Number of sampling steps
|
|
cfg: CFG scale value
|
|
|
|
Raises:
|
|
ValueError: If validation fails
|
|
"""
|
|
if not validate_sampler_settings(sampler_name, scheduler, steps, cfg):
|
|
self.handle_error(
|
|
f"Invalid sampler settings: sampler={sampler_name}, "
|
|
f"scheduler={scheduler}, steps={steps}, cfg={cfg}"
|
|
)
|
|
|
|
def get_scheduler_suggestions(self, sampler_name: str) -> list:
|
|
"""
|
|
Get scheduler suggestions compatible with the selected sampler.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
|
|
Returns:
|
|
List of recommended scheduler names
|
|
"""
|
|
return get_compatible_scheduler_suggestions(sampler_name)
|
|
|
|
def get_steps_recommendation(self, sampler_name: str) -> dict:
|
|
"""
|
|
Get steps recommendation for the selected sampler.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
|
|
Returns:
|
|
Dictionary with min, max, and default steps
|
|
"""
|
|
min_steps, max_steps, default_steps = get_recommended_steps_range(sampler_name)
|
|
return {
|
|
"min": min_steps,
|
|
"max": max_steps,
|
|
"default": default_steps,
|
|
"recommendation": f"Range: {min_steps}-{max_steps} steps",
|
|
}
|
|
|
|
def get_cfg_recommendation(self, sampler_name: str) -> dict:
|
|
"""
|
|
Get CFG recommendation for the selected sampler.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
|
|
Returns:
|
|
Dictionary with min, max, and default CFG values
|
|
"""
|
|
min_cfg, max_cfg, default_cfg = get_recommended_cfg_range(sampler_name)
|
|
return {
|
|
"min": min_cfg,
|
|
"max": max_cfg,
|
|
"default": default_cfg,
|
|
"recommendation": f"Recommended range: {min_cfg}-{max_cfg} CFG",
|
|
}
|
|
|
|
def get_combo_analysis(
|
|
self, sampler_name: str, scheduler: str, steps: int, cfg: float
|
|
) -> dict:
|
|
"""
|
|
Analyze the sampler combo configuration and provide recommendations.
|
|
|
|
Args:
|
|
sampler_name: The sampler algorithm name
|
|
scheduler: The scheduler algorithm name
|
|
steps: Number of sampling steps
|
|
cfg: CFG scale value
|
|
|
|
Returns:
|
|
Dictionary containing analysis and recommendations
|
|
"""
|
|
analysis = {
|
|
"sampler": sampler_name,
|
|
"scheduler": scheduler,
|
|
"steps": steps,
|
|
"cfg": cfg,
|
|
"valid": validate_sampler_settings(sampler_name, scheduler, steps, cfg),
|
|
"scheduler_suggestions": self.get_scheduler_suggestions(sampler_name),
|
|
"steps_rec": self.get_steps_recommendation(sampler_name),
|
|
"cfg_rec": self.get_cfg_recommendation(sampler_name),
|
|
}
|
|
|
|
# Add compatibility assessment
|
|
suggested_schedulers = self.get_scheduler_suggestions(sampler_name)
|
|
analysis["scheduler_compatible"] = scheduler in suggested_schedulers
|
|
|
|
# Add performance assessment
|
|
steps_rec = self.get_steps_recommendation(sampler_name)
|
|
analysis["steps_optimal"] = steps_rec["min"] <= steps <= steps_rec["max"]
|
|
|
|
cfg_rec = self.get_cfg_recommendation(sampler_name)
|
|
analysis["cfg_optimal"] = cfg_rec["min"] <= cfg <= cfg_rec["max"]
|
|
|
|
return analysis
|
|
|
|
@classmethod
|
|
def get_available_samplers(cls) -> list:
|
|
"""
|
|
Get list of available samplers.
|
|
|
|
Returns:
|
|
List of sampler names
|
|
"""
|
|
return list(SAMPLERS)
|
|
|
|
@classmethod
|
|
def get_available_schedulers(cls) -> list:
|
|
"""
|
|
Get list of available schedulers.
|
|
|
|
Returns:
|
|
List of scheduler names
|
|
"""
|
|
return list(SCHEDULERS)
|
|
|
|
def __str__(self) -> str:
|
|
"""String representation of the node."""
|
|
return (
|
|
f"SamplerComboNode(samplers={len(SAMPLERS)}, "
|
|
f"schedulers={len(SCHEDULERS)})"
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
"""Detailed string representation of the node."""
|
|
return (
|
|
f"SamplerComboNode("
|
|
f"samplers={len(SAMPLERS)}, "
|
|
f"schedulers={len(SCHEDULERS)}, "
|
|
f"category='{self.CATEGORY}', "
|
|
f"function='{self.FUNCTION}'"
|
|
f")"
|
|
)
|