From 05e6eac7b347655916302bb5a210b9aacd7a598e Mon Sep 17 00:00:00 2001 From: Huang-Huang Bao Date: Sat, 8 Jul 2023 11:27:56 +0800 Subject: [PATCH 1/7] Scale graph canvas based on DPI factor Similar to fixes in litegraph.js editor demo: https://github.com/ernestp/litegraph.js/blob/3ef215cf11b5d38cc4f7062d6f78b478e2f02b39/editor/js/code.js#L19-L28 Also workarounds to address viewpoint problem of lightgrapgh.js in DPI scaling scenario. Fixes #161 --- web/scripts/app.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/web/scripts/app.js b/web/scripts/app.js index bf424058f..d3a05c275 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -4,7 +4,19 @@ import { api } from "./api.js"; import { defaultGraph } from "./defaultGraph.js"; import { getPngMetadata, importA1111, getLatentMetadata } from "./pnginfo.js"; -/** +// DPI scaling fix, see https://github.com/comfyanonymous/ComfyUI/pull/845 +(function() { + const originalRenderInfo = LGraphCanvas.prototype.renderInfo + LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { + // Patch renderInfo() to use canvas.offsetHeight instead of canvas.height as bottom viewpoint bound + if (!y) { + y = this.canvas.offsetHeight - 80 + } + return originalRenderInfo.call(this, ctx, x, y) + } +})() + +/** * @typedef {import("types/comfy").ComfyExtension} ComfyExtension */ @@ -1038,8 +1050,12 @@ export class ComfyApp { this.graph.start(); function resizeCanvas() { - canvasEl.width = canvasEl.offsetWidth; - canvasEl.height = canvasEl.offsetHeight; + // Limit minimal scale to 1, see https://github.com/comfyanonymous/ComfyUI/pull/845 + const scale = Math.max(window.devicePixelRatio, 1); + const { width, height } = canvasEl.getBoundingClientRect(); + canvasEl.width = Math.round(width * scale); + canvasEl.height = Math.round(height * scale); + canvasEl.getContext("2d").scale(scale, scale); canvas.draw(true, true); } From 880c9b928befa6a7dda0a22a4569e3bfc56e0ca8 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 11 Jul 2023 02:56:37 -0400 Subject: [PATCH 2/7] Update litegraph to latest. --- .gitignore | 1 - web/lib/litegraph.core.js | 169 ++++++++++++++++---------------------- 2 files changed, 72 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index 21a85d316..0177e1d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,3 @@ venv/ web/extensions/* !web/extensions/logging.js.example !web/extensions/core/ -startup-scripts/ \ No newline at end of file diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index a60848d77..f96321ac0 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -144,6 +144,10 @@ ctrl_shift_v_paste_connect_unselected_outputs: true, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes + // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. + // use this if you must have node IDs that are unique across all graphs and subgraphs. + use_uuids: false, + /** * Register a node class so it can be listed when the user wants to create a new one * @method registerNodeType @@ -603,6 +607,13 @@ return target; }, + /* + * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 + */ + uuidv4: function() { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)); + }, + /** * Returns if the types of two slots are compatible (taking into account wildcards, etc) * @method isValidConnection @@ -1407,7 +1418,12 @@ console.warn( "LiteGraph: there is already a node with this ID, changing it" ); - node.id = ++this.last_node_id; + if (LiteGraph.use_uuids) { + node.id = LiteGraph.uuidv4(); + } + else { + node.id = ++this.last_node_id; + } } if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { @@ -1415,10 +1431,16 @@ } //give him an id - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; + if (LiteGraph.use_uuids) { + if (node.id == null || node.id == -1) + node.id = LiteGraph.uuidv4(); + } + else { + if (node.id == null || node.id == -1) { + node.id = ++this.last_node_id; + } else if (this.last_node_id < node.id) { + this.last_node_id = node.id; + } } node.graph = this; @@ -2415,7 +2437,12 @@ enumerable: true }); - this.id = -1; //not know till not added + if (LiteGraph.use_uuids) { + this.id = LiteGraph.uuidv4(); + } + else { + this.id = -1; //not know till not added + } this.type = null; //inputs available: array of inputs @@ -2629,6 +2656,11 @@ } delete data["id"]; + + if (LiteGraph.use_uuids) { + data["id"] = LiteGraph.uuidv4() + } + //remove links node.configure(data); @@ -3540,8 +3572,8 @@ /** * computes the minimum size of a node according to its inputs and output slots * @method computeSize - * @param {number} minHeight - * @return {number} the total size + * @param {vec2} minHeight + * @return {vec2} the total size */ LGraphNode.prototype.computeSize = function(out) { if (this.constructor.size) { @@ -4279,10 +4311,16 @@ break; } } + + var nextId + if (LiteGraph.use_uuids) + nextId = LiteGraph.uuidv4(); + else + nextId = ++this.graph.last_link_id; //create link class link_info = new LLink( - ++this.graph.last_link_id, + nextId, input.type || output.type, this.id, slot, @@ -6023,6 +6061,9 @@ LGraphNode.prototype.executeAction = function(action) //it wasn't clicked on the links boxes if (!skip_action) { var block_drag_node = false; + if(node && node.flags && node.flags.pinned) { + block_drag_node = true; + } var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets @@ -7070,6 +7111,8 @@ LGraphNode.prototype.executeAction = function(action) var selected_nodes_array = []; for (var i in this.selected_nodes) { var node = this.selected_nodes[i]; + if (node.clonable === false) + continue; node._relative_id = index; selected_nodes_array.push(node); index += 1; @@ -7077,12 +7120,12 @@ LGraphNode.prototype.executeAction = function(action) for (var i = 0; i < selected_nodes_array.length; ++i) { var node = selected_nodes_array[i]; - var cloned = node.clone(); - if(!cloned) - { - console.warn("node type not found: " + node.type ); - continue; - } + var cloned = node.clone(); + if(!cloned) + { + console.warn("node type not found: " + node.type ); + continue; + } clipboard_info.nodes.push(cloned.serialize()); if (node.inputs && node.inputs.length) { for (var j = 0; j < node.inputs.length; ++j) { @@ -9952,7 +9995,7 @@ LGraphNode.prototype.executeAction = function(action) break; case "slider": var old_value = w.value; - var nvalue = Math.clamp((x - 15) / (widget_width - 30), 0, 1); + var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); if(w.options.read_only) break; w.value = w.options.min + (w.options.max - w.options.min) * nvalue; if (old_value != w.value) { @@ -12949,7 +12992,7 @@ LGraphNode.prototype.executeAction = function(action) var newSelected = {}; var fApplyMultiNode = function(node){ - if (node.clonable == false) { + if (node.clonable === false) { return; } var newnode = node.clone(); @@ -13344,82 +13387,6 @@ LGraphNode.prototype.executeAction = function(action) }; //API ************************************************* - //like rect but rounded corners - if (typeof(window) != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) { - window.CanvasRenderingContext2D.prototype.roundRect = function( - x, - y, - w, - h, - radius, - radius_low - ) { - var top_left_radius = 0; - var top_right_radius = 0; - var bottom_left_radius = 0; - var bottom_right_radius = 0; - - if ( radius === 0 ) - { - this.rect(x,y,w,h); - return; - } - - if(radius_low === undefined) - radius_low = radius; - - //make it compatible with official one - if(radius != null && radius.constructor === Array) - { - if(radius.length == 1) - top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0]; - else if(radius.length == 2) - { - top_left_radius = bottom_right_radius = radius[0]; - top_right_radius = bottom_left_radius = radius[1]; - } - else if(radius.length == 4) - { - top_left_radius = radius[0]; - top_right_radius = radius[1]; - bottom_left_radius = radius[2]; - bottom_right_radius = radius[3]; - } - else - return; - } - else //old using numbers - { - top_left_radius = radius || 0; - top_right_radius = radius || 0; - bottom_left_radius = radius_low || 0; - bottom_right_radius = radius_low || 0; - } - - //top right - this.moveTo(x + top_left_radius, y); - this.lineTo(x + w - top_right_radius, y); - this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius); - - //bottom right - this.lineTo(x + w, y + h - bottom_right_radius); - this.quadraticCurveTo( - x + w, - y + h, - x + w - bottom_right_radius, - y + h - ); - - //bottom left - this.lineTo(x + bottom_right_radius, y + h); - this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius); - - //top left - this.lineTo(x, y + bottom_left_radius); - this.quadraticCurveTo(x, y, x + top_left_radius, y); - }; - }//if - function compareObjects(a, b) { for (var i in a) { if (a[i] != b[i]) { @@ -14148,10 +14115,10 @@ LGraphNode.prototype.executeAction = function(action) return; } if( !is_edge_point ) //not edges - point[0] = Math.clamp(x,0,1); + point[0] = clamp(x, 0, 1); else point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - Math.clamp(y,0,1); + point[1] = 1.0 - clamp(y, 0, 1); points.sort(function(a,b){ return a[0] - b[0]; }); this.selected = points.indexOf(point); this.must_update = true; @@ -14299,10 +14266,11 @@ LGraphNode.prototype.executeAction = function(action) return oDOM.removeEventListener(sEvent, fCall, capture); } } - - Math.clamp = function(v, a, b) { + + function clamp(v, a, b) { return a > v ? a : b < v ? b : v; }; + global.clamp = clamp; if (typeof window != "undefined" && !window["requestAnimationFrame"]) { window.requestAnimationFrame = @@ -14316,6 +14284,13 @@ LGraphNode.prototype.executeAction = function(action) if (typeof exports != "undefined") { exports.LiteGraph = this.LiteGraph; + exports.LGraph = this.LGraph; + exports.LLink = this.LLink; + exports.LGraphNode = this.LGraphNode; + exports.LGraphGroup = this.LGraphGroup; + exports.DragAndScale = this.DragAndScale; + exports.LGraphCanvas = this.LGraphCanvas; + exports.ContextMenu = this.ContextMenu; } From 2b2a1474f71222f19ccc05864ec4a645c0fcfb60 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 11 Jul 2023 03:12:00 -0400 Subject: [PATCH 3/7] Move to litegraph. --- web/lib/litegraph.core.js | 2 +- web/scripts/app.js | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index f96321ac0..9a006dfa9 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -8191,7 +8191,7 @@ LGraphNode.prototype.executeAction = function(action) **/ LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { x = x || 10; - y = y || this.canvas.height - 80; + y = y || this.canvas.offsetHeight - 80; ctx.save(); ctx.translate(x, y); diff --git a/web/scripts/app.js b/web/scripts/app.js index d3a05c275..6f238b2c7 100644 --- a/web/scripts/app.js +++ b/web/scripts/app.js @@ -4,18 +4,6 @@ import { api } from "./api.js"; import { defaultGraph } from "./defaultGraph.js"; import { getPngMetadata, importA1111, getLatentMetadata } from "./pnginfo.js"; -// DPI scaling fix, see https://github.com/comfyanonymous/ComfyUI/pull/845 -(function() { - const originalRenderInfo = LGraphCanvas.prototype.renderInfo - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - // Patch renderInfo() to use canvas.offsetHeight instead of canvas.height as bottom viewpoint bound - if (!y) { - y = this.canvas.offsetHeight - 80 - } - return originalRenderInfo.call(this, ctx, x, y) - } -})() - /** * @typedef {import("types/comfy").ComfyExtension} ComfyExtension */ From f4b9390623d968753e30fc89db7e55c697dbc7ba Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Tue, 11 Jul 2023 17:35:55 -0400 Subject: [PATCH 4/7] Add a random string to the temp prefix for PreviewImage. --- nodes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nodes.py b/nodes.py index 72a73e9f2..416969c27 100644 --- a/nodes.py +++ b/nodes.py @@ -7,6 +7,7 @@ import hashlib import traceback import math import time +import random from PIL import Image, ImageOps from PIL.PngImagePlugin import PngInfo @@ -1116,6 +1117,7 @@ class SaveImage: def __init__(self): self.output_dir = folder_paths.get_output_directory() self.type = "output" + self.prefix_append = "" @classmethod def INPUT_TYPES(s): @@ -1133,6 +1135,7 @@ class SaveImage: CATEGORY = "image" def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): + filename_prefix += self.prefix_append full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) results = list() for image in images: @@ -1160,6 +1163,7 @@ class PreviewImage(SaveImage): def __init__(self): self.output_dir = folder_paths.get_temp_directory() self.type = "temp" + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) @classmethod def INPUT_TYPES(s): From 90aa59709985ffa1b02b6330b7720ed399fbf4df Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 12 Jul 2023 02:07:48 -0400 Subject: [PATCH 5/7] Add back roundRect to fix issue on firefox ESR. --- web/lib/litegraph.core.js | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/web/lib/litegraph.core.js b/web/lib/litegraph.core.js index 9a006dfa9..2a33bd4a7 100644 --- a/web/lib/litegraph.core.js +++ b/web/lib/litegraph.core.js @@ -13387,6 +13387,82 @@ LGraphNode.prototype.executeAction = function(action) }; //API ************************************************* + //like rect but rounded corners + if (typeof(window) != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) { + window.CanvasRenderingContext2D.prototype.roundRect = function( + x, + y, + w, + h, + radius, + radius_low + ) { + var top_left_radius = 0; + var top_right_radius = 0; + var bottom_left_radius = 0; + var bottom_right_radius = 0; + + if ( radius === 0 ) + { + this.rect(x,y,w,h); + return; + } + + if(radius_low === undefined) + radius_low = radius; + + //make it compatible with official one + if(radius != null && radius.constructor === Array) + { + if(radius.length == 1) + top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0]; + else if(radius.length == 2) + { + top_left_radius = bottom_right_radius = radius[0]; + top_right_radius = bottom_left_radius = radius[1]; + } + else if(radius.length == 4) + { + top_left_radius = radius[0]; + top_right_radius = radius[1]; + bottom_left_radius = radius[2]; + bottom_right_radius = radius[3]; + } + else + return; + } + else //old using numbers + { + top_left_radius = radius || 0; + top_right_radius = radius || 0; + bottom_left_radius = radius_low || 0; + bottom_right_radius = radius_low || 0; + } + + //top right + this.moveTo(x + top_left_radius, y); + this.lineTo(x + w - top_right_radius, y); + this.quadraticCurveTo(x + w, y, x + w, y + top_right_radius); + + //bottom right + this.lineTo(x + w, y + h - bottom_right_radius); + this.quadraticCurveTo( + x + w, + y + h, + x + w - bottom_right_radius, + y + h + ); + + //bottom left + this.lineTo(x + bottom_right_radius, y + h); + this.quadraticCurveTo(x, y + h, x, y + h - bottom_left_radius); + + //top left + this.lineTo(x, y + bottom_left_radius); + this.quadraticCurveTo(x, y, x + top_left_radius, y); + }; + }//if + function compareObjects(a, b) { for (var i in a) { if (a[i] != b[i]) { From 46dc050c9f5cb1309d411608c2782ab0336f2e9f Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 12 Jul 2023 19:28:48 -0400 Subject: [PATCH 6/7] Fix potential tensors being on different devices issues. --- comfy/sd1_clip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/comfy/sd1_clip.py b/comfy/sd1_clip.py index fbd87c569..c008e963a 100644 --- a/comfy/sd1_clip.py +++ b/comfy/sd1_clip.py @@ -32,7 +32,7 @@ class ClipTokenWeightEncoder: output.append(z) if (len(output) == 0): - return z_empty, first_pooled + return z_empty.cpu(), first_pooled.cpu() return torch.cat(output, dim=-2).cpu(), first_pooled.cpu() class SD1ClipModel(torch.nn.Module, ClipTokenWeightEncoder): @@ -139,7 +139,7 @@ class SD1ClipModel(torch.nn.Module, ClipTokenWeightEncoder): pooled_output = outputs.pooler_output if self.text_projection is not None: - pooled_output = pooled_output @ self.text_projection + pooled_output = pooled_output.to(self.text_projection.device) @ self.text_projection return z.float(), pooled_output.float() def encode(self, tokens): From b2f03164c727d2252ac8509361082ba88481cfb1 Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Wed, 12 Jul 2023 20:15:02 -0400 Subject: [PATCH 7/7] Prevent the clip_g position_ids key from being saved in the checkpoint. This is to make it match the official checkpoint. --- comfy/supported_models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/comfy/supported_models.py b/comfy/supported_models.py index b1beee8c5..b7fdfe9fe 100644 --- a/comfy/supported_models.py +++ b/comfy/supported_models.py @@ -126,6 +126,7 @@ class SDXLRefiner(supported_models_base.BASE): def process_clip_state_dict_for_saving(self, state_dict): replace_prefix = {} state_dict_g = diffusers_convert.convert_text_enc_state_dict_v20(state_dict, "clip_g") + state_dict_g.pop("clip_g.transformer.text_model.embeddings.position_ids") replace_prefix["clip_g"] = "conditioner.embedders.0.model" state_dict_g = supported_models_base.state_dict_prefix_replace(state_dict_g, replace_prefix) return state_dict_g @@ -164,6 +165,7 @@ class SDXL(supported_models_base.BASE): replace_prefix = {} keys_to_replace = {} state_dict_g = diffusers_convert.convert_text_enc_state_dict_v20(state_dict, "clip_g") + state_dict_g.pop("clip_g.transformer.text_model.embeddings.position_ids") for k in state_dict: if k.startswith("clip_l"): state_dict_g[k] = state_dict[k]