most stable build yet

few issues has to be cleared with the Dynamic sliders stack node, padding issues, persistence issue (lose dynamic functionality after ComfyUi restarts).
This commit is contained in:
FunnyFinger
2025-04-14 18:29:37 +03:00
committed by GitHub
parent b85340ddaa
commit f76961942b
5 changed files with 466 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
from .nodes.dynamic_sliders_stack import DynamicSlidersStack
from .nodes.stack_receiver import StackReceiver
NODE_CLASS_MAPPINGS = {
"DynamicSlidersStack": DynamicSlidersStack,
"StackReceiver": StackReceiver
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DynamicSlidersStack": "Dynamic Sliders Stack",
"StackReceiver": "Stack Receiver"
}
WEB_DIRECTORY = "./web/js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+1
View File
@@ -0,0 +1 @@
# Empty init file to make the directory a package
@@ -0,0 +1,61 @@
class DynamicSlidersStack:
@classmethod
def INPUT_TYPES(cls):
inputs = {
"required": {
"sliders_count": ("INT", {
"default": 5,
"min": 1,
"max": 10,
"step": 1,
"display": "number"
}),
"sliders_Max": ("FLOAT", {
"default": 1.0,
"min": 0.01,
"max": 2.0,
"step": 0.01,
"display": "number"
}),
},
"optional": {}
}
# Add 10 title/slider pairs (will be shown/hidden via JS)
for i in range(1, 11):
inputs["optional"][f"title_{i}"] = ("STRING", {"default": f"Slider {i}"})
inputs["optional"][f"slider_{i}"] = ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 2.0,
"step": 0.0001,
"display": "slider"
})
return inputs
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("sliders_values",)
FUNCTION = "process_sliders"
CATEGORY = "Dynamic_Slider_Pack"
def process_sliders(self, sliders_count, sliders_Max, **kwargs):
# Ensure minimum sliders_Max of 0.01
sliders_Max = max(0.01, sliders_Max)
# Extract slider values up to the count limit
slider_values = []
max_value = 0
for i in range(1, sliders_count + 1):
key = f"slider_{i}"
if key in kwargs:
value = kwargs[key]
slider_values.append(value)
max_value = max(max_value, value)
# Join values with commas - display with 2 decimal places
values_str = ", ".join([f"{val:.2f}" for val in slider_values])
return (values_str,)
@@ -0,0 +1,37 @@
class StackReceiver:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("STRING", {"forceInput": True}),
"slider_index": ("INT", {
"default": 1,
"min": 1,
"max": 10,
"step": 1,
"display": "number"
})
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
FUNCTION = "get_slider_value"
CATEGORY = "Dynamic_Slider_Pack"
def get_slider_value(self, input, slider_index):
# Parse the comma-separated values from input
try:
values = [float(val.strip()) for val in input.split(',')]
# Check if the index is valid
if 1 <= slider_index <= len(values):
return (values[slider_index - 1],)
else:
# Return default if index is out of range
return (0.0,)
except:
# Handle parsing errors
return (0.0,)
@@ -0,0 +1,351 @@
(function() {
// Wait for app to be defined
const checkExist = setInterval(function() {
if (typeof app !== 'undefined') {
clearInterval(checkExist);
// Log to console to verify the script is loaded
console.log("[Dynamic_Slider_Pack] ✅ JavaScript loaded successfully.");
// Register the extension
app.registerExtension({
name: "Dynamic_Slider_Pack",
nodeCreated(node) {
// Only apply to our specific node
if (node.constructor.type !== "DynamicSlidersStack") {
return;
}
console.log("[Dynamic_Slider_Pack] Node created, initializing");
// Constants for node sizing
const HEADER_HEIGHT = 30;
const CONTROLS_HEIGHT = 60;
const PAIR_HEIGHT = 60;
const FOOTER_PADDING = 4;
const DEFAULT_WIDTH = 340;
const MIN_WIDTH = 200;
const MAX_SLIDER_VALUE = 2.0; // Hard cap on max value
// Lock to prevent recursive updates
node._updatingSliders = false;
// Override the onResize method to handle width constraints properly
const origOnResize = node.onResize;
node.onResize = function(size) {
if (origOnResize) {
origOnResize.call(this, size);
}
// Allow width to be changed freely but maintain minimum
if (size[0] < MIN_WIDTH) {
size[0] = MIN_WIDTH;
}
// Calculate minimum height based on visible widgets
const countWidget = this.widgets.find(w => w.name === "sliders_count");
const count = countWidget ? countWidget.value : 5;
const minHeight = HEADER_HEIGHT + CONTROLS_HEIGHT + (count * PAIR_HEIGHT) + FOOTER_PADDING;
// Set to exact calculated height to prevent extra space
size[1] = minHeight;
this.size = size;
};
// Function to find the maximum slider value and index
function findMaxSliderValue() {
const countWidget = node.widgets.find(w => w.name === "sliders_count");
if (!countWidget) return { maxValue: 1.0, maxIndex: 0 };
const count = countWidget.value;
let maxValue = 0;
let maxIndex = -1;
for (let i = 1; i <= count; i++) {
const sliderWidget = node.widgets.find(w => w.name === `slider_${i}`);
if (sliderWidget && !sliderWidget.hidden) {
if (sliderWidget.value > maxValue) {
maxValue = sliderWidget.value;
maxIndex = i - 1; // Zero-based index
}
}
}
// If no max found, use defaults
if (maxIndex < 0) {
maxValue = 1.0;
maxIndex = 0;
}
return { maxValue, maxIndex };
}
// Update the MAX widget based on current slider values
function updateMaxFromSliders() {
if (node._updatingSliders) return;
const maxWidget = node.widgets.find(w => w.name === "sliders_Max");
if (!maxWidget) return;
const { maxValue } = findMaxSliderValue();
// Cap at the hard maximum of 2.0
const capped = Math.min(maxValue, MAX_SLIDER_VALUE);
// Only update if significantly different
if (Math.abs(maxWidget.value - capped) > 0.0001) {
console.log(`[Dynamic_Slider_Pack] Updating MAX to ${capped.toFixed(4)} from sliders`);
maxWidget.value = capped;
}
}
// Function to update widget visibility based on sliders_count
function updateWidgetVisibility() {
if (node._updatingSliders) return;
node._updatingSliders = true;
try {
// Find the control widgets
const countWidget = node.widgets.find(w => w.name === "sliders_count");
const maxWidget = node.widgets.find(w => w.name === "sliders_Max");
if (!countWidget || !maxWidget) {
console.error("[Dynamic_Slider_Pack] Could not find control widgets");
return;
}
const count = countWidget.value;
const maxVal = Math.max(0.01, maxWidget.value);
console.log(`[Dynamic_Slider_Pack] Updating visibility: showing ${count} sliders with max=${maxVal}`);
// Loop through all potential title/slider widgets
let visibleSliders = 0;
for (let i = 1; i <= 10; i++) {
const titleWidget = node.widgets.find(w => w.name === `title_${i}`);
const sliderWidget = node.widgets.find(w => w.name === `slider_${i}`);
if (titleWidget && sliderWidget) {
// Show only up to 'count' widgets
const shouldShow = i <= count;
titleWidget.hidden = !shouldShow;
sliderWidget.hidden = !shouldShow;
if (shouldShow) {
visibleSliders++;
// Update slider's max value
if (sliderWidget.options) {
sliderWidget.options.max = MAX_SLIDER_VALUE; // Always use the hard max
sliderWidget.options.step = 0.0001;
// Cap value at current Max if needed
if (sliderWidget.value > maxVal) {
sliderWidget.value = maxVal;
}
}
}
}
}
// Calculate the new height based on visible widgets
const newHeight = HEADER_HEIGHT + CONTROLS_HEIGHT + (visibleSliders * PAIR_HEIGHT) + FOOTER_PADDING;
// Update node size
const currentWidth = node.size[0] || DEFAULT_WIDTH;
node.size = [currentWidth, newHeight];
// Use the graph's setDirty for better rendering
if (node.graph) {
node.graph.setDirty(true, true);
} else {
node.setDirtyCanvas(true, true);
}
} finally {
node._updatingSliders = false;
}
}
// Set up callbacks for each slider to update MAX value
function setupSliderCallbacks() {
for (let i = 1; i <= 10; i++) {
const sliderWidget = node.widgets.find(w => w.name === `slider_${i}`);
if (sliderWidget) {
// Store original callback if any
const origCallback = sliderWidget.callback;
// Replace with our callback
sliderWidget.callback = function(v) {
if (origCallback) origCallback.call(this, v);
// Skip if we're currently updating sliders programmatically
if (node._updatingSliders) return;
// Update MAX widget if this slider is now the maximum
updateMaxFromSliders();
};
}
}
}
// Set up MAX widget callback to scale slider values
function setupMaxCallback() {
const maxWidget = node.widgets.find(w => w.name === "sliders_Max");
if (!maxWidget) return;
// Store original callback if any
const origCallback = maxWidget.callback;
// Replace with our callback to handle slider scaling
maxWidget.callback = function(newMax) {
if (origCallback) origCallback.call(this, newMax);
// Skip if we're in the middle of updating
if (node._updatingSliders) return;
node._updatingSliders = true;
try {
// Ensure minimum max value
newMax = Math.max(0.01, newMax);
console.log(`[Dynamic_Slider_Pack] MAX changed to ${newMax.toFixed(4)}`);
// Find the current maximum slider
const { maxValue, maxIndex } = findMaxSliderValue();
// Only proceed if we have a valid max index
if (maxIndex >= 0) {
const countWidget = node.widgets.find(w => w.name === "sliders_count");
const count = countWidget ? countWidget.value : 5;
// Calculate scaling ratio
const ratio = maxValue > 0 ? newMax / maxValue : 1;
console.log(`[Dynamic_Slider_Pack] Scaling ratio: ${ratio.toFixed(4)}`);
// Scale all slider values
for (let i = 1; i <= count; i++) {
const sliderWidget = node.widgets.find(w => w.name === `slider_${i}`);
if (sliderWidget && !sliderWidget.hidden) {
// Scale value
const scaledValue = sliderWidget.value * ratio;
sliderWidget.value = scaledValue;
}
}
}
updateWidgetVisibility();
} finally {
node._updatingSliders = false;
}
};
}
// Set up sliders_count callback
function setupCountCallback() {
const countWidget = node.widgets.find(w => w.name === "sliders_count");
if (!countWidget) return;
const origCallback = countWidget.callback;
countWidget.callback = function(v) {
if (origCallback) origCallback.call(this, v);
// Update slider visibility
updateWidgetVisibility();
// After changing count, update MAX value
updateMaxFromSliders();
};
}
// Add right-click menu options
function setupContextMenu() {
const getExtraMenuOptions = node.getExtraMenuOptions;
node.getExtraMenuOptions = function(_, options) {
if (getExtraMenuOptions) getExtraMenuOptions.apply(this, arguments);
options.push({
content: "Refresh Sliders",
callback: () => {
updateWidgetVisibility();
updateMaxFromSliders();
}
});
// Add option to evenly distribute slider values
options.push({
content: "Balance Slider Values",
callback: () => {
const countWidget = node.widgets.find(w => w.name === "sliders_count");
const maxWidget = node.widgets.find(w => w.name === "sliders_Max");
if (countWidget && maxWidget) {
const count = countWidget.value;
const maxVal = Math.max(0.01, maxWidget.value);
// Prevent callbacks during this operation
node._updatingSliders = true;
try {
// Calculate step size for even distribution
const step = count > 1 ? maxVal / (count - 1) : maxVal / 2;
// Distribute values evenly, making sure highest = MAX
for (let i = 0; i < count; i++) {
const sliderWidget = node.widgets.find(w => w.name === `slider_${i+1}`);
if (sliderWidget) {
const value = count > 1 ? step * i : maxVal / 2;
sliderWidget.value = value;
}
}
// Set the last slider to exactly MAX
if (count > 0) {
const lastSlider = node.widgets.find(w => w.name === `slider_${count}`);
if (lastSlider) {
lastSlider.value = maxVal;
}
}
} finally {
node._updatingSliders = false;
}
node.setDirtyCanvas(true, true);
}
}
});
// Add option to reset node size
options.push({
content: "Reset Node Size",
callback: () => {
node.size[0] = DEFAULT_WIDTH;
updateWidgetVisibility();
}
});
};
}
// Initialize the node
function initialize() {
// Set up callbacks in this specific order
setupSliderCallbacks();
setupMaxCallback();
setupCountCallback();
setupContextMenu();
updateWidgetVisibility();
// Add a short delay before updating Max based on sliders
setTimeout(() => {
updateMaxFromSliders();
console.log("[Dynamic_Slider_Pack] Initialization complete");
}, 200);
}
// Initialize with a delay to ensure widgets are created
setTimeout(initialize, 500);
}
});
}
}, 100); // Check every 100ms
})();