mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-05 05:00:03 +00:00
Shared measure functions (#254)
* Add findPointOnCurve function * Add measure functions: contains centre/rect * Improve measure interfaces (ReadOnlyPoint/Rect) * nit - Doc * Add TS strict types * Add TS strict types * Add TS strict types * Add TS type
This commit is contained in:
@@ -49,7 +49,7 @@ export class LGraph {
|
||||
* ```
|
||||
*/
|
||||
links: Map<LinkId, LLink> & Record<LinkId, LLink>
|
||||
list_of_graphcanvas?: LGraphCanvas[]
|
||||
list_of_graphcanvas: LGraphCanvas[] | null
|
||||
status: number
|
||||
last_node_id: number
|
||||
last_link_id: number
|
||||
@@ -58,7 +58,7 @@ export class LGraph {
|
||||
_nodes: LGraphNode[]
|
||||
_nodes_by_id: Record<NodeId, LGraphNode>
|
||||
_nodes_in_order: LGraphNode[]
|
||||
_nodes_executable: LGraphNode[]
|
||||
_nodes_executable: LGraphNode[] | null
|
||||
_groups: LGraphGroup[]
|
||||
iteration: number
|
||||
globaltime: number
|
||||
@@ -69,7 +69,7 @@ export class LGraph {
|
||||
last_update_time: number
|
||||
starttime: number
|
||||
catch_errors: boolean
|
||||
execution_timer_id: number
|
||||
execution_timer_id: number | null
|
||||
errors_in_execution: boolean
|
||||
execution_time: number
|
||||
_last_trigger_time?: number
|
||||
@@ -101,8 +101,8 @@ export class LGraph {
|
||||
onOutputRenamed?(old_name: string, name: string): void
|
||||
onOutputTypeChanged?(name: string, type: string): void
|
||||
onOutputRemoved?(name: string): void
|
||||
onBeforeChange?(graph: LGraph, info: LGraphNode): void
|
||||
onAfterChange?(graph: LGraph, info: LGraphNode): void
|
||||
onBeforeChange?(graph: LGraph, info?: LGraphNode): void
|
||||
onAfterChange?(graph: LGraph, info?: LGraphNode): void
|
||||
onConnectionChange?(node: LGraphNode): void
|
||||
on_change?(graph: LGraph): void
|
||||
onSerialize?(data: ISerialisedGraph): void
|
||||
@@ -378,11 +378,11 @@ export class LGraph {
|
||||
}
|
||||
//This is more internal, it computes the executable nodes in order and returns it
|
||||
computeExecutionOrder(only_onExecute: boolean, set_level?: boolean): LGraphNode[] {
|
||||
let L: LGraphNode[] = []
|
||||
const L: LGraphNode[] = []
|
||||
const S: LGraphNode[] = []
|
||||
const M: Dictionary<LGraphNode> = {}
|
||||
const visited_links: Record<number, boolean> = {} //to avoid repeating links
|
||||
const remaining_links: Record<number, number> = {} //to a
|
||||
const visited_links: Record<NodeId, boolean> = {} //to avoid repeating links
|
||||
const remaining_links: Record<NodeId, number> = {} //to a
|
||||
|
||||
//search for the nodes without inputs (starting nodes)
|
||||
for (let i = 0, l = this._nodes.length; i < l; ++i) {
|
||||
@@ -414,10 +414,10 @@ export class LGraph {
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (S.length == 0) break
|
||||
|
||||
//get an starting node
|
||||
const node = S.shift()
|
||||
if (node === undefined) break
|
||||
|
||||
L.push(node) //add to ordered list
|
||||
delete M[node.id] //remove from the pending nodes
|
||||
|
||||
@@ -471,13 +471,22 @@ export class LGraph {
|
||||
|
||||
const l = L.length
|
||||
|
||||
//save order number in the node
|
||||
for (let i = 0; i < l; ++i) {
|
||||
L[i].order = i
|
||||
/** Ensure type is set */
|
||||
type OrderedLGraphNode = LGraphNode & { order: number }
|
||||
|
||||
/** Sets the order property of each provided node to its index in {@link nodes}. */
|
||||
function setOrder(nodes: LGraphNode[]): asserts nodes is OrderedLGraphNode[] {
|
||||
const l = nodes.length
|
||||
for (let i = 0; i < l; ++i) {
|
||||
nodes[i].order = i
|
||||
}
|
||||
}
|
||||
|
||||
//save order number in the node
|
||||
setOrder(L)
|
||||
|
||||
//sort now by priority
|
||||
L = L.sort(function (A, B) {
|
||||
L.sort(function (A, B) {
|
||||
// @ts-expect-error ctor props
|
||||
const Ap = A.constructor.priority || A.priority || 0
|
||||
// @ts-expect-error ctor props
|
||||
@@ -490,9 +499,7 @@ export class LGraph {
|
||||
})
|
||||
|
||||
//save order number in the node, again...
|
||||
for (let i = 0; i < l; ++i) {
|
||||
L[i].order = i
|
||||
}
|
||||
setOrder(L)
|
||||
|
||||
return L
|
||||
}
|
||||
@@ -508,7 +515,7 @@ export class LGraph {
|
||||
|
||||
while (pending.length) {
|
||||
const current = pending.shift()
|
||||
if (!current.inputs) continue
|
||||
if (!current?.inputs) continue
|
||||
|
||||
if (!visited[current.id] && current != node) {
|
||||
visited[current.id] = true
|
||||
@@ -535,7 +542,7 @@ export class LGraph {
|
||||
margin = margin || 100
|
||||
|
||||
const nodes = this.computeExecutionOrder(false, true)
|
||||
const columns = []
|
||||
const columns: LGraphNode[][] = []
|
||||
for (let i = 0; i < nodes.length; ++i) {
|
||||
const node = nodes[i]
|
||||
const col = node._level || 1
|
||||
@@ -634,7 +641,7 @@ export class LGraph {
|
||||
* Adds a new node instance to this graph
|
||||
* @param {LGraphNode} node the instance of the node
|
||||
*/
|
||||
add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null {
|
||||
add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null | undefined {
|
||||
if (!node) return
|
||||
|
||||
// LEGACY: This was changed from constructor === LGraphGroup
|
||||
@@ -787,7 +794,7 @@ export class LGraph {
|
||||
* @return {Array} a list with all the nodes of this type
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
findNodesByClass(classObject: Function, result: LGraphNode[]): LGraphNode[] {
|
||||
findNodesByClass(classObject: Function, result?: LGraphNode[]): LGraphNode[] {
|
||||
result = result || []
|
||||
result.length = 0
|
||||
for (let i = 0, l = this._nodes.length; i < l; ++i) {
|
||||
@@ -806,7 +813,7 @@ export class LGraph {
|
||||
result = result || []
|
||||
result.length = 0
|
||||
for (let i = 0, l = this._nodes.length; i < l; ++i) {
|
||||
if (this._nodes[i].type.toLowerCase() == matchType)
|
||||
if (this._nodes[i].type?.toLowerCase() == matchType)
|
||||
result.push(this._nodes[i])
|
||||
}
|
||||
return result
|
||||
@@ -816,7 +823,7 @@ export class LGraph {
|
||||
* @param {String} name the name of the node to search
|
||||
* @return {Node} the node or null
|
||||
*/
|
||||
findNodeByTitle(title: string): LGraphNode {
|
||||
findNodeByTitle(title: string): LGraphNode | null {
|
||||
for (let i = 0, l = this._nodes.length; i < l; ++i) {
|
||||
if (this._nodes[i].title == title)
|
||||
return this._nodes[i]
|
||||
@@ -843,20 +850,14 @@ export class LGraph {
|
||||
* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
|
||||
* @return {LGraphNode} the node at this position or null
|
||||
*/
|
||||
getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode {
|
||||
getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode | null {
|
||||
nodes_list = nodes_list || this._nodes
|
||||
const nRet = null
|
||||
for (let i = nodes_list.length - 1; i >= 0; i--) {
|
||||
const n = nodes_list[i]
|
||||
const skip_title = n.constructor.title_mode == TitleMode.NO_TITLE
|
||||
if (n.isPointInside(x, y, margin, skip_title)) {
|
||||
// check for lesser interest nodes (TODO check for overlapping, use the top)
|
||||
/*if (typeof n == "LGraphGroup"){
|
||||
nRet = n;
|
||||
}else{*/
|
||||
if (n.isPointInside(x, y, margin, skip_title))
|
||||
return n
|
||||
/*}*/
|
||||
}
|
||||
}
|
||||
return nRet
|
||||
}
|
||||
@@ -956,7 +957,7 @@ export class LGraph {
|
||||
* @param {String} old_name
|
||||
* @param {String} new_name
|
||||
*/
|
||||
renameInput(old_name: string, name: string): boolean {
|
||||
renameInput(old_name: string, name: string): boolean | undefined {
|
||||
if (name == old_name) return
|
||||
|
||||
if (!this.inputs[old_name]) return false
|
||||
@@ -978,7 +979,7 @@ export class LGraph {
|
||||
* @param {String} name
|
||||
* @param {String} type
|
||||
*/
|
||||
changeInputType(name: string, type: string): boolean {
|
||||
changeInputType(name: string, type: string): boolean | undefined {
|
||||
if (!this.inputs[name]) return false
|
||||
|
||||
if (this.inputs[name].type &&
|
||||
@@ -1045,7 +1046,7 @@ export class LGraph {
|
||||
* @param {String} old_name
|
||||
* @param {String} new_name
|
||||
*/
|
||||
renameOutput(old_name: string, name: string): boolean {
|
||||
renameOutput(old_name: string, name: string): boolean | undefined {
|
||||
if (!this.outputs[old_name]) return false
|
||||
|
||||
if (this.outputs[name]) {
|
||||
@@ -1066,7 +1067,7 @@ export class LGraph {
|
||||
* @param {String} name
|
||||
* @param {String} type
|
||||
*/
|
||||
changeOutputType(name: string, type: string): boolean {
|
||||
changeOutputType(name: string, type: string): boolean | undefined {
|
||||
if (!this.outputs[name]) return false
|
||||
|
||||
if (this.outputs[name].type &&
|
||||
@@ -1215,7 +1216,7 @@ export class LGraph {
|
||||
* @param {String} str configure a graph from a JSON string
|
||||
* @param {Boolean} returns if there was any error parsing
|
||||
*/
|
||||
configure(data: ISerialisedGraph, keep_old?: boolean): boolean {
|
||||
configure(data: ISerialisedGraph, keep_old?: boolean): boolean | undefined {
|
||||
// TODO: Finish typing configure()
|
||||
if (!data) return
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export class LGraphNode {
|
||||
|
||||
title: string
|
||||
graph: LGraph | null = null
|
||||
id?: NodeId
|
||||
id: NodeId
|
||||
type: string | null = null
|
||||
inputs: INodeInputSlot[] = []
|
||||
outputs: INodeOutputSlot[] = []
|
||||
|
||||
@@ -26,29 +26,38 @@ export interface IFoundSlot extends IInputOrOutput {
|
||||
link_pos: Point
|
||||
}
|
||||
|
||||
/** A point on the canvas: x, y */
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number] | Float32Array | Float64Array
|
||||
|
||||
/** A size: width, height */
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number] | Float32Array | Float64Array
|
||||
|
||||
/** A very firm array */
|
||||
type ArRect = [x: number, y: number, width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates: x, y, width, height */
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect = ArRect | Float32Array | Float64Array
|
||||
|
||||
// TODO: Need regular arrays also?
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]`. Requires functions exclusive to `TypedArray`. */
|
||||
export type Rect32 = Float32Array
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates that will not be modified */
|
||||
export type ReadOnlyPoint = readonly [x: number, y: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect = readonly [x: number, y: number, width: number, height: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
type TypedArrays = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array
|
||||
type TypedBigIntArrays = BigInt64Array | BigUint64Array
|
||||
type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> = Omit<T, "fill" | "copyWithin" | "reverse" | "set" | "sort" | "subarray">
|
||||
|
||||
/** Union of property names that are of type Match */
|
||||
export type KeysOfType<T, Match> = { [P in keyof T]: T[P] extends Match ? P : never }[keyof T]
|
||||
|
||||
/** A new type that contains only the properties of T that are of type Match */
|
||||
export type PickByType<T, Match> = { [P in keyof T]: Extract<T[P], Match> }
|
||||
|
||||
/** The names of all methods and functions in T */
|
||||
export type MethodNames<T> = KeysOfType<T, (...args: any) => any>
|
||||
/** The names of all (optional) methods and functions in T */
|
||||
export type MethodNames<T> = KeysOfType<T, ((...args: any) => any) | undefined>
|
||||
|
||||
export interface IBoundaryNodes {
|
||||
top: LGraphNode
|
||||
@@ -113,7 +122,7 @@ export interface ConnectingLink extends IInputOrOutput {
|
||||
interface IContextMenuBase {
|
||||
title?: string
|
||||
className?: string
|
||||
callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void
|
||||
callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void | boolean
|
||||
}
|
||||
|
||||
/** ContextMenu */
|
||||
|
||||
100
src/measure.ts
100
src/measure.ts
@@ -1,15 +1,13 @@
|
||||
import type { Point, Rect } from "./interfaces"
|
||||
import type { Point, ReadOnlyPoint, ReadOnlyRect } from "./interfaces"
|
||||
import { LinkDirection } from "./types/globalEnums"
|
||||
|
||||
type PointReadOnly = readonly [x: number, y: number] | Float32Array | Float64Array
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points (2D vector)
|
||||
* @param a Point a as x, y
|
||||
* @param b Point b as x, y
|
||||
* @returns Distance between point a & b
|
||||
* @param a Point a as `x, y`
|
||||
* @param b Point b as `x, y`
|
||||
* @returns Distance between point {@link a} & {@link b}
|
||||
*/
|
||||
export function distance(a: PointReadOnly, b: PointReadOnly): number {
|
||||
export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
|
||||
return Math.sqrt(
|
||||
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
|
||||
)
|
||||
@@ -18,21 +16,21 @@ export function distance(a: PointReadOnly, b: PointReadOnly): number {
|
||||
/**
|
||||
* Calculates the distance2 (squared) between two points (2D vector).
|
||||
* Much faster when only comparing distances (closest/furthest point).
|
||||
* @param a Point a as x, y
|
||||
* @param b Point b as x, y
|
||||
* @returns Distance2 (squared) between point a & b
|
||||
* @param a Point a as `x, y`
|
||||
* @param b Point b as `x, y`
|
||||
* @returns Distance2 (squared) between point {@link a} & {@link b}
|
||||
*/
|
||||
export function dist2(a: PointReadOnly, b: PointReadOnly): number {
|
||||
export function dist2(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
|
||||
return ((b[0] - a[0]) * (b[0] - a[0])) + ((b[1] - a[1]) * (b[1] - a[1]))
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a point is inside a rectangle.
|
||||
* @param point The point to check, as x, y
|
||||
* @param rect The rectangle, as x, y, width, height
|
||||
* @returns true if the point is inside the rect, otherwise false
|
||||
* @param point The point to check, as `x, y`
|
||||
* @param rect The rectangle, as `x, y, width, height`
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isPointInRectangle(point: PointReadOnly, rect: Rect): boolean {
|
||||
export function isPointInRectangle(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
|
||||
return rect[0] < point[0]
|
||||
&& rect[0] + rect[2] > point[0]
|
||||
&& rect[1] < point[1]
|
||||
@@ -47,7 +45,7 @@ export function isPointInRectangle(point: PointReadOnly, rect: Rect): boolean {
|
||||
* @param top Rect y
|
||||
* @param width Rect width
|
||||
* @param height Rect height
|
||||
* @returns true if the point is inside the rect, otherwise false
|
||||
* @returns `true` if the point is inside the rect, otherwise `false`
|
||||
*/
|
||||
export function isInsideRectangle(x: number, y: number, left: number, top: number, width: number, height: number): boolean {
|
||||
return left < x
|
||||
@@ -61,7 +59,7 @@ export function isInsideRectangle(x: number, y: number, left: number, top: numbe
|
||||
* @param x Point x
|
||||
* @param y Point y
|
||||
* @param radius Radius to use as rough guide for octagon
|
||||
* @returns true if the point is roughly inside the octagon centred on 0,0 with specified radius
|
||||
* @returns `true` if the point is roughly inside the octagon centred on 0,0 with specified radius
|
||||
*/
|
||||
export function isSortaInsideOctagon(x: number, y: number, radius: number): boolean {
|
||||
const sum = Math.min(radius, Math.abs(x)) + Math.min(radius, Math.abs(y))
|
||||
@@ -70,11 +68,11 @@ export function isSortaInsideOctagon(x: number, y: number, radius: number): bool
|
||||
|
||||
/**
|
||||
* Determines if two rectangles have any overlap
|
||||
* @param a Rectangle A as x, y, width, height
|
||||
* @param b Rectangle B as x, y, width, height
|
||||
* @returns true if rectangles overlap, otherwise false
|
||||
* @param a Rectangle A as `x, y, width, height`
|
||||
* @param b Rectangle B as `x, y, width, height`
|
||||
* @returns `true` if rectangles overlap, otherwise `false`
|
||||
*/
|
||||
export function overlapBounding(a: Rect, b: Rect): boolean {
|
||||
export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
const aRight = a[0] + a[2]
|
||||
const aBottom = a[1] + a[3]
|
||||
const bRight = b[0] + b[2]
|
||||
@@ -88,6 +86,36 @@ export function overlapBounding(a: Rect, b: Rect): boolean {
|
||||
: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if rectangle {@link a} contains the centre point of rectangle {@link b}.
|
||||
* @param a Container rectangle A as `x, y, width, height`
|
||||
* @param b Sub-rectangle B as `x, y, width, height`
|
||||
* @returns `true` if {@link a} contains most of {@link b}, otherwise `false`
|
||||
*/
|
||||
export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
const centreX = b[0] + (b[2] * 0.5)
|
||||
const centreY = b[1] + (b[3] * 0.5)
|
||||
return isInsideRectangle(centreX, centreY, a[0], a[1], a[2], a[3])
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if rectangle {@link a} wholly contains rectangle {@link b}.
|
||||
* @param a Container rectangle A as `x, y, width, height`
|
||||
* @param b Sub-rectangle B as `x, y, width, height`
|
||||
* @returns `true` if {@link a} wholly contains {@link b}, otherwise `false`
|
||||
*/
|
||||
export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
||||
const aRight = a[0] + a[2]
|
||||
const aBottom = a[1] + a[3]
|
||||
const bRight = b[0] + b[2]
|
||||
const bBottom = b[1] + b[3]
|
||||
|
||||
return a[0] < b[0]
|
||||
&& a[1] < b[1]
|
||||
&& aRight > bRight
|
||||
&& aBottom > bBottom
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an offset in the specified direction to {@link out}
|
||||
* @param amount Amount of offset to add
|
||||
@@ -185,6 +213,34 @@ export function rotateLink(offset: Point, from: LinkDirection, to: LinkDirection
|
||||
* @param point The point to check
|
||||
* @returns 0 if all three points are in a straight line, a negative value if point is to the left of the projected line, or positive if the point is to the right
|
||||
*/
|
||||
export function getOrientation(lineStart: PointReadOnly, lineEnd: PointReadOnly, x: number, y: number): number {
|
||||
export function getOrientation(lineStart: ReadOnlyPoint, lineEnd: ReadOnlyPoint, x: number, y: number): number {
|
||||
return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) - ((lineEnd[0] - lineStart[0]) * (y - lineEnd[1]))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param out The array to store the point in
|
||||
* @param a Start point
|
||||
* @param b End point
|
||||
* @param controlA Start curve control point
|
||||
* @param controlB End curve control point
|
||||
* @param t Time: factor of distance to travel along the curve (e.g 0.25 is 25% along the curve)
|
||||
*/
|
||||
export function findPointOnCurve(
|
||||
out: Point,
|
||||
a: ReadOnlyPoint,
|
||||
b: ReadOnlyPoint,
|
||||
controlA: ReadOnlyPoint,
|
||||
controlB: ReadOnlyPoint,
|
||||
t: number = 0.5,
|
||||
): void {
|
||||
const iT = 1 - t
|
||||
|
||||
const c1 = iT * iT * iT
|
||||
const c2 = 3 * (iT * iT) * t
|
||||
const c3 = 3 * iT * (t * t)
|
||||
const c4 = t * t * t
|
||||
|
||||
out[0] = (c1 * a[0]) + (c2 * controlA[0]) + (c3 * controlB[0]) + (c4 * b[0])
|
||||
out[1] = (c1 * a[1]) + (c2 * controlA[1]) + (c3 * controlB[1]) + (c4 * b[1])
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface Serialisable<SerialisableObject> {
|
||||
/** Serialised LGraphNode */
|
||||
export interface ISerialisedNode {
|
||||
title?: string
|
||||
id?: NodeId
|
||||
id: NodeId
|
||||
type?: string
|
||||
pos?: Point
|
||||
size?: Size
|
||||
|
||||
Reference in New Issue
Block a user