Files
PBandDev-comfyui-node-organ…/src/layout/layout-utils.ts
T

120 lines
3.3 KiB
TypeScript

import type { LGraphNode } from "./types";
/**
* Get internal edges between group members by examining node connections
* Returns array of [sourceId, targetId] pairs where both are in the group
*/
export function getInternalEdges(
memberIds: Set<number>,
allNodes: Map<number, LGraphNode>
): Array<[number, number]> {
const edges: Array<[number, number]> = [];
// Build a map of linkId -> sourceNodeId for all member outputs
const linkToSource = new Map<number, number>();
for (const memberId of memberIds) {
const node = allNodes.get(memberId);
if (!node?.outputs) continue;
for (const output of node.outputs) {
if (output.links) {
for (const linkId of output.links) {
linkToSource.set(linkId, memberId);
}
}
}
}
// Check each member's inputs to find internal connections
for (const memberId of memberIds) {
const node = allNodes.get(memberId);
if (!node?.inputs) continue;
for (const input of node.inputs) {
if (input.link !== null && input.link !== undefined) {
const sourceId = linkToSource.get(input.link);
if (sourceId !== undefined && memberIds.has(sourceId)) {
edges.push([sourceId, memberId]);
}
}
}
}
return edges;
}
/**
* Assign layers to group members using longest-path algorithm
* Returns members grouped by layer (index 0 = sources)
*/
export function assignMemberLayers(
memberIds: Set<number>,
edges: Array<[number, number]>,
allNodes: Map<number, LGraphNode>
): LGraphNode[][] {
// Build adjacency and in-degree
const successors = new Map<number, number[]>();
const inDegree = new Map<number, number>();
for (const id of memberIds) {
successors.set(id, []);
inDegree.set(id, 0);
}
for (const [source, target] of edges) {
successors.get(source)!.push(target);
inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
}
// Longest-path layer assignment via topological sort
const nodeLayer = new Map<number, number>();
const queue: number[] = [];
// Start with sources (in-degree 0)
for (const id of memberIds) {
if ((inDegree.get(id) ?? 0) === 0) {
queue.push(id);
nodeLayer.set(id, 0);
}
}
while (queue.length > 0) {
const current = queue.shift()!;
const currentLayer = nodeLayer.get(current) ?? 0;
for (const succ of successors.get(current) ?? []) {
// Update successor layer to max of current paths
const succLayer = nodeLayer.get(succ) ?? 0;
nodeLayer.set(succ, Math.max(succLayer, currentLayer + 1));
// Decrease in-degree and add to queue when all predecessors processed
const newInDegree = (inDegree.get(succ) ?? 1) - 1;
inDegree.set(succ, newInDegree);
if (newInDegree === 0) {
queue.push(succ);
}
}
}
// Handle any unvisited nodes (cycles or disconnected)
for (const id of memberIds) {
if (!nodeLayer.has(id)) {
nodeLayer.set(id, 0);
}
}
// Group by layer
const maxLayer = Math.max(...nodeLayer.values(), 0);
const layers: LGraphNode[][] = Array.from({ length: maxLayer + 1 }, () => []);
for (const id of memberIds) {
const node = allNodes.get(id);
if (node) {
const layer = nodeLayer.get(id) ?? 0;
layers[layer].push(node);
}
}
return layers;
}