From 058cc76af3f4f086ab59ce3b604ef5cf33ddc9dc Mon Sep 17 00:00:00 2001 From: RyanOnTheInside <7623207+ryanontheinside@users.noreply.github.com> Date: Sun, 22 Dec 2024 09:55:40 -0500 Subject: [PATCH] basic min/max validation for sequence controls --- web/js/widget_validation.js | 209 +++++++++++++++++++++++++++--------- 1 file changed, 160 insertions(+), 49 deletions(-) diff --git a/web/js/widget_validation.js b/web/js/widget_validation.js index c5ebfb4..d66d22a 100644 --- a/web/js/widget_validation.js +++ b/web/js/widget_validation.js @@ -5,8 +5,8 @@ import { app } from "../../../scripts/app.js"; app.registerExtension({ name: "RealTime.WidgetValidation", async beforeRegisterNodeDef(nodeType, nodeData, app) { - // Skip if not a real-time control node - if (!nodeData.category?.startsWith("real-time/control/value")) { + // Update category check to include sequence nodes + if (!nodeData.category?.startsWith("real-time/control/")) { return; } @@ -20,11 +20,33 @@ app.registerExtension({ nodeType.prototype.onNodeCreated = function() { const result = onNodeCreated?.apply(this, arguments); - // Use setTimeout to check connections after graph is loaded - setTimeout(() => { - // Check existing connections on load + // Wrap the values widget callback to ensure validation + const valuesWidget = this.widgets.find(w => w.name === "values"); + if (valuesWidget) { + const originalCallback = valuesWidget.callback; + valuesWidget.callback = (value) => { + console.log("Values widget direct callback:", { + before: value, + hasConstraints: !!this.targetConstraints + }); + + if (this.targetConstraints) { + this.validateSequenceValues(valuesWidget); + value = valuesWidget.value; // Use validated value + } + + console.log("After validation:", { + after: value + }); + + return originalCallback?.call(valuesWidget, value); + }; + } + + // Function to check connections + const checkConnections = () => { const outputLinks = this.outputs[0]?.links || []; - console.log("Checking existing connections on load (delayed):", outputLinks); + console.log("Checking existing connections:", outputLinks); for (const linkId of outputLinks) { const link = app.graph.links[linkId]; @@ -37,18 +59,36 @@ app.registerExtension({ const inputName = targetNode.inputs[targetSlot]?.name; const targetWidget = targetNode.widgets.find(w => w.name === inputName); - if (targetWidget?.options) { + if (targetWidget?.options || (targetWidget?.type === "converted-widget" && targetWidget.options)) { console.log("Found existing connection to widget:", targetWidget); this.targetConstraints = { min: targetWidget.options.min, max: targetWidget.options.max, step: targetWidget.options.step }; - this.updateWidgetConstraints(); + + // For sequence nodes, validate immediately + if (nodeData.category?.includes("/sequence")) { + const valuesWidget = this.widgets.find(w => w.name === "values"); + if (valuesWidget) { + this.validateSequenceValues(valuesWidget); + } + } else { + this.updateWidgetConstraints(); + } } } } - }, 100); // Small delay to ensure graph is loaded + }; + + // Check immediately + checkConnections(); + + // Also check after a delay to ensure graph is fully loaded + setTimeout(checkConnections, 100); + + // And check once more after a longer delay + setTimeout(checkConnections, 1000); return result; }; @@ -56,54 +96,37 @@ app.registerExtension({ const originalOnConnectOutput = nodeType.prototype.onConnectOutput; nodeType.prototype.onConnectOutput = function (slot, type, input, targetNode, targetSlot) { console.log("Output connection made from control node:", this.title); - console.log("Target node:", targetNode?.title); - console.log("Target slot:", targetSlot); - console.log("All target widgets:", targetNode?.widgets); - // Call original connection handler + // Call original connection handler if it exists const result = originalOnConnectOutput?.apply(this, arguments); if (targetNode?.widgets) { - // Find widget by input name instead of slot index const inputName = targetNode.inputs[targetSlot]?.name; - console.log("Looking for widget matching input name:", inputName); - const targetWidget = targetNode.widgets.find(w => w.name === inputName); + console.log("Found target widget:", targetWidget); - if (targetWidget?.options) { - console.log("Target widget options:", targetWidget.options); - - // Store the constraints including step + if (targetWidget?.options || (targetWidget?.type === "converted-widget" && targetWidget.options)) { + // Handle both regular and converted widgets + const options = targetWidget.options; this.targetConstraints = { - min: targetWidget.options.min, - max: targetWidget.options.max, - step: targetWidget.options.step + min: options.min, + max: options.max, + step: options.step }; - console.log("Setting constraints:", this.targetConstraints); - - // Update widgets with new constraints - const widgets = ["maximum_value", "minimum_value", "starting_value"]; - widgets.forEach(name => { - const widget = this.widgets.find(w => w.name === name); - if (widget) { - if (this.targetConstraints.min !== undefined) { - widget.options.min = this.targetConstraints.min; - } - if (this.targetConstraints.max !== undefined) { - widget.options.max = this.targetConstraints.max; - } - if (this.targetConstraints.step !== undefined) { - widget.options.step = this.targetConstraints.step; - } - } - }); + console.log("Set constraints:", this.targetConstraints); - // Clamp current values - this.clampWidgetValues(); - } else { - console.log("Widget has no options"); + // For sequence nodes, validate the values string + if (nodeData.category?.includes("/sequence")) { + const valuesWidget = this.widgets.find(w => w.name === "values"); + if (valuesWidget) { + this.validateSequenceValues(valuesWidget); + } + } else { + // Existing validation for non-sequence nodes + this.updateWidgetConstraints(); + } } } @@ -151,16 +174,104 @@ app.registerExtension({ } }; + // Add method to validate sequence values + nodeType.prototype.validateSequenceValues = function(widget) { + if (!this.targetConstraints) { + console.log("No target constraints found for validation"); + return; + } + + console.log("Validating sequence values:", { + widget: widget, + value: widget.value, + constraints: this.targetConstraints + }); + + const separator = widget.options?.multiline ? "\n" : ","; + const values = widget.value.split(separator) + .map(v => v.trim()) + .filter(v => v.length > 0); + + // Convert and validate values based on node type + const isFloat = this.type === "FloatSequence"; + const isInt = this.type === "IntSequence"; + + console.log("Processing values:", { + nodeType: this.type, + isFloat: isFloat, + isInt: isInt, + values: values + }); + + if (isFloat || isInt) { + const validValues = values.map(v => { + let num = isFloat ? parseFloat(v) : parseInt(v); + if (isNaN(num)) num = this.targetConstraints.min; + const clamped = Math.max(this.targetConstraints.min, Math.min(num, this.targetConstraints.max)); + console.log(`Validating value: ${v} -> ${num} -> ${clamped}`); + return isFloat ? clamped.toString() : Math.round(clamped).toString(); + }); + + // Update the widget value with validated values + const newValue = validValues.join(separator); + console.log("Setting new value:", newValue); + widget.value = newValue; + } + }; + // Override the widget's callback to enforce constraints const originalWidgetCallback = nodeType.prototype.onWidgetChanged; + console.log("Setting up widget change handler for:", nodeData.name, { + hasOriginalCallback: !!originalWidgetCallback + }); + nodeType.prototype.onWidgetChanged = function(widget, value) { - const result = originalWidgetCallback?.apply(this, arguments); - - // If this is one of our constrained widgets, enforce the constraints - if (["maximum_value", "minimum_value", "starting_value"].includes(widget.name)) { + console.log("Widget changed - BEFORE validation:", { + nodeName: this.title, + widgetName: widget.name, + originalValue: value, + widgetValue: widget.value, + hasConstraints: !!this.targetConstraints, + nodeType: this.type, + constraints: this.targetConstraints + }); + + // For sequence nodes, validate before calling original callback + if (widget.name === "values" && this.targetConstraints) { + console.log("About to validate sequence values"); + this.validateSequenceValues(widget); + // Important: Update the value parameter to match the validated widget value + value = widget.value; // Use the validated value + + // Force widget update to ensure UI reflects validated value + widget.value = value; + if (widget.callback) { + widget.callback(value); + } + + console.log("After validation:", { + newValue: value, + widgetValue: widget.value, + originalValue: value + }); + } else if (["maximum_value", "minimum_value", "starting_value"].includes(widget.name)) { + console.log("Clamping widget values"); this.clampWidgetValues(); } + console.log("Before calling original callback:", { + finalValue: value, + widgetValue: widget.value + }); + + // Call original callback with validated value + const result = originalWidgetCallback?.apply(this, [widget, value]); + + console.log("After original callback:", { + result: result, + finalWidgetValue: widget.value + }); + return result; }; }