Perturbation Texture, Text Bridge, Switch (two way & three way), Integer Setting Advanced, HiDream Resolution Nodes Added
This commit is contained in:
+24
-1
@@ -8,6 +8,7 @@ from .boolean_reverse_node import BooleanReverse
|
||||
from .get_image_size_ratio_node import GetImageSizeRatio
|
||||
from .noise_plus_blend_node import NoisePlusBlend
|
||||
from .integer_settings_node import IntegerSettings
|
||||
from .integer_settings_advanced_node import IntegerSettingsAdvanced
|
||||
from .choose_upscale_model_node import ChooseUpscaleModel
|
||||
from .region_mask_generator_node import RegionMaskGenerator
|
||||
from .region_mask_validator_node import RegionMaskValidator
|
||||
@@ -16,6 +17,11 @@ from .region_mask_conditioning_node import RegionMaskConditioning
|
||||
from .flux_attention_control_node import FluxAttentionControl
|
||||
from .region_overlay_visualizer_node import RegionOverlayVisualizer
|
||||
from .flux_attention_cleanup_node import FluxAttentionCleanup
|
||||
from .hidream_resolution_node import HiDreamResolutionNode
|
||||
from .perturbation_texture_node import PerturbationTexture
|
||||
from .text_bridge_node import TextBridge
|
||||
from .two_way_switch_node import TwoWaySwitch
|
||||
from .three_way_switch_node import ThreeWaySwitch
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FluxResolutionNode": FluxResolutionNode,
|
||||
@@ -26,6 +32,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"GetImageSizeRatio": GetImageSizeRatio,
|
||||
"NoisePlusBlend": NoisePlusBlend,
|
||||
"IntegerSettings": IntegerSettings,
|
||||
"IntegerSettingsAdvanced": IntegerSettingsAdvanced,
|
||||
"ChooseUpscaleModel": ChooseUpscaleModel,
|
||||
"RegionMaskGenerator": RegionMaskGenerator,
|
||||
"RegionMaskValidator": RegionMaskValidator,
|
||||
@@ -34,6 +41,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FluxAttentionControl": FluxAttentionControl,
|
||||
"RegionOverlayVisualizer": RegionOverlayVisualizer,
|
||||
"FluxAttentionCleanup": FluxAttentionCleanup,
|
||||
"HiDreamResolutionNode": HiDreamResolutionNode,
|
||||
"PerturbationTexture": PerturbationTexture,
|
||||
"TextBridge": TextBridge,
|
||||
"TwoWaySwitch": TwoWaySwitch,
|
||||
"ThreeWaySwitch": ThreeWaySwitch,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -45,6 +57,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"GetImageSizeRatio": "Get Image Size Ratio",
|
||||
"NoisePlusBlend": "Noise Plus Blend",
|
||||
"IntegerSettings": "Integer Settings",
|
||||
"IntegerSettingsAdvanced": "Integer Settings Advanced",
|
||||
"ChooseUpscaleModel": "Choose Upscale Model",
|
||||
"RegionMaskGenerator": "Region Mask Generator",
|
||||
"RegionMaskValidator": "Region Mask Validator",
|
||||
@@ -53,4 +66,14 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FluxAttentionControl": "Flux Attention Control",
|
||||
"RegionOverlayVisualizer": "Region Overlay Visualizer",
|
||||
"FluxAttentionCleanup": "Flux Attention Cleanup",
|
||||
}
|
||||
"HiDreamResolutionNode": "HiDream Resolution",
|
||||
"PerturbationTexture": "Perturbation Texture",
|
||||
"TextBridge": "Text Bridge",
|
||||
"TwoWaySwitch": "Switch (Two Way)",
|
||||
"ThreeWaySwitch": "Switch (Three Way)",
|
||||
}
|
||||
|
||||
# Tell ComfyUI where to find JavaScript files
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,38 @@
|
||||
class IntegerSettingsAdvanced:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"setting_1": ("BOOLEAN", {"default": True, "label_on": "Enable", "label_off": "Disable"}),
|
||||
"setting_2": ("BOOLEAN", {"default": False, "label_on": "Enable", "label_off": "Disable"}),
|
||||
"setting_3": ("BOOLEAN", {"default": False, "label_on": "Enable", "label_off": "Disable"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("setting_value",)
|
||||
FUNCTION = "integer_settings_advanced"
|
||||
|
||||
CATEGORY = "ControlAltAI Nodes/Logic"
|
||||
|
||||
def integer_settings_advanced(self, setting_1, setting_2, setting_3):
|
||||
"""
|
||||
Returns integer based on which setting is enabled.
|
||||
Due to mutual exclusion (handled by JS), only one should be True.
|
||||
Priority order: setting_3 > setting_2 > setting_1
|
||||
"""
|
||||
if setting_3:
|
||||
return (3,)
|
||||
elif setting_2:
|
||||
return (2,)
|
||||
else:
|
||||
# Default to 1 (setting_1 or fallback)
|
||||
return (1,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"IntegerSettingsAdvanced": IntegerSettingsAdvanced,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IntegerSettingsAdvanced": "Integer Settings Advanced",
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import numpy as np
|
||||
from PIL import Image, ImageChops
|
||||
import torch
|
||||
|
||||
class PerturbationTexture:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"noise_scale": ("FLOAT", {"default": 0.5, "min": 0.00, "max": 1.00, "step": 0.01}),
|
||||
"texture_strength": ("INT", {"default": 50, "min": 0, "max": 100}),
|
||||
"texture_type": (["Film Grain", "Skin Pore", "Natural", "Fine Detail"], {"default": "Skin Pore"}),
|
||||
"frequency": ("FLOAT", {"default": 1.0, "min": 0.2, "max": 5.0, "step": 0.1}),
|
||||
"perturbation_factor": ("FLOAT", {"default": 0.30, "min": 0.01, "max": 0.5, "step": 0.01}),
|
||||
"use_mask": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"mask": ("MASK",),
|
||||
"seed": ("INT", {"default": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE")
|
||||
RETURN_NAMES = ("textured_image_output", "texture_layer")
|
||||
FUNCTION = "apply_perturbation_texture"
|
||||
|
||||
CATEGORY = "ControlAltAI Nodes/Image"
|
||||
|
||||
def tensor_to_pil(self, tensor_image):
|
||||
"""Converts tensor to a PIL Image"""
|
||||
tensor_image = tensor_image.squeeze(0) # Remove batch dimension if it exists
|
||||
pil_image = Image.fromarray((tensor_image.cpu().numpy() * 255).astype(np.uint8))
|
||||
return pil_image
|
||||
|
||||
def pil_to_tensor(self, pil_image):
|
||||
"""Converts a PIL image back to a tensor"""
|
||||
return torch.from_numpy(np.array(pil_image).astype(np.float32) / 255).unsqueeze(0)
|
||||
|
||||
def generate_adaptive_texture(self, base_image, noise_scale, texture_type, frequency, perturbation_factor, texture_strength, seed=None):
|
||||
"""Generate texture with adaptive color matching."""
|
||||
width, height = base_image.size
|
||||
|
||||
# Set seed for reproducibility if provided
|
||||
if seed is not None and seed >= 0:
|
||||
np.random.seed(seed)
|
||||
|
||||
# Convert base image to numpy array
|
||||
base_np = np.array(base_image).astype(np.float32) / 255.0
|
||||
|
||||
# Generate noise patterns based on texture type
|
||||
noise_patterns = self.generate_noise_patterns(width, height, noise_scale, texture_type, frequency)
|
||||
|
||||
# Convert noise to -1 to 1 range for proper mixing
|
||||
noise_normalized = (noise_patterns.astype(np.float32) - 128.0) / 128.0
|
||||
|
||||
# Apply perturbation with texture_strength controlling the final intensity
|
||||
effective_perturbation = perturbation_factor * (texture_strength / 100.0)
|
||||
|
||||
# Apply noise as color-matched variations around the base color
|
||||
result = base_np + (noise_normalized * effective_perturbation)
|
||||
|
||||
# Clamp to valid range
|
||||
result = np.clip(result, 0, 1)
|
||||
|
||||
# Create a more visible texture layer for preview/debugging
|
||||
texture_layer = base_np + (noise_normalized * perturbation_factor * 2.0)
|
||||
texture_layer = np.clip(texture_layer, 0, 1)
|
||||
|
||||
final_image = Image.fromarray((result * 255).astype(np.uint8))
|
||||
texture_image = Image.fromarray((texture_layer * 255).astype(np.uint8))
|
||||
|
||||
return final_image, texture_image
|
||||
|
||||
def generate_noise_patterns(self, width, height, noise_scale, texture_type, frequency):
|
||||
"""Generates noise patterns optimized for each texture type."""
|
||||
|
||||
# Safe resize function for noise scaling
|
||||
def safe_resize(arr, target_height, target_width):
|
||||
from PIL import Image
|
||||
if arr.ndim == 2:
|
||||
img = Image.fromarray((arr * 255 / arr.max()).astype(np.uint8))
|
||||
else:
|
||||
img = Image.fromarray(arr.astype(np.uint8))
|
||||
img = img.resize((target_width, target_height), Image.LANCZOS)
|
||||
return np.array(img).astype(np.float32) / 255.0 * arr.max()
|
||||
|
||||
if texture_type == "Film Grain":
|
||||
# Film grain - larger, more irregular pattern with RGB variation
|
||||
base_noise_r = np.random.normal(128, 64 * noise_scale, (height, width))
|
||||
base_noise_g = np.random.normal(128, 64 * noise_scale, (height, width))
|
||||
base_noise_b = np.random.normal(128, 64 * noise_scale, (height, width))
|
||||
|
||||
# Add larger scale variation for film-like clustering
|
||||
large_scale_h = max(4, int(height/(4*frequency)))
|
||||
large_scale_w = max(4, int(width/(4*frequency)))
|
||||
large_scale_r = np.random.normal(0, 30 * noise_scale, (large_scale_h, large_scale_w))
|
||||
large_scale_g = np.random.normal(0, 30 * noise_scale, (large_scale_h, large_scale_w))
|
||||
large_scale_b = np.random.normal(0, 30 * noise_scale, (large_scale_h, large_scale_w))
|
||||
|
||||
large_scale_r = safe_resize(large_scale_r, height, width)
|
||||
large_scale_g = safe_resize(large_scale_g, height, width)
|
||||
large_scale_b = safe_resize(large_scale_b, height, width)
|
||||
|
||||
combined_r = np.clip(base_noise_r * 0.7 + large_scale_r * 0.3, 0, 255)
|
||||
combined_g = np.clip(base_noise_g * 0.7 + large_scale_g * 0.3, 0, 255)
|
||||
combined_b = np.clip(base_noise_b * 0.7 + large_scale_b * 0.3, 0, 255)
|
||||
|
||||
elif texture_type == "Skin Pore":
|
||||
# Fine, subtle texture optimized for skin with reduced intensity
|
||||
base_scale = noise_scale * 0.6 # More subtle for natural skin texture
|
||||
|
||||
# Create subtle RGB variations for realistic skin texture
|
||||
base_noise_r = np.random.normal(128, 32 * base_scale, (height, width))
|
||||
base_noise_g = np.random.normal(128, 28 * base_scale, (height, width))
|
||||
base_noise_b = np.random.normal(128, 24 * base_scale, (height, width))
|
||||
|
||||
# Fine pore-like details at higher frequency
|
||||
fine_h = max(4, int(height*frequency*1.5))
|
||||
fine_w = max(4, int(width*frequency*1.5))
|
||||
fine_noise_r = np.random.normal(0, 20 * base_scale, (fine_h, fine_w))
|
||||
fine_noise_g = np.random.normal(0, 18 * base_scale, (fine_h, fine_w))
|
||||
fine_noise_b = np.random.normal(0, 16 * base_scale, (fine_h, fine_w))
|
||||
|
||||
fine_noise_r = safe_resize(fine_noise_r, height, width)
|
||||
fine_noise_g = safe_resize(fine_noise_g, height, width)
|
||||
fine_noise_b = safe_resize(fine_noise_b, height, width)
|
||||
|
||||
combined_r = np.clip(base_noise_r + fine_noise_r * 0.8, 0, 255)
|
||||
combined_g = np.clip(base_noise_g + fine_noise_g * 0.8, 0, 255)
|
||||
combined_b = np.clip(base_noise_b + fine_noise_b * 0.8, 0, 255)
|
||||
|
||||
elif texture_type == "Natural":
|
||||
# Multi-layered natural texture with organic frequency distribution
|
||||
base_noise_r = np.random.normal(128, 48 * noise_scale, (height, width))
|
||||
base_noise_g = np.random.normal(128, 44 * noise_scale, (height, width))
|
||||
base_noise_b = np.random.normal(128, 40 * noise_scale, (height, width))
|
||||
|
||||
# Multiple frequency layers for natural complexity
|
||||
frequencies = [frequency*2, frequency, frequency/3]
|
||||
weights = [0.5, 0.3, 0.2]
|
||||
|
||||
combined_r = base_noise_r.copy()
|
||||
combined_g = base_noise_g.copy()
|
||||
combined_b = base_noise_b.copy()
|
||||
|
||||
for freq, weight in zip(frequencies, weights):
|
||||
f_h = max(4, int(height*freq))
|
||||
f_w = max(4, int(width*freq))
|
||||
|
||||
layer_r = np.random.normal(0, 30 * noise_scale * weight, (f_h, f_w))
|
||||
layer_g = np.random.normal(0, 28 * noise_scale * weight, (f_h, f_w))
|
||||
layer_b = np.random.normal(0, 26 * noise_scale * weight, (f_h, f_w))
|
||||
|
||||
layer_r = safe_resize(layer_r, height, width)
|
||||
layer_g = safe_resize(layer_g, height, width)
|
||||
layer_b = safe_resize(layer_b, height, width)
|
||||
|
||||
combined_r += layer_r * weight
|
||||
combined_g += layer_g * weight
|
||||
combined_b += layer_b * weight
|
||||
|
||||
combined_r = np.clip(combined_r, 0, 255)
|
||||
combined_g = np.clip(combined_g, 0, 255)
|
||||
combined_b = np.clip(combined_b, 0, 255)
|
||||
|
||||
else: # Fine Detail
|
||||
# High-frequency detailed texture for micro-details
|
||||
high_freq = frequency * 2.5
|
||||
|
||||
base_noise_r = np.random.normal(128, 40 * noise_scale, (height, width))
|
||||
base_noise_g = np.random.normal(128, 38 * noise_scale, (height, width))
|
||||
base_noise_b = np.random.normal(128, 36 * noise_scale, (height, width))
|
||||
|
||||
# High-frequency fine details
|
||||
fine_h = max(4, int(height*high_freq))
|
||||
fine_w = max(4, int(width*high_freq))
|
||||
fine_detail_r = np.random.normal(0, 25 * noise_scale, (fine_h, fine_w))
|
||||
fine_detail_g = np.random.normal(0, 23 * noise_scale, (fine_h, fine_w))
|
||||
fine_detail_b = np.random.normal(0, 21 * noise_scale, (fine_h, fine_w))
|
||||
|
||||
fine_detail_r = safe_resize(fine_detail_r, height, width)
|
||||
fine_detail_g = safe_resize(fine_detail_g, height, width)
|
||||
fine_detail_b = safe_resize(fine_detail_b, height, width)
|
||||
|
||||
combined_r = np.clip(base_noise_r + fine_detail_r * 0.7, 0, 255)
|
||||
combined_g = np.clip(base_noise_g + fine_detail_g * 0.7, 0, 255)
|
||||
combined_b = np.clip(base_noise_b + fine_detail_b * 0.7, 0, 255)
|
||||
|
||||
# Stack RGB channels into final noise pattern
|
||||
return np.stack([combined_r, combined_g, combined_b], axis=2)
|
||||
|
||||
def apply_perturbation_texture(self, image, noise_scale=0.5, texture_strength=50, texture_type="Skin Pore",
|
||||
frequency=1.0, perturbation_factor=0.15, use_mask=False, mask=None, seed=-1):
|
||||
"""Main function to apply adaptive color-matched texture."""
|
||||
# Convert tensor image to PIL
|
||||
base_image = self.tensor_to_pil(image)
|
||||
|
||||
# Use provided seed or generate random if -1
|
||||
seed_value = seed if seed >= 0 else None
|
||||
|
||||
# Generate adaptive texture
|
||||
textured_image, texture_layer = self.generate_adaptive_texture(
|
||||
base_image, noise_scale, texture_type, frequency,
|
||||
perturbation_factor, texture_strength, seed_value
|
||||
)
|
||||
|
||||
# Apply mask if specified
|
||||
if use_mask and mask is not None:
|
||||
mask_pil = self.tensor_to_pil(mask).convert('L')
|
||||
mask_resized = mask_pil.resize(base_image.size)
|
||||
# Invert mask so white areas get texture, black areas are protected
|
||||
inverted_mask = ImageChops.invert(mask_resized)
|
||||
# Composite: base where mask is black, textured where mask is white
|
||||
textured_image = Image.composite(base_image, textured_image, inverted_mask)
|
||||
|
||||
# Convert results back to tensors
|
||||
texture_tensor = self.pil_to_tensor(texture_layer)
|
||||
textured_tensor = self.pil_to_tensor(textured_image)
|
||||
|
||||
return textured_tensor, texture_tensor
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PerturbationTexture": PerturbationTexture,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PerturbationTexture": "Perturbation Texture",
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
class TextBridge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text_input": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"passthrough_text": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text_output",)
|
||||
FUNCTION = "bridge_text"
|
||||
|
||||
CATEGORY = "ControlAltAI Nodes/Utility"
|
||||
|
||||
def bridge_text(self, text_input="", passthrough_text=""):
|
||||
"""
|
||||
Bridge function that allows editing of input text and passes it through as output.
|
||||
If passthrough_text is connected, it uses that as the base text.
|
||||
The text_input field allows manual editing/override.
|
||||
"""
|
||||
# If passthrough_text is provided and text_input is empty or default, use passthrough
|
||||
if passthrough_text and (not text_input or text_input == ""):
|
||||
output_text = passthrough_text
|
||||
else:
|
||||
# Use the manually entered/edited text
|
||||
output_text = text_input
|
||||
|
||||
return (output_text,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"TextBridge": TextBridge,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TextBridge": "Text Bridge",
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
class AnyType(str):
|
||||
"""A special string subclass that equals any other type for ComfyUI type checking."""
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
# Create an instance to use as the any type
|
||||
any_type = AnyType("*")
|
||||
|
||||
class ThreeWaySwitch:
|
||||
"""Three-way switch that selects between three inputs based on selection setting."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"selection_setting": ("INT", {"default": 1, "min": 1, "max": 3}),
|
||||
},
|
||||
"optional": {
|
||||
"input_1": (any_type,),
|
||||
"input_2": (any_type,),
|
||||
"input_3": (any_type,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("output",)
|
||||
FUNCTION = "switch_inputs"
|
||||
|
||||
CATEGORY = "ControlAltAI Nodes/Logic"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, **kwargs):
|
||||
"""Allow any input types."""
|
||||
return True
|
||||
|
||||
def switch_inputs(self, selection_setting=1, input_1=None, input_2=None, input_3=None):
|
||||
"""
|
||||
Three-way switch that selects between three inputs based on the selection_setting.
|
||||
Compatible with IntegerSettingsAdvanced node:
|
||||
- selection_setting = 1: selects input_1
|
||||
- selection_setting = 2: selects input_2
|
||||
- selection_setting = 3: selects input_3
|
||||
"""
|
||||
if selection_setting == 2:
|
||||
# Second option - select input_2, fallback to input_1, then input_3
|
||||
selected_output = input_2 if input_2 is not None else (input_1 if input_1 is not None else input_3)
|
||||
elif selection_setting == 3:
|
||||
# Third option - select input_3, fallback to input_1, then input_2
|
||||
selected_output = input_3 if input_3 is not None else (input_1 if input_1 is not None else input_2)
|
||||
else:
|
||||
# Default/First option (1) - select input_1, fallback to input_2, then input_3
|
||||
selected_output = input_1 if input_1 is not None else (input_2 if input_2 is not None else input_3)
|
||||
|
||||
return (selected_output,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ThreeWaySwitch": ThreeWaySwitch,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ThreeWaySwitch": "Switch (Three Way)",
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
class AnyType(str):
|
||||
"""A special string subclass that equals any other type for ComfyUI type checking."""
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
# Create an instance to use as the any type
|
||||
any_type = AnyType("*")
|
||||
|
||||
class TwoWaySwitch:
|
||||
"""Two-way switch that selects between two inputs based on selection setting."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"selection_setting": ("INT", {"default": 1, "min": 1, "max": 2}),
|
||||
},
|
||||
"optional": {
|
||||
"input_1": (any_type,),
|
||||
"input_2": (any_type,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("output",)
|
||||
FUNCTION = "switch_inputs"
|
||||
|
||||
CATEGORY = "ControlAltAI Nodes/Logic"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, **kwargs):
|
||||
"""Allow any input types."""
|
||||
return True
|
||||
|
||||
def switch_inputs(self, selection_setting=1, input_1=None, input_2=None):
|
||||
"""
|
||||
Two-way switch that selects between two inputs based on the selection_setting.
|
||||
Compatible with IntegerSettings node:
|
||||
- selection_setting = 1 (Disable): selects input_1
|
||||
- selection_setting = 2 (Enable): selects input_2
|
||||
"""
|
||||
if selection_setting == 2:
|
||||
# Enable state - select second input
|
||||
selected_output = input_2 if input_2 is not None else input_1
|
||||
else:
|
||||
# Disable state (1) or any other value - select first input
|
||||
selected_output = input_1 if input_1 is not None else input_2
|
||||
|
||||
return (selected_output,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"TwoWaySwitch": TwoWaySwitch,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TwoWaySwitch": "Switch (Two Way)",
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
// Register the extension for IntegerSettingsAdvanced node
|
||||
app.registerExtension({
|
||||
name: "ControlAltAI.IntegerSettingsAdvanced",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "IntegerSettingsAdvanced") {
|
||||
console.log("Registering IntegerSettingsAdvanced mutual exclusion behavior");
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// Store reference to the node
|
||||
const node = this;
|
||||
|
||||
// Function to enforce mutual exclusion
|
||||
function enforceMutualExclusion(activeWidget) {
|
||||
// Get all boolean widgets
|
||||
const booleanWidgets = node.widgets.filter(w =>
|
||||
w.type === "toggle" &&
|
||||
(w.name === "setting_1" || w.name === "setting_2" || w.name === "setting_3")
|
||||
);
|
||||
|
||||
// If a widget is being set to true, set others to false
|
||||
if (activeWidget.value === true) {
|
||||
booleanWidgets.forEach(widget => {
|
||||
if (widget !== activeWidget) {
|
||||
widget.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Always ensure at least one is true (always one behavior)
|
||||
const anyEnabled = booleanWidgets.some(w => w.value === true);
|
||||
if (!anyEnabled) {
|
||||
// If none are enabled, enable setting_1 as default
|
||||
const setting1Widget = booleanWidgets.find(w => w.name === "setting_1");
|
||||
if (setting1Widget) {
|
||||
setting1Widget.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger canvas redraw
|
||||
if (app.graph) {
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Hook into widget callbacks after node is fully created
|
||||
setTimeout(() => {
|
||||
node.widgets.forEach(widget => {
|
||||
if (widget.type === "toggle" &&
|
||||
(widget.name === "setting_1" || widget.name === "setting_2" || widget.name === "setting_3")) {
|
||||
|
||||
// Store original callback
|
||||
const originalCallback = widget.callback;
|
||||
|
||||
// Override with mutual exclusion logic
|
||||
widget.callback = function(value) {
|
||||
// Call original callback first
|
||||
if (originalCallback) {
|
||||
originalCallback.call(this, value);
|
||||
}
|
||||
|
||||
// Apply mutual exclusion
|
||||
enforceMutualExclusion(this);
|
||||
};
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user