Add files via upload

few issues still persists for the dynamic sliders stack node, the bottom padding issue when adding/removing sliders, and the persistence issue where the dynamic functionality is lost after ComfyUi restarts.
This commit is contained in:
FunnyFinger
2025-04-14 18:33:01 +03:00
committed by GitHub
parent b0d310fa49
commit 119d6f60c7
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
+61
View File
@@ -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,)
+37
View File
@@ -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,)
+351
View File
@@ -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
})();