Files
AEmotionStudio-ComfyUI-Shad…/__init__.py
T
Æmotion StudioandClaude Opus 5 86aac9e27d feat: add Shader Noise Walk, a node that ramps one parameter across a batch
The pack's premise is exploring around a seed rather than hopping between seeds,
and a strength ramp shows it plainly. Producing one meant a queue run per value
with a widget nudged between each. This does the ramp in a single run.

ShaderNoiseWalk subclasses the Direct sampler and derives INPUT_TYPES from it, so
every sampler input is present and a future one cannot silently go missing --
test_the_walk_node_offers_everything_the_direct_node_does asserts that. It adds
walk_parameter (strength, phase_shift, noise_scale, warp_strength, octaves,
shape_mask_strength, color_intensity or seed), walk_start, walk_end and
walk_steps, and returns a batched LATENT for the comparer nodes or one VAE
decode.

Each point is a full run through pipelines.standard.run with the model resident
throughout, which is the economy of it: five H3 runs at 608x352/56 frames took
125s total, against several minutes for the 21 GB load alone.

Multi-stream latents batch stream by stream through ComfyUI's cat_nested, so a
three-step H3 walk returns [(3,24,...), (3,32,...)]. batch_index is dropped --
it picks a noise slot for a single run and means nothing across several; other
latent keys carry through.

Walking `seed` is offered deliberately. It is ordinary seed-hopping, the thing
the README contrasts this pack against, so having it in the same node makes the
comparison one click.

Measured on H3, distance from the unshaded baseline across five points:

    shader_strength 0.00 -> 0.30   0.0000 .. 0.1761, monotonic
    phase_shift     0.00 -> 1.50   0.1126 .. 0.1695, spread 3.1x tighter

which is the first evidence for the documented claim that phase shift reveals
"different facets of the same core elements": it holds roughly constant distance
while rearranging detail, where strength sweeps distance from zero. It is not
perfectly flat -- distance drifts mildly downward as phase rises.

10 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 02:25:25 -07:00

211 lines
6.9 KiB
Python

"""
ComfyUI-ShaderNoiseKSampler
A custom KSampler node that uses shader-based noise patterns
for creative image generation.
"""
# Import node classes from nodes package
from .nodes import (
ShaderNoiseKSampler,
DirectShaderNoiseKSampler,
AdvancedImageComparer,
VideoComparer,
)
from .shader_noise_walk import ShaderNoiseWalk
from .shader_to_tensor import ShaderToTensor
# Import shader registry
from .shaders.registry import (
ShaderRegistry,
register_shader,
get_shader,
list_shaders,
)
# Importing the generator modules runs their @shader_generator decorators,
# which register "domain_warp", "tensor_field", "curl_noise" and
# "temporal_coherent". Only the aliases below are registered here.
from .shaders.domain_warp import (
DomainWarpGenerator,
generate_domain_warp_tensor,
)
from .shaders.tensor_field import (
TensorFieldGenerator,
generate_tensor_field_tensor,
)
from .shaders.curl_noise import (
CurlNoiseGenerator,
generate_curl_noise_tensor,
)
from .shaders.temporal_coherent_noise import (
TemporalCoherentNoiseGenerator,
generate_temporal_coherent_noise_tensor,
)
register_shader("curl", CurlNoiseGenerator, {
"description": "Curl/fluid noise patterns (alias)",
"supports_temporal": True,
})
register_shader("temporal_coherent_noise", TemporalCoherentNoiseGenerator, {
"description": "Temporally coherent noise (alias)",
"supports_temporal": True,
})
# Register API routes for server-side parameter saving
try:
from server import PromptServer
from .api_routes import setup_routes
setup_routes(PromptServer.instance)
except ImportError:
# PromptServer not available (e.g., running tests without ComfyUI)
pass
except Exception as e:
print(f"[ShaderNoiseKSampler] Warning: Could not register API routes: {e}")
# Legacy SHADER_GENERATORS dict for backward compatibility
# Maps shader type names to generator functions
SHADER_GENERATORS = {
"domain_warp": generate_domain_warp_tensor,
"tensor_field": generate_tensor_field_tensor,
"curl": generate_curl_noise_tensor,
"curl_noise": generate_curl_noise_tensor,
"temporal_coherent": generate_temporal_coherent_noise_tensor,
"temporal_coherent_noise": generate_temporal_coherent_noise_tensor,
}
def _wrap_legacy_generator(legacy_func):
"""
Wrap a legacy generator function to accept the new 'params' keyword argument.
Legacy functions expect 'shader_params' as a dict, but the new convention uses
'params' which may be a ShaderParams instance. This wrapper translates between
the two conventions and converts ShaderParams to dict.
Args:
legacy_func: Legacy generator function expecting shader_params as dict
Returns:
Wrapped function accepting params (ShaderParams or dict)
"""
def wrapper(**kwargs):
# If 'params' is provided but not 'shader_params', translate it
if 'params' in kwargs and 'shader_params' not in kwargs:
params = kwargs.pop('params')
# Convert ShaderParams to dict if needed for legacy function
if hasattr(params, 'to_dict'):
shader_params = params.to_dict()
elif hasattr(params, '__iter__'):
shader_params = dict(params)
else:
shader_params = params
kwargs['shader_params'] = shader_params
return legacy_func(**kwargs)
return wrapper
def get_shader_generator(shader_type: str):
"""
Get the appropriate shader generator function based on shader type.
This function provides backward compatibility with the old API
while using the new registry system internally. The returned function
accepts both 'params' (new convention) and 'shader_params' (legacy convention).
Args:
shader_type: Name of the shader type
Returns:
Generator function for the shader type. Falls back to generate_noise_tensor
if not found (consistent with shader_noise_ksampler.py behavior).
"""
# Import here to avoid circular imports
from .shader_params_reader import generate_noise_tensor
# First try the legacy dict for backward compatibility
# Wrap legacy functions to accept 'params' keyword argument
if shader_type in SHADER_GENERATORS:
return _wrap_legacy_generator(SHADER_GENERATORS[shader_type])
# Fall back to registry - return the static generate method
generator_class = get_shader(shader_type)
if generator_class is not None:
# Return the static generate method directly (consistent with shader_noise_ksampler.py)
return generator_class.generate
# Fallback: wrap generate_noise_tensor to translate params -> shader_params
# This matches the behavior in shader_noise_ksampler.py
def fallback_wrapper(params, height, width, batch_size, device, seed, target_channels, **kwargs):
# Convert ShaderParams to dict if needed for legacy function
if hasattr(params, 'to_dict'):
shader_params = params.to_dict()
elif hasattr(params, '__iter__'):
shader_params = dict(params)
else:
shader_params = {}
return generate_noise_tensor(
shader_params=shader_params,
height=height,
width=width,
batch_size=batch_size,
device=device,
seed=seed,
target_channels=target_channels,
**kwargs
)
return fallback_wrapper
def register_shader_generator(shader_type: str, generator_function):
"""
Register a shader generator function.
This function provides backward compatibility with the old API.
Registers to both the legacy SHADER_GENERATORS dict and the new registry.
Args:
shader_type: Name of the shader type
generator_function: Generator function or class to register
"""
# Add to legacy dict for backward compatibility
SHADER_GENERATORS[shader_type] = generator_function
# Also register to the new registry so shader_noise_ksampler.py can find it
register_shader(shader_type, generator_function)
# Node class mappings
NODE_CLASS_MAPPINGS = {
"ShaderNoiseKSampler": ShaderNoiseKSampler,
"ShaderNoiseKSamplerDirect": DirectShaderNoiseKSampler,
"ShaderNoiseWalk": ShaderNoiseWalk,
"AdvancedImageComparer": AdvancedImageComparer,
"Video Comparer": VideoComparer,
}
# Display name mappings
NODE_DISPLAY_NAME_MAPPINGS = {
"ShaderNoiseKSampler": "Shader Noise KSampler",
"ShaderNoiseKSamplerDirect": "Shader Noise KSampler (Direct)",
"ShaderNoiseWalk": "Shader Noise Walk",
"AdvancedImageComparer": "Advanced Image Comparer",
"Video Comparer": "Video Comparer",
}
# Add web directory for UI components
WEB_DIRECTORY = "./web"
# List of exported elements
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"WEB_DIRECTORY",
"SHADER_GENERATORS",
"get_shader_generator",
"register_shader_generator",
"ShaderRegistry",
"register_shader",
"get_shader",
"list_shaders",
]