164 lines
5.8 KiB
JavaScript
164 lines
5.8 KiB
JavaScript
function gcd(a, b) {
|
|
// Функция для вычисления наибольшего общего делителя (НОД)
|
|
while (b !== 0) {
|
|
let t = b;
|
|
b = a % b;
|
|
a = t;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
function lcm(a, b) {
|
|
// Функция для вычисления наименьшего общего кратного (НОК)
|
|
return (a * b) / gcd(a, b);
|
|
}
|
|
|
|
function findPatternLength(rules) {
|
|
// Вычисление длины цикла как НОК всех process_every
|
|
return rules.map(rule => rule.process_every).reduce((acc, val) => lcm(acc, val), 1);
|
|
}
|
|
|
|
export function generatePattern(rules) {
|
|
let length = findPatternLength(rules); // Определение длины паттерна
|
|
let pattern = new Array(length).fill(0);
|
|
|
|
rules.forEach(rule => {
|
|
let offset = rule.offset % rule.process_every;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
let value = ((i + offset) % rule.process_every === 0) === (rule.mode === "process_every") ? 1 : 0;
|
|
pattern[i] = pattern[i] || value;
|
|
}
|
|
});
|
|
|
|
return pattern;
|
|
}
|
|
|
|
export function recursiveLinkUpstream(node, slot_type, node_type, depth) {
|
|
depth += 1
|
|
let connections = []
|
|
const inputList = [...Array(node.inputs.length).keys()]
|
|
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 === slot_type) {
|
|
|
|
connections.push([connectedNode.id, depth])
|
|
|
|
if (connectedNode.inputs) {
|
|
const index = (connectedNode.type === node_type) ? 0 : null
|
|
connections = connections.concat(recursiveLinkUpstream(connectedNode, slot_type, node_type, depth))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return connections
|
|
}
|
|
|
|
export function renameNodeInputs(node, name) {
|
|
for (let i=0; i < node.inputs.length; i++) {
|
|
node.inputs[i].name = `${name}${i + 1}`
|
|
}
|
|
}
|
|
|
|
export function removeNodeInputs(node, indexesToRemove) {
|
|
indexesToRemove.sort((a, b) => b - a);
|
|
|
|
for (let i of indexesToRemove) {
|
|
if (node.inputs.length <= 2) { console.log("too short"); continue } // if only 2 left
|
|
node.removeInput(i)
|
|
}
|
|
|
|
node.setSize(node.computeSize())
|
|
node.graph?.setDirtyCanvas(true, true)
|
|
}
|
|
|
|
const MARGIN_X = 15; // side padding — matches the node's standard widgets
|
|
const MARGIN_Y = 10; // top/bottom padding around the grid (node-local px)
|
|
const MIN_SQUARE = 24; // smallest square that still shows the row on tiny/long patterns
|
|
const MAX_SQUARE = 72; // largest square, so a short pattern doesn't blow up
|
|
const MIN_NODE_WIDTH = 220; // enough width for the squares to read
|
|
|
|
// Square side that fits the given (full node) width and column count. Squares
|
|
// shrink to fit a long pattern (down to 1px) and are capped so a short one stays
|
|
// reasonable. Guarantees squareSize * cols <= width - 2*MARGIN_X, i.e. no overflow.
|
|
function fitSquareSize(width, cols) {
|
|
const availWidth = width - MARGIN_X * 2;
|
|
return Math.max(1, Math.min(availWidth / Math.max(cols, 1), MAX_SQUARE));
|
|
}
|
|
|
|
/**
|
|
* Adds a self-contained canvas widget that draws a pattern preview directly onto
|
|
* the LiteGraph node context. Works with the current ComfyUI frontend (Nodes v2):
|
|
* it paints in node-local coordinates, so it scales and positions correctly with
|
|
* zoom and DPI — no DOM overlay and no manual ctx.getTransform() math.
|
|
*
|
|
* The preview auto-sizes to the space available inside the node: computeSize()
|
|
* reports exactly the height the grid needs for the current width and pattern
|
|
* length, so the node reserves the right amount of room, the squares grow/shrink
|
|
* as the node gets wider/narrower or the pattern longer/shorter, and the drawing
|
|
* always stays inside the node body.
|
|
*
|
|
* @param node the LiteGraph node
|
|
* @param name widget name
|
|
* @param getPattern (node) => number[] the 0/1 pattern to render
|
|
* @param drawSquares (ctx, startX, startY, squareSize, pattern) => void
|
|
*/
|
|
export function addCustomCanvasWidget(node, name, getPattern, drawSquares) {
|
|
const widget = {
|
|
type: "customCanvas",
|
|
name,
|
|
value: undefined,
|
|
options: { serialize: false },
|
|
computeSize() {
|
|
// Use the real node width, not the passed value: LiteGraph feeds a
|
|
// widened width to selected nodes, which would inflate the reserve.
|
|
const nodeWidth = node.size[0];
|
|
const cols = getPattern(node).length || 1;
|
|
const rowHeight = Math.max(fitSquareSize(nodeWidth, cols), MIN_SQUARE);
|
|
return [nodeWidth, rowHeight + MARGIN_Y * 2];
|
|
},
|
|
draw(ctx, node, width, widgetY) {
|
|
const pattern = getPattern(node);
|
|
if (!pattern || !pattern.length) return;
|
|
|
|
// Use node.size[0] rather than the `width` argument: on a selected node
|
|
// LiteGraph passes an enlarged width, which would push the grid right and
|
|
// peg the square size at its cap. node.size[0] is stable in both states.
|
|
const nodeWidth = node.size[0];
|
|
const cols = pattern.length;
|
|
const squareSize = fitSquareSize(nodeWidth, cols);
|
|
|
|
// Same band height computeSize reserved -> the grid stays inside the node.
|
|
const bandHeight = Math.max(squareSize, MIN_SQUARE) + MARGIN_Y * 2;
|
|
const gridWidth = squareSize * cols;
|
|
|
|
const startX = (nodeWidth - gridWidth) / 2;
|
|
const startY = widgetY + (bandHeight - squareSize) / 2;
|
|
|
|
// Light backing drawn first so empty cells stay readable on dark nodes.
|
|
ctx.fillStyle = "#ffffffcc";
|
|
ctx.fillRect(startX, startY, gridWidth, squareSize);
|
|
|
|
drawSquares(ctx, startX, startY, squareSize, pattern);
|
|
},
|
|
};
|
|
|
|
node.addCustomWidget(widget);
|
|
|
|
// Widen a touch and reserve the preview height on creation. On workflow load
|
|
// LiteGraph applies the saved size after onNodeCreated, so this does not fight
|
|
// persisted sizes.
|
|
node.size[0] = Math.max(node.size[0], MIN_NODE_WIDTH);
|
|
node.setSize(node.computeSize());
|
|
|
|
return widget;
|
|
}
|
|
|
|
|