feat: groups working

This commit is contained in:
Simula_r
2025-10-20 14:47:49 -07:00
parent e550b1c7f7
commit 7d4b9b3cfd

View File

@@ -1,346 +1,125 @@
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { ReadOnlyRectangle } from '@/lib/litegraph/src/infrastructure/Rectangle'
import type { Point } from '@/lib/litegraph/src/interfaces'
import { createBounds } from '@/lib/litegraph/src/measure'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { app as comfyApp } from '@/scripts/app'
type posAndBounds = {
id: NodeId
position: Point
bounds: ReadOnlyRectangle
}
/* eslint-disable no-console */
type NodeWithVueBounds = {
id: NodeId
position: Point
lgBounds: ReadOnlyRectangle
vueBounds: ReadOnlyRectangle
}
//Problem. When switching from litegraph to vue nodes because the vue nodes are a new design they are arbitrarily and non uniformally different sized but all together larger than litegraph nodes. But they use the same posiiton as the litegraph nodes... so that means they can be overlapped. And for litegraph workflows where nodes are very close together the overlap can be often and severe because the vue nodes are much larger.
//Solution take the litegraph node positions and bounds and scale them proportionally from the center of the canvas before the switch to vue nodes.
//Implementation guide:
//Step 1: litegraph nodes pos and bounds to new array to work with
//Get all the nodes positions and bounds in litegraph and put them in a new array to work with. This array is called lgOriginalPosAndBounds
//Scale their positions and bounds proportionally from the center of the all nodes bounds
//Determine the right sclaing factor for now we can scale all of them by say 50% 1.5x
//For convenince assign new scaled pos and bounds to new array lgScaledPosAndBounds
//Step 2: iterate over the lgScaledPosAndBounds positions and move the vue nodes there.
//Loop over the lgScalePosAndBounds
//Loop over the layoutStore.getAllNodes().value
//For each lgScalePosAndBounds find the layoutStore.getAllNodes().value equivilent and move the node to the lgScalePosAndBounds position
//Acutally move the node using moveNode(nodeID, nodePos) from useLayoutMutations();
//Questions:
// 1. Q: how do we get litegraph nodes pos and bounds for every node cleanly and accurately in canvas space? A: Should be able to use comfyApp.canvas.graph.nodes
//How do we check lgScalePosAndBounds item is the same item as layoutStore.getAllNodes().value maybe we use nodeID? A: yes we can use graph.getNodeById()
//The before should be all the litegraph nodes exactly where they are
//The after should be all the litegraph nodes scaled proportionally from their center and the vue nodes moved to their positions.
//Next Problem. Groups are not handled by this function. So if there are groups within the workflow. We are not scaling them which means when the node positions change they often are outisdeo of the groups and then it looks broken. In othere words the problem is groups do not scale with nodes.
//Quesitons: How do groups work in litegraph. How can we get the node bounds? Can we just scale their position in the exact same way? Should be able to?
//Rough idea: We need to get all groups. Loop over them, read their Rect x and y and their width and height. And then after are done scaling the nodes posiitons we need to do the same for the groups but we also need to scale their width and height.
const SCALE_FACTOR = 1.5
export function useFixVueNodeOverlap() {
const allNodes = layoutStore.getAllNodes().value
const graph = comfyApp.canvas?.graph
const allVueNodes = layoutStore.getAllNodes().value
const canvas = comfyApp.canvas
const graph = canvas.graph
const { moveNode } = useLayoutMutations()
//Problem. When switching from litegraph to vue nodes because the vue nodes are a new design they are arbitrarily and non uniformally different sized but all together larger than litegraph nodes. But they use the same posiiton as the litegraph nodes... so that means they can be overlapped. And for litegraph workflows where nodes are very close together the overlap can be often and severe because the vue nodes are much larger.
if (!graph || !graph.nodes) return
//Solution take the litegraph node positions and bounds and scale them proportionally from the center of the canvas before the switch to vue nodes.
//Implementation guide:
//Step 1: litegraph nodes pos and bounds to new array to work with
//Get all the nodes positions and bounds in litegraph and put them in a new array to work with. This array is called lgOriginalPosAndBounds
//Scale their positions and bounds proportionally from the center of the canvas
//Determine the right sclaing factor for now we can scale all of them by say 20% (for now)
//For convenince assign new scaled pos and bounds to new array lgScaledPosAndBounds
//Step 2: iterate over the lgScaledPosAndBounds positions and move the vue nodes there.
//Loop over the lgScalePosAndBounds
//Loop over the layoutStore.getAllNodes().value
//For each lgScalePosAndBounds find the layoutStore.getAllNodes().value equivilent and move the node to the lgScalePosAndBounds position
//Acutally move the node using moveNode(nodeID, nodePos) from useLayoutMutations();
//Questions:
// 1. Q: how do we get litegraph nodes pos and bounds for every node cleanly and accurately in canvas space? A: Should be able to use comfyApp.canvas.graph.nodes
//How do we check lgScalePosAndBounds item is the same item as layoutStore.getAllNodes().value maybe we use nodeID? A: yes we can use graph.getNodeById()
//The before should be all the litegraph nodes exactly where they are
//The after should be all the litegraph nodes scaled proportionally from their center and the vue nodes moved to their positions.
if (!graph) return
const lgOriginalPosAndBounds = graph?.nodes.map((node) => {
return {
id: node.id,
position: node.pos,
bounds: node.boundingRect
}
})
console.log(graph) //Prints that we have one.
const lgOriginalNodesPosAndBounds = graph?.nodes.map((node) => ({
id: node.id,
position: node.pos,
boundinRect: node.boundingRect
}))
const lgBounds = createBounds(graph.nodes)
console.log(lgBounds)
const lgBoundsCenterX = lgBounds![0] + lgBounds![2] / 2
const lgBoundsCenterY = lgBounds![1] + lgBounds![3] / 2
// Merge LiteGraph positions with Vue node bounds
const nodesWithVueBounds = mergeWithVueBounds(lgOriginalPosAndBounds, allNodes)
console.log('Nodes with Vue bounds:', nodesWithVueBounds)
// Calculate overlap and get the closest pair distance
const { maxOverlap, minDistance } = calculateOverlapAndMinDistance(nodesWithVueBounds)
console.log('Maximum overlap with Vue bounds:', maxOverlap, 'px')
console.log('Minimum distance between overlapping nodes:', minDistance, 'px')
// Calculate total expansion needed (max overlap + 20px buffer)
const additionalSpace = 20
const totalExpansion = maxOverlap + additionalSpace
console.log('Total expansion needed:', totalExpansion, 'px')
console.log('LG Bounds dimensions:', lgBounds)
// Apply dynamic scaling uniformly to all nodes (preserves topology)
const lgScaledPosAndBounds = lgOriginalPosAndBounds?.map((posAndBounds) => {
const { position, bounds } = scaleNodeProportionally(
posAndBounds,
totalExpansion,
minDistance
)
return {
id: posAndBounds.id,
position,
bounds
const lgScaledNodesPosAndBounds = lgOriginalNodesPosAndBounds?.map(
(posAndBounds) => {
const { position } = scalePosFromCenter(posAndBounds.position)
return {
id: posAndBounds.id,
position,
boundingRect: posAndBounds.boundinRect
}
}
})
)
console.log('Litegraoh Original Node Pos and Bounds', lgOriginalPosAndBounds)
console.log('Litegraoh Scaled Node Pos and Bounds', lgScaledPosAndBounds)
console.log('All Vue Nodes', allNodes)
const scaledLgBounds = createBounds(lgScaledNodesPosAndBounds)
// Debug: Check ID types
if (lgScaledPosAndBounds && lgScaledPosAndBounds.length > 0) {
const lgId = lgScaledPosAndBounds[0].id
console.log('Sample LG ID:', lgId, 'Type:', typeof lgId)
}
if (allNodes.size > 0) {
const firstVueNode = Array.from(allNodes.values())[0]
console.log(
'Sample Vue ID:',
firstVueNode.id,
'Type:',
typeof firstVueNode.id
)
}
console.log('=== MOVING NODES ===')
let movedCount = 0
lgScaledPosAndBounds?.forEach((scaledPosAndBounds) => {
// Find the original position for comparison
const originalPosAndBounds = lgOriginalPosAndBounds?.find(
(orig) => orig.id === scaledPosAndBounds.id
)
allNodes.forEach((vueNode) => {
// Convert both IDs to string for comparison (LG uses number, Vue uses string)
lgScaledNodesPosAndBounds.forEach((scaledPosAndBounds) => {
allVueNodes.forEach((vueNode) => {
if (String(scaledPosAndBounds.id) === String(vueNode.id)) {
const newPos = {
x: scaledPosAndBounds.position[0],
y: scaledPosAndBounds.position[1]
}
const oldPos = originalPosAndBounds
? {
x: originalPosAndBounds.position[0],
y: originalPosAndBounds.position[1]
}
: { x: 0, y: 0 }
console.log(`Node ${vueNode.id}:`)
console.log(' Old position:', oldPos)
console.log(' New position:', newPos)
console.log(
' Delta:',
(newPos.x - oldPos.x).toFixed(1),
',',
(newPos.y - oldPos.y).toFixed(1)
)
moveNode(vueNode.id, newPos)
movedCount++
}
})
})
console.log('Total nodes moved:', movedCount)
function mergeWithVueBounds(
lgNodes: posAndBounds[],
vueNodes: ReadonlyMap<string, any>
): NodeWithVueBounds[] {
const result: NodeWithVueBounds[] = []
graph.groups.forEach((group) => {
const { position } = scalePosFromCenter(group.pos)
const newWidth = group.size[0] * SCALE_FACTOR
const newHeight = group.size[1] * SCALE_FACTOR
console.log('=== MERGE DEBUG ===')
console.log('Total LG nodes:', lgNodes.length)
console.log('Total Vue nodes:', vueNodes.size)
group.pos = [position[0], position[1]]
group.size = [newWidth, newHeight]
})
lgNodes.forEach((lgNode) => {
const vueNode = vueNodes.get(String(lgNode.id))
console.log(`Node ${lgNode.id}:`, {
hasVueNode: !!vueNode,
hasBounds: !!(vueNode && vueNode.bounds),
lgBounds: lgNode.bounds,
vueBounds: vueNode?.bounds
})
if (vueNode && vueNode.bounds) {
result.push({
id: lgNode.id,
position: lgNode.position,
lgBounds: lgNode.bounds,
vueBounds: vueNode.bounds
})
}
if (scaledLgBounds)
canvas.ds.fitToBounds(scaledLgBounds, {
zoom: 0.6
})
console.log('Merged nodes with Vue bounds:', result.length)
return result
}
function calculateOverlapAndMinDistance(
nodes: NodeWithVueBounds[]
): { maxOverlap: number; minDistance: number } {
let maxOverlap = 0
let minDistance = Infinity
console.log('=== OVERLAP CALCULATION DEBUG ===')
// Check every pair of nodes for overlap
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const nodeA = nodes[i]
const nodeB = nodes[j]
// Debug: Check the vueBounds format
if (i === 0 && j === 1) {
const vb = nodeA.vueBounds as any
console.log('VueBounds structure:')
console.log(' Keys:', Object.keys(vb))
console.log(' [0]:', vb[0])
console.log(' [1]:', vb[1])
console.log(' [2]:', vb[2])
console.log(' [3]:', vb[3])
console.log(' .x:', vb.x)
console.log(' .y:', vb.y)
console.log(' .width:', vb.width)
console.log(' .height:', vb.height)
console.log(' .left:', vb.left)
console.log(' .top:', vb.top)
console.log(' Full object:', JSON.stringify(vb, null, 2))
}
// Calculate center-to-center distance
// vueBounds is an object with {x, y, width, height} properties
const aVueBounds = nodeA.vueBounds as any
const bVueBounds = nodeB.vueBounds as any
const aCenterX = nodeA.position[0] + aVueBounds.width / 2
const aCenterY = nodeA.position[1] + aVueBounds.height / 2
const bCenterX = nodeB.position[0] + bVueBounds.width / 2
const bCenterY = nodeB.position[1] + bVueBounds.height / 2
const distance = Math.sqrt(
Math.pow(bCenterX - aCenterX, 2) + Math.pow(bCenterY - aCenterY, 2)
)
// Use Vue bounds with LiteGraph positions
const aLeft = nodeA.position[0]
const aTop = nodeA.position[1]
const aRight = aLeft + aVueBounds.width
const aBottom = aTop + aVueBounds.height
const bLeft = nodeB.position[0]
const bTop = nodeB.position[1]
const bRight = bLeft + bVueBounds.width
const bBottom = bTop + bVueBounds.height
// Calculate overlap in both dimensions
const overlapX = Math.max(
0,
Math.min(aRight, bRight) - Math.max(aLeft, bLeft)
)
const overlapY = Math.max(
0,
Math.min(aBottom, bBottom) - Math.max(aTop, bTop)
)
// Debug first few pairs
if (i < 2 && j < 3) {
console.log(`Pair ${nodeA.id}-${nodeB.id}:`, {
aRect: { left: aLeft, top: aTop, right: aRight, bottom: aBottom },
bRect: { left: bLeft, top: bTop, right: bRight, bottom: bBottom },
overlapX,
overlapY,
overlaps: overlapX > 0 && overlapY > 0
})
}
// Only count as overlap if both dimensions overlap
if (overlapX > 0 && overlapY > 0) {
// Use the minimum of the two dimensions as the overlap measure
const overlap = Math.min(overlapX, overlapY)
if (overlap > maxOverlap) {
maxOverlap = overlap
minDistance = distance
}
}
}
}
console.log('Final overlap results:', { maxOverlap, minDistance })
return { maxOverlap, minDistance: minDistance === Infinity ? 0 : minDistance }
}
function scaleNodeProportionally(
posAndBounds: posAndBounds,
totalExpansion: number,
minDistance: number
): {
function scalePosFromCenter(pos: Point): {
position: Point
bounds: ReadOnlyRectangle
} {
// If no expansion needed, return original position
if (totalExpansion <= 0 || minDistance === 0) {
return {
position: posAndBounds.position,
bounds: posAndBounds.bounds
}
}
const vectorX = pos[0] - lgBoundsCenterX
const vectorY = pos[1] - lgBoundsCenterY
// Calculate scale factor from expansion distance
// If lgBounds doesn't exist, use a default scale
if (!lgBounds || lgBounds[2] === 0) {
return {
position: posAndBounds.position,
bounds: posAndBounds.bounds
}
}
const scaledVectorX = vectorX * SCALE_FACTOR
const scaledVectorY = vectorY * SCALE_FACTOR
// CORRECT calculation:
// The closest overlapping nodes are currently minDistance apart
// We need them to be (minDistance + totalExpansion) apart
// When we scale from center, distances get multiplied by scaleFactor
// So: minDistance * scaleFactor = minDistance + totalExpansion
// Therefore: scaleFactor = (minDistance + totalExpansion) / minDistance
const scaleFactor = (minDistance + totalExpansion) / minDistance
console.log('Calculated scale factor:', scaleFactor)
console.log('This will move the closest nodes from', minDistance.toFixed(1), 'px apart to', (minDistance * scaleFactor).toFixed(1), 'px apart')
console.log('Increase:', ((minDistance * scaleFactor) - minDistance).toFixed(1), 'px (target was', totalExpansion, 'px)')
// Calculate center of all litegraph nodes bounding box
// lgBounds format: [x, y, width, height]
const centerX = lgBounds[0] + lgBounds[2] / 2
const centerY = lgBounds[1] + lgBounds[3] / 2
// Calculate vector from center to node position
const vectorX = posAndBounds.position[0] - centerX
const vectorY = posAndBounds.position[1] - centerY
// Scale the vector
const scaledVectorX = vectorX * scaleFactor
const scaledVectorY = vectorY * scaleFactor
// Calculate new position (center + scaled vector)
const newPosition: Point = [
centerX + scaledVectorX,
centerY + scaledVectorY
lgBoundsCenterX + scaledVectorX,
lgBoundsCenterY + scaledVectorY
]
// Return scaled position and original bounds
// (Vue will recalculate bounds based on actual rendering)
return {
position: newPosition,
bounds: posAndBounds.bounds
position: newPosition
}
}
console.log('Reached the end of the fix overlap code.')
console.log(
'Litegraoh Original Node Pos and Bounds',
lgOriginalNodesPosAndBounds
)
console.log('Litegraoh Scaled Node Pos and Bounds', lgScaledNodesPosAndBounds)
console.log('All Vue Nodes', allVueNodes)
}