Files
Claude 3462044de5 CRITICAL: Add hidden region_boxes widget - fixes data passing to Python
Root cause: Python code was falling back to hardcoded defaults because
node.properties["values"] from JavaScript weren't being passed to Python
on fresh (unsaved) nodes. They only worked after saving/loading workflow.

The fix:
1. Added hidden 'region_boxes' STRING input to both nodes (Python)
2. Created hidden widget that serializes node.properties["values"] to JSON (JavaScript)
3. Update hidden widget whenever regions change (transformFunc, width/height scaling)
4. Python now checks region_boxes FIRST, then falls back to saved workflow data

This ensures region data is passed correctly even on brand new nodes.

Updated percentage-based defaults to match calculateDefaultRegions():
- 512x512: [[41, 102, 138, 307], [360, 77, 140, 348]]
- 1024x1024: [[82, 205, 277, 614], [717, 154, 287, 696]]

User should now see car and giraffe in correct positions immediately!
2025-11-23 02:07:48 +00:00

206 lines
6.1 KiB
JavaScript

export function CUSTOM_INT(node, inputName, val, func, config = {}) {
return {
widget: node.addWidget(
"number",
inputName,
val,
func,
Object.assign({}, { min: 0, max: 4096, step: 640, precision: 0 }, config)
),
};
}
export function recursiveLinkUpstream(node, type, depth, index=null) {
depth += 1
let connections = []
const inputList = (index !== null) ? [index] : [...Array(node.inputs.length).keys()]
if (inputList.length === 0) { return }
for (let i of inputList) {
const link = node.inputs[i].link
if (link) {
const nodeID = node.graph.links[link].origin_id
const slotID = node.graph.links[link].origin_slot
const connectedNode = node.graph._nodes_by_id[nodeID]
if (connectedNode.outputs[slotID].type === type) {
connections.push([connectedNode.id, depth])
if (connectedNode.inputs) {
const index = (connectedNode.type === "LatentComposite") ? 0 : null
connections = connections.concat(recursiveLinkUpstream(connectedNode, type, depth, index))
} else {
}
}
}
}
return connections
}
export function transformFunc(widget, value, node, index) {
const s = widget.options.step / 10;
widget.value = Math.round(value / s) * s;
// Convert 1-based region selector (Region 1, Region 2...) to 0-based array index
const regionSelectorValue = node.widgets[node.index].value;
const arrayIndex = regionSelectorValue - 1; // Region 1 → index 0, Region 2 → index 1, etc.
node.properties["values"][arrayIndex][index] = widget.value
if (node.widgets_values) {
node.widgets_values[2] = node.properties["values"].join()
}
// Update hidden region_boxes widget with current values
const boxWidget = node.widgets.find(w => w.name === "region_boxes");
if (boxWidget) {
boxWidget.value = JSON.stringify(node.properties["values"]);
}
}
export function swapInputs(node, indexA, indexB) {
const linkA = node.inputs[indexA].link
let origin_slotA = null
let node_IDA = null
let connectedNodeA = null
let labelA = node.inputs[indexA].label || null
const linkB = node.inputs[indexB].link
let origin_slotB = null
let node_IDB = null
let connectedNodeB = null
let labelB = node.inputs[indexB].label || null
if (linkA) {
node_IDA = node.graph.links[linkA].origin_id
origin_slotA = node.graph.links[linkA].origin_slot
connectedNodeA = node.graph._nodes_by_id[node_IDA]
node.disconnectInput(indexA)
}
if (linkB) {
node_IDB = node.graph.links[linkB].origin_id
origin_slotB = node.graph.links[linkB].origin_slot
connectedNodeB = node.graph._nodes_by_id[node_IDB]
node.disconnectInput(indexB)
}
if (linkA) {
connectedNodeA.connect(origin_slotA, node, indexB)
}
if (linkB) {
connectedNodeB.connect(origin_slotB, node, indexA)
}
node.inputs[indexA].label = labelB
node.inputs[indexB].label = labelA
}
export function renameNodeInputs(node, name, offset=0) {
for (let i=offset; i < node.inputs.length; i++) {
node.inputs[i].name = `${name}${i-offset}`
}
}
export function removeNodeInputs(node, indexesToRemove, offset=0) {
indexesToRemove.sort((a, b) => b - a);
for (let i of indexesToRemove) {
if (node.inputs.length <= 2) {
// Minimum 2 inputs required
continue
}
node.removeInput(i)
node.properties.values.splice(i-offset, 1)
}
const inputLength = node.properties["values"].length-1
node.widgets[node.index].options.max = inputLength
if (node.widgets[node.index].value > inputLength) {
node.widgets[node.index].value = inputLength
}
node.onResize(node.size)
}
export function getDrawColor(percent, alpha) {
let h = 360*percent
let s = 50;
let l = 50;
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
};
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
}
/**
* Calculate default region positions as percentages of width/height
* Ensures results are divisible by 8 for latent space compatibility
* @param {number} width - Canvas width
* @param {number} height - Canvas height
* @returns {Array} Array of [x, y, w, h, strength] for 2 regions
*/
export function calculateDefaultRegions(width, height) {
// Round to nearest multiple of 8
const round8 = (val) => Math.round(val / 8) * 8;
// Region 1: Left third (8% to 35% of width)
const region1_x = round8(width * 0.08);
const region1_w = round8(width * 0.27);
const region1_y = round8(height * 0.20); // Start at 20% down
const region1_h = round8(height * 0.60); // 60% of height
// Region 2: Right third (70% to 98% of width)
const region2_x = round8(width * 0.70);
const region2_w = round8(width * 0.28);
const region2_y = round8(height * 0.15); // Start at 15% down
const region2_h = round8(height * 0.68); // 68% of height (taller for giraffe)
return [
[region1_x, region1_y, region1_w, region1_h, 2.0], // Region 1 - left third
[region2_x, region2_y, region2_w, region2_h, 2.0] // Region 2 - right third
];
}
export function computeCanvasSize(node, size) {
if (!node.widgets || node.widgets.length === 0 || node.widgets[0].last_y == null) return;
// FIXED canvas height - simpler and more reliable than dynamic calculation
const CANVAS_HEIGHT = 280;
const WIDGET_SPACING = 4;
let y = LiteGraph.NODE_WIDGET_HEIGHT * Math.max(node.inputs ? node.inputs.length : 0, node.outputs ? node.outputs.length : 0) + 5;
// Position widgets sequentially with canvas at fixed height
for (const w of node.widgets) {
w.y = y;
if (w.type === "customCanvas") {
y += CANVAS_HEIGHT;
} else if (w.computeSize) {
y += w.computeSize()[1] + WIDGET_SPACING;
} else {
y += LiteGraph.NODE_WIDGET_HEIGHT + WIDGET_SPACING;
}
}
// Set canvas height
node.canvasHeight = CANVAS_HEIGHT;
// Ensure node is tall enough to fit all widgets
// Canvas is drawn directly to LiteGraph canvas, no HTML element overlay
const BOTTOM_MARGIN = 10; // Small margin for visual breathing room
const requiredHeight = y + BOTTOM_MARGIN;
if (node.size[1] < requiredHeight) {
node.size[1] = requiredHeight;
if (node.graph) {
node.graph.setDirtyCanvas(true);
}
}
}