mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
[backport core/1.41] fix: respect 'always snap to grid' when auto-scale layout from nodes 1.0 to 2.0 (#10078)
Backport of #9332 to `core/1.41` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10078-backport-core-1-41-fix-respect-always-snap-to-grid-when-auto-scale-layout-from-node-3256d73d365081a2ae67fd37a76f7216) by [Unito](https://www.unito.io) Co-authored-by: woctordho <woctordho@outlook.com>
This commit is contained in:
@@ -45,7 +45,8 @@ import { LiteGraph, SubgraphNode } from './litegraph'
|
||||
import {
|
||||
alignOutsideContainer,
|
||||
alignToContainer,
|
||||
createBounds
|
||||
createBounds,
|
||||
snapPoint
|
||||
} from './measure'
|
||||
import { SubgraphInput } from './subgraph/SubgraphInput'
|
||||
import { SubgraphInputNode } from './subgraph/SubgraphInputNode'
|
||||
@@ -2594,7 +2595,18 @@ export class LGraph
|
||||
|
||||
// configure nodes afterwards so they can reach each other
|
||||
for (const [id, nodeData] of nodeDataMap) {
|
||||
this.getNodeById(id)?.configure(nodeData)
|
||||
const node = this.getNodeById(id)
|
||||
node?.configure(nodeData)
|
||||
|
||||
if (LiteGraph.alwaysSnapToGrid && node) {
|
||||
const snapTo = this.getSnapToGridSize()
|
||||
if (node.snapToGrid(snapTo)) {
|
||||
// snapToGrid mutates the internal _pos array in-place, bypassing the setter
|
||||
// This reassignment triggers the pos setter to sync to the Vue layout store
|
||||
node.pos = [node.pos[0], node.pos[1]]
|
||||
}
|
||||
snapPoint(node.size, snapTo, 'ceil')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2951,6 +2951,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
|
||||
// Enforce minimum size
|
||||
const min = node.computeSize()
|
||||
if (this._snapToGrid) {
|
||||
// Previously newBounds.size is snapped with 'round'
|
||||
// Now the minimum size is snapped with 'ceil' to avoid clipping
|
||||
snapPoint(min, this._snapToGrid, 'ceil')
|
||||
}
|
||||
if (newBounds.width < min[0]) {
|
||||
// If resizing from left, adjust position to maintain right edge
|
||||
if (resizeDirection.includes('W')) {
|
||||
|
||||
@@ -130,6 +130,34 @@ test('snapPoint correctly snaps points to grid', ({ expect }) => {
|
||||
expect(point3).toEqual([20, 20])
|
||||
})
|
||||
|
||||
test('snapPoint correctly snaps points to grid using ceil', ({ expect }) => {
|
||||
const point: Point = [12.3, 18.7]
|
||||
expect(snapPoint(point, 5, 'ceil')).toBe(true)
|
||||
expect(point).toEqual([15, 20])
|
||||
|
||||
const point2: Point = [15, 20]
|
||||
expect(snapPoint(point2, 5, 'ceil')).toBe(true)
|
||||
expect(point2).toEqual([15, 20])
|
||||
|
||||
const point3: Point = [15.1, -18.7]
|
||||
expect(snapPoint(point3, 10, 'ceil')).toBe(true)
|
||||
expect(point3).toEqual([20, -10])
|
||||
})
|
||||
|
||||
test('snapPoint correctly snaps points to grid using floor', ({ expect }) => {
|
||||
const point: Point = [12.3, 18.7]
|
||||
expect(snapPoint(point, 5, 'floor')).toBe(true)
|
||||
expect(point).toEqual([10, 15])
|
||||
|
||||
const point2: Point = [15, 20]
|
||||
expect(snapPoint(point2, 5, 'floor')).toBe(true)
|
||||
expect(point2).toEqual([15, 20])
|
||||
|
||||
const point3: Point = [15.1, -18.7]
|
||||
expect(snapPoint(point3, 10, 'floor')).toBe(true)
|
||||
expect(point3).toEqual([10, -20])
|
||||
})
|
||||
|
||||
test('createBounds correctly creates bounding box', ({ expect }) => {
|
||||
const objects = [
|
||||
{ boundingRect: [0, 0, 10, 10] as Rect },
|
||||
|
||||
@@ -351,11 +351,15 @@ export function createBounds(
|
||||
* @returns `true` if snapTo is truthy, otherwise `false`
|
||||
* @remarks `NaN` propagates through this function and does not affect return value.
|
||||
*/
|
||||
export function snapPoint(pos: Point | Rect, snapTo: number): boolean {
|
||||
export function snapPoint(
|
||||
pos: Point | Rect,
|
||||
snapTo: number,
|
||||
method: 'round' | 'ceil' | 'floor' = 'round'
|
||||
): boolean {
|
||||
if (!snapTo) return false
|
||||
|
||||
pos[0] = snapTo * Math.round(pos[0] / snapTo)
|
||||
pos[1] = snapTo * Math.round(pos[1] / snapTo)
|
||||
pos[0] = snapTo * Math[method](pos[0] / snapTo)
|
||||
pos[1] = snapTo * Math[method](pos[1] / snapTo)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export function useNodeSnap() {
|
||||
if (!gridSizeValue) return { ...size }
|
||||
|
||||
const sizeArray: [number, number] = [size.width, size.height]
|
||||
if (snapPoint(sizeArray, gridSizeValue)) {
|
||||
if (snapPoint(sizeArray, gridSizeValue, 'ceil')) {
|
||||
return { width: sizeArray[0], height: sizeArray[1] }
|
||||
}
|
||||
return { ...size }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { LGraph, RendererType } from '@/lib/litegraph/src/LGraph'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { snapPoint } from '@/lib/litegraph/src/measure'
|
||||
import type { Point as LGPoint } from '@/lib/litegraph/src/interfaces'
|
||||
import type { Point } from '@/renderer/core/layout/types'
|
||||
import {
|
||||
@@ -15,7 +17,7 @@ interface Positioned {
|
||||
size: LGPoint
|
||||
}
|
||||
|
||||
function unprojectPosSize(item: Positioned, anchor: Point) {
|
||||
function unprojectPosSize(item: Positioned, anchor: Point, graph: LGraph) {
|
||||
const c = unprojectBounds(
|
||||
{
|
||||
x: item.pos[0],
|
||||
@@ -30,6 +32,14 @@ function unprojectPosSize(item: Positioned, anchor: Point) {
|
||||
item.pos[1] = c.y
|
||||
item.size[0] = c.width
|
||||
item.size[1] = c.height
|
||||
|
||||
if (LiteGraph.alwaysSnapToGrid) {
|
||||
const snapTo = graph.getSnapToGridSize?.()
|
||||
if (snapTo) {
|
||||
snapPoint(item.pos, snapTo, 'round')
|
||||
snapPoint(item.size, snapTo, 'ceil')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +70,18 @@ export function ensureCorrectLayoutScale(
|
||||
|
||||
const anchor = getGraphRenderAnchor(graph)
|
||||
|
||||
const applySnap = (
|
||||
pos: [number, number],
|
||||
method: 'round' | 'ceil' | 'floor' = 'round'
|
||||
) => {
|
||||
if (LiteGraph.alwaysSnapToGrid) {
|
||||
const snapTo = graph.getSnapToGridSize?.()
|
||||
if (snapTo) {
|
||||
snapPoint(pos, snapTo, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of graph.nodes) {
|
||||
const c = unprojectBounds(
|
||||
{
|
||||
@@ -75,6 +97,9 @@ export function ensureCorrectLayoutScale(
|
||||
node.pos[1] = c.y
|
||||
node.size[0] = c.width
|
||||
node.size[1] = c.height
|
||||
|
||||
applySnap(node.pos)
|
||||
applySnap(node.size, 'ceil')
|
||||
}
|
||||
|
||||
for (const reroute of graph.reroutes.values()) {
|
||||
@@ -84,10 +109,11 @@ export function ensureCorrectLayoutScale(
|
||||
RENDER_SCALE_FACTOR
|
||||
)
|
||||
reroute.pos = [p.x, p.y]
|
||||
applySnap(reroute.pos)
|
||||
}
|
||||
|
||||
for (const group of graph.groups) {
|
||||
unprojectPosSize(group, anchor)
|
||||
unprojectPosSize(group, anchor, graph)
|
||||
}
|
||||
|
||||
if ('inputNode' in graph && 'outputNode' in graph) {
|
||||
@@ -96,7 +122,7 @@ export function ensureCorrectLayoutScale(
|
||||
graph.outputNode as SubgraphOutputNode
|
||||
]) {
|
||||
if (ioNode) {
|
||||
unprojectPosSize(ioNode, anchor)
|
||||
unprojectPosSize(ioNode, anchor, graph)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
LGraphNode,
|
||||
LiteGraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { snapPoint } from '@/lib/litegraph/src/measure'
|
||||
import type { Vector2 } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
@@ -1343,10 +1344,14 @@ export class ComfyApp {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
const snapTo = LiteGraph.alwaysSnapToGrid
|
||||
? this.rootGraph.getSnapToGridSize()
|
||||
: 0
|
||||
forEachNode(this.rootGraph, (node) => {
|
||||
const size = node.computeSize()
|
||||
size[0] = Math.max(node.size[0], size[0])
|
||||
size[1] = Math.max(node.size[1], size[1])
|
||||
snapPoint(size, snapTo, 'ceil')
|
||||
node.setSize(size)
|
||||
if (node.widgets) {
|
||||
// If you break something in the backend and want to patch workflows in the frontend
|
||||
|
||||
Reference in New Issue
Block a user