From 37a25c64ddd5db5d4dcb8e81205e6e9510020f79 Mon Sep 17 00:00:00 2001 From: Royce Schultz Date: Tue, 30 Apr 2024 00:02:29 -0600 Subject: [PATCH 01/25] Adds node for sending system notifications. --- py/system_notification.py | 40 +++++++++++++++++++++++++++++++ web/js/systemNotification.js | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 py/system_notification.py create mode 100644 web/js/systemNotification.js diff --git a/py/system_notification.py b/py/system_notification.py new file mode 100644 index 0000000..d20db94 --- /dev/null +++ b/py/system_notification.py @@ -0,0 +1,40 @@ +# Hack: string type that is always equal in not equal comparisons +class AnyType(str): + def __ne__(self, __value: object) -> bool: + return False + + +# Our any instance wants to be a wildcard string +any = AnyType("*") + + +class PlaySound: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "any": (any, {}), + "mode": (["always", "on empty queue"], {}), + }} + + FUNCTION = "nop" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + OUTPUT_NODE = True + RETURN_TYPES = (any,) + + CATEGORY = "utils" + + def IS_CHANGED(self, **kwargs): + return float("NaN") + + def nop(self, any, mode): + return {"ui": {"a": []}, "result": (any,)} + + +NODE_CLASS_MAPPINGS = { + "SystemNotification|pysssss": PlaySound, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "SystemNotification|pysssss": "SystemNotification 🐍", +} diff --git a/web/js/systemNotification.js b/web/js/systemNotification.js new file mode 100644 index 0000000..34499fb --- /dev/null +++ b/web/js/systemNotification.js @@ -0,0 +1,46 @@ +import { app } from "../../../scripts/app.js"; + +const notificationSetup = () => { + if (!("Notification" in window)) { + console.log("This browser does not support notifications."); + alert("This browser does not support notifications."); + return; + } + if (Notification.permission === "denied") { + console.log("Notifications are blocked. Please enable them in your browser settings."); + alert("Notifications are blocked. Please enable them in your browser settings."); + return; + } + if (Notification.permission !== "granted") { + Notification.requestPermission() + } + return true; +} + +app.registerExtension({ + name: "pysssss.SystemNotification", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "SystemNotification|pysssss") { + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = async function () { + onExecuted?.apply(this, arguments); + if (this.widgets[0].value === "on empty queue") { + if (app.ui.lastQueueSize !== 0) { + await new Promise((r) => setTimeout(r, 500)); + } + if (app.ui.lastQueueSize !== 0) { + return; + } + } + if(!notificationSetup()) return; + const notification = new Notification("ComfyUI", { body: "Your notification has triggered." }) + }; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, arguments); + notificationSetup(); + } + } + }, +}); From 16eb5b8fd72a5bab6fdb1b263dbb02944b7a88f7 Mon Sep 17 00:00:00 2001 From: Royce Schultz Date: Tue, 30 Apr 2024 00:33:39 -0600 Subject: [PATCH 02/25] Adds system notifications to README. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3d6037c..c08a505 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,12 @@ Adds various menu items to some nodes for quickly setting up common parts of gra Plays a sound when the node is executed, either after each prompt or only when the queue is empty for queuing multiple prompts. You can customize the sound by replacing the mp3 file `web/extensions/pysssss/CustomScripts/assets\notify.mp3` +## System Notification +![image](https://github.com/pythongosssss/ComfyUI-Custom-Scripts/assets/30354775/993fd783-5cd6-4779-aa97-173bc06cc405) +![image](https://github.com/pythongosssss/ComfyUI-Custom-Scripts/assets/30354775/e45227fb-5714-4f45-b96b-6601902ef6e2) + +Sends a system notification via the browser when the node is executed, either after each prompt or only when the queue is empty for queuing multiple prompts. + ## [WIP] Repeater ![image](https://github.com/pythongosssss/ComfyUI-Custom-Scripts/assets/125205205/ec0dac25-14e4-4d44-b975-52193656709d) Node allows you to either create a list of N repeats of the input node, or create N outputs from the input node. From f86d288d7526a58dbe75c0c6449109bcc0aaf199 Mon Sep 17 00:00:00 2001 From: DrJKL Date: Tue, 30 Apr 2024 15:29:26 -0700 Subject: [PATCH 03/25] Add scroll to navigate to lightbox. --- web/js/common/lightbox.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/js/common/lightbox.js b/web/js/common/lightbox.js index d351acf..ddac228 100644 --- a/web/js/common/lightbox.js +++ b/web/js/common/lightbox.js @@ -62,6 +62,13 @@ export class Lightbox { onclick: (e) => { e.stopImmediatePropagation(); }, + onwheel: (e) => { + if (!(e instanceof WheelEvent)) { + return; + } + const direction = Math.sign(e.deltaY); + this.update(direction); + }, }); } From 7679b57440129520bfc7b0eefb98e6bb7fe9a423 Mon Sep 17 00:00:00 2001 From: "Regis Gaughan, III" Date: Thu, 2 May 2024 21:25:39 -0400 Subject: [PATCH 04/25] Update "AlwaysSnapToGrid" to work with groups. When https://github.com/comfyanonymous/ComfyUI/pull/3393 is applied, this would be required for always snapping groups as well. --- web/js/snapToGrid.js | 48 +++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/web/js/snapToGrid.js b/web/js/snapToGrid.js index 6cdc318..c432a50 100644 --- a/web/js/snapToGrid.js +++ b/web/js/snapToGrid.js @@ -1,7 +1,21 @@ import { app } from "../../../scripts/app.js"; + let setting; const id = "pysssss.SnapToGrid"; + +/** Wraps the provided function call to set/reset shiftDown when setting is enabled. */ +function wrapCallInSettingCheck(fn) { + if (setting?.value) { + const shift = app.shiftDown; + app.shiftDown = true; + const r = fn(); + app.shiftDown = shift; + return r; + } + return fn(); +} + const ext = { name: id, init() { @@ -22,14 +36,7 @@ const ext = { // Override drawNode to draw the drop position const drawNode = LGraphCanvas.prototype.drawNode; LGraphCanvas.prototype.drawNode = function () { - if (setting?.value) { - const shift = app.shiftDown; - app.shiftDown = true; - const r = drawNode.apply(this, arguments); - app.shiftDown = shift; - return r; - } - return drawNode.apply(this, arguments); + wrapCallInSettingCheck(() => drawNode.apply(this, arguments)); }; // Override node added to add a resize handler to force grid alignment @@ -38,18 +45,27 @@ const ext = { const r = onNodeAdded?.apply(this, arguments); const onResize = node.onResize; node.onResize = function () { - if (setting?.value) { - const shift = app.shiftDown; - app.shiftDown = true; - const r = onResize?.apply(this, arguments); - app.shiftDown = shift; - return r; - } - return onResize?.apply(this, arguments); + wrapCallInSettingCheck(() => onResize?.apply(this, arguments)); }; return r; }; + + const groupMove = LGraphGroup.prototype.move; + LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { + wrapCallInSettingCheck(() => groupMove.apply(this, arguments)); + } + + const canvasDrawGroups = LGraphCanvas.prototype.drawGroups; + LGraphCanvas.prototype.drawGroups = function (canvas, ctx) { + wrapCallInSettingCheck(() => canvasDrawGroups.apply(this, arguments)); + } + + const canvasOnGroupAdd = LGraphCanvas.onGroupAdd; + LGraphCanvas.onGroupAdd = function() { + wrapCallInSettingCheck(() => canvasOnGroupAdd.apply(this, arguments)); + } + return configure.apply(this, arguments); }; }, From 0bfab12bec430cfd399d55fef9f7c30177e2ff73 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 9 May 2024 03:58:22 +0200 Subject: [PATCH 05/25] Make text inputs optional --- py/string_function.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/string_function.py b/py/string_function.py index cdf9a00..b315039 100644 --- a/py/string_function.py +++ b/py/string_function.py @@ -7,10 +7,10 @@ class StringFunction: "required": { "action": (["append", "replace"], {}), "tidy_tags": (["yes", "no"], {}), - "text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}), }, "optional": { + "text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}), + "text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}), "text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}) } } @@ -20,7 +20,7 @@ class StringFunction: CATEGORY = "utils" OUTPUT_NODE = True - def exec(self, action, tidy_tags, text_a, text_b, text_c=""): + def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""): # Converted inputs are sent as the string of 'undefined' if not connected if text_a == "undefined": text_a = "" From 78814e1af0dac02a03cca6dea5ede6bf08c31eb0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 9 May 2024 03:59:09 +0200 Subject: [PATCH 06/25] remove empty input workaround --- py/string_function.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/py/string_function.py b/py/string_function.py index b315039..3f054b7 100644 --- a/py/string_function.py +++ b/py/string_function.py @@ -20,15 +20,7 @@ class StringFunction: CATEGORY = "utils" OUTPUT_NODE = True - def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""): - # Converted inputs are sent as the string of 'undefined' if not connected - if text_a == "undefined": - text_a = "" - if text_b == "undefined": - text_b = "" - if text_c == "undefined": - text_c = "" - + def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""): tidy_tags = tidy_tags == "yes" out = "" if action == "append": From 09d3e5add3922fd5411564d829e69c0fa24548b0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 9 May 2024 04:07:02 +0200 Subject: [PATCH 07/25] use regex for tidy_tags --- py/string_function.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/py/string_function.py b/py/string_function.py index 3f054b7..f502ba3 100644 --- a/py/string_function.py +++ b/py/string_function.py @@ -34,7 +34,10 @@ class StringFunction: else: out = text_a.replace(text_b, text_c) if tidy_tags: - out = out.replace(" ", " ").replace(" ,", ",").replace(",,", ",").replace(",,", ",") + out = re.sub(r"\s{2,}", " ", out) + out = out.replace(" ,", ",") + out = re.sub(r",{2,}", ",", out) + out = out.strip() return {"ui": {"text": (out,)}, "result": (out,)} NODE_CLASS_MAPPINGS = { From ebd7eb74e6624528b9d17f1bfe3c720bdaef4efd Mon Sep 17 00:00:00 2001 From: DrJKL Date: Thu, 16 May 2024 03:13:17 -0700 Subject: [PATCH 08/25] This got lost in https://github.com/pythongosssss/https://github.com/pythongosssss/ComfyUI-Custom-Scripts/pull/204/files-Custom-Scripts/pull/204 --- web/js/imageFeed.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index 22dc323..f32bd8d 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -405,6 +405,8 @@ Recommended: "enabled (max performance)" uness images are erroneously deduplicat ), ]) ); + // If lightbox is open, update it with new image + lightbox.updateWithNewImage(href, feedDirection.value); } imageFeed.append( From a03516739d1429cdc19893c3e9604351263779cb Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 20:43:14 +0100 Subject: [PATCH 09/25] Dont update if ctrl modifier is set (zooming) --- web/js/common/lightbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/common/lightbox.js b/web/js/common/lightbox.js index ddac228..5c90790 100644 --- a/web/js/common/lightbox.js +++ b/web/js/common/lightbox.js @@ -63,7 +63,7 @@ export class Lightbox { e.stopImmediatePropagation(); }, onwheel: (e) => { - if (!(e instanceof WheelEvent)) { + if (!(e instanceof WheelEvent) || e.ctrlKey) { return; } const direction = Math.sign(e.deltaY); From ca753b988f006f7daf19ff48dfef3646be460845 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 20:50:31 +0100 Subject: [PATCH 10/25] Allow custom message --- py/system_notification.py | 7 ++-- web/js/systemNotification.js | 81 +++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/py/system_notification.py b/py/system_notification.py index d20db94..e9a29c9 100644 --- a/py/system_notification.py +++ b/py/system_notification.py @@ -8,10 +8,11 @@ class AnyType(str): any = AnyType("*") -class PlaySound: +class SystemNotification: @classmethod def INPUT_TYPES(s): return {"required": { + "message": ("STRING", {"default": "Your notification has triggered."}), "any": (any, {}), "mode": (["always", "on empty queue"], {}), }} @@ -27,12 +28,12 @@ class PlaySound: def IS_CHANGED(self, **kwargs): return float("NaN") - def nop(self, any, mode): + def nop(self, any, **kwargs): return {"ui": {"a": []}, "result": (any,)} NODE_CLASS_MAPPINGS = { - "SystemNotification|pysssss": PlaySound, + "SystemNotification|pysssss": SystemNotification, } NODE_DISPLAY_NAME_MAPPINGS = { diff --git a/web/js/systemNotification.js b/web/js/systemNotification.js index 34499fb..2cc93ba 100644 --- a/web/js/systemNotification.js +++ b/web/js/systemNotification.js @@ -1,46 +1,49 @@ import { app } from "../../../scripts/app.js"; const notificationSetup = () => { - if (!("Notification" in window)) { - console.log("This browser does not support notifications."); - alert("This browser does not support notifications."); - return; - } - if (Notification.permission === "denied") { - console.log("Notifications are blocked. Please enable them in your browser settings."); - alert("Notifications are blocked. Please enable them in your browser settings."); - return; - } - if (Notification.permission !== "granted") { - Notification.requestPermission() - } - return true; -} + if (!("Notification" in window)) { + console.log("This browser does not support notifications."); + alert("This browser does not support notifications."); + return; + } + if (Notification.permission === "denied") { + console.log("Notifications are blocked. Please enable them in your browser settings."); + alert("Notifications are blocked. Please enable them in your browser settings."); + return; + } + if (Notification.permission !== "granted") { + Notification.requestPermission(); + } + return true; +}; app.registerExtension({ - name: "pysssss.SystemNotification", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (nodeData.name === "SystemNotification|pysssss") { - const onExecuted = nodeType.prototype.onExecuted; - nodeType.prototype.onExecuted = async function () { - onExecuted?.apply(this, arguments); - if (this.widgets[0].value === "on empty queue") { - if (app.ui.lastQueueSize !== 0) { - await new Promise((r) => setTimeout(r, 500)); - } - if (app.ui.lastQueueSize !== 0) { - return; - } - } - if(!notificationSetup()) return; - const notification = new Notification("ComfyUI", { body: "Your notification has triggered." }) - }; + name: "pysssss.SystemNotification", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "SystemNotification|pysssss") { + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = async function () { + onExecuted?.apply(this, arguments); + const mode = this.widgets.find((w) => w.name === "mode"); + const message = this.widgets.find((w) => w.name === "message"); - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - onNodeCreated?.apply(this, arguments); - notificationSetup(); - } - } - }, + if (mode.value === "on empty queue") { + if (app.ui.lastQueueSize !== 0) { + await new Promise((r) => setTimeout(r, 500)); + } + if (app.ui.lastQueueSize !== 0) { + return; + } + } + if (!notificationSetup()) return; + const notification = new Notification("ComfyUI", { body: message.value ?? "Your notification has triggered." }); + }; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, arguments); + notificationSetup(); + }; + } + }, }); From 6eb5c74b04d48cfff81d0ce34c811b5671b00f4c Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 20:58:30 +0100 Subject: [PATCH 11/25] Make output node --- py/text_files.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/text_files.py b/py/text_files.py index 9a249ca..253de9b 100644 --- a/py/text_files.py +++ b/py/text_files.py @@ -132,6 +132,8 @@ class LoadText(TextFileNode): class SaveText(TextFileNode): + OUTPUT_NODE = True + @classmethod def IS_CHANGED(self, **kwargs): return float("nan") From c07fb55a86df3625ed6514acdb89b971ebe8a351 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 21:00:04 +0100 Subject: [PATCH 12/25] Allow preview.jpeg --- py/better_combos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/better_combos.py b/py/better_combos.py index a7ed34e..c3f1c36 100644 --- a/py/better_combos.py +++ b/py/better_combos.py @@ -85,7 +85,7 @@ def populate_items(names, type): file_path_no_ext = os.path.splitext(file_path)[0] - for ext in ["png", "jpg", "jpeg", "preview.png"]: + for ext in ["png", "jpg", "jpeg", "preview.png", "preview.jpeg"]: has_image = os.path.isfile(file_path_no_ext + "." + ext) if has_image: item_image = f"{file_name}.{ext}" From 4244e2cd033a39503b3d3f74aabb7549fe66933d Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 21:33:51 +0100 Subject: [PATCH 13/25] Add sqrt Add int Add bitwise ops Fix treating everything as floats --- py/math_expression.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/py/math_expression.py b/py/math_expression.py index 385eed2..08eebe9 100644 --- a/py/math_expression.py +++ b/py/math_expression.py @@ -12,7 +12,11 @@ operators = { ast.Pow: op.pow, ast.BitXor: op.xor, ast.USub: op.neg, - ast.Mod: op.mod + ast.Mod: op.mod, + ast.BitAnd: op.and_, + ast.BitOr: op.or_, + ast.Invert: op.invert, + ast.And: op.and_, } # TODO: restructure args to provide more info, generate hint based on args to save duplication @@ -52,6 +56,16 @@ functions = { "call": lambda *args: random.choice(args), "hint": "...numbers" }, + "sqrt": { + "args": (1, 1), + "call": lambda a: math.sqrt(a), + "hint": "number" + }, + "int": { + "args": (1, 1), + "call": lambda a = None: int(a), + "hint": "number" + }, } autocompleteWords = list({ @@ -140,7 +154,11 @@ class MathExpression: if isinstance(node, ast.Num): return node.n elif isinstance(node, ast.BinOp): - return operators[type(node.op)](float(eval_expr(node.left)), float(eval_expr(node.right))) + l = eval_expr(node.left) + r = eval_expr(node.right) + l = l if isinstance(l, int) else float(l) + r = r if isinstance(r, int) else float(r) + return operators[type(node.op)](l, r) elif isinstance(node, ast.UnaryOp): return operators[type(node.op)](eval_expr(node.operand)) elif isinstance(node, ast.Attribute): From 8a77dcae71e95e70bb1c50241fa6ccce70ed943f Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Thu, 16 May 2024 21:57:33 +0100 Subject: [PATCH 14/25] Add `and` `or` `not` Add `iif(v, t, f)` Add `==` `!=` `<` `<=` `>` `>=` --- py/math_expression.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/py/math_expression.py b/py/math_expression.py index 08eebe9..95dfdec 100644 --- a/py/math_expression.py +++ b/py/math_expression.py @@ -16,7 +16,9 @@ operators = { ast.BitAnd: op.and_, ast.BitOr: op.or_, ast.Invert: op.invert, - ast.And: op.and_, + ast.And: lambda a, b: 1 if a and b else 0, + ast.Or: lambda a, b: 1 if a or b else 0, + ast.Not: lambda a: 0 if a else 1, } # TODO: restructure args to provide more info, generate hint based on args to save duplication @@ -66,6 +68,11 @@ functions = { "call": lambda a = None: int(a), "hint": "number" }, + "iif": { + "args": (3, 3), + "call": lambda a, b, c = None: b if a else c, + "hint": "value, truepart, falsepart" + }, } autocompleteWords = list({ @@ -150,15 +157,20 @@ class MathExpression: lookup = {"a": a, "b": b, "c": c} + def eval_op(l, r): + l = eval_expr(l) + r = eval_expr(r) + l = l if isinstance(l, int) else float(l) + r = r if isinstance(r, int) else float(r) + return operators[type(node.op)](l, r) + def eval_expr(node): - if isinstance(node, ast.Num): + if isinstance(node, ast.Constant) or isinstance(node, ast.Num): return node.n elif isinstance(node, ast.BinOp): - l = eval_expr(node.left) - r = eval_expr(node.right) - l = l if isinstance(l, int) else float(l) - r = r if isinstance(r, int) else float(r) - return operators[type(node.op)](l, r) + return eval_op(node.left, node.right) + elif isinstance(node, ast.BoolOp): + return eval_op(node.values[0], node.values[1]) elif isinstance(node, ast.UnaryOp): return operators[type(node.op)](eval_expr(node.operand)) elif isinstance(node, ast.Attribute): @@ -192,6 +204,23 @@ class MathExpression: args.append(eval_expr(arg)) return fn["call"](*args) raise NameError(f"Invalid function call: {node.func.id}") + elif isinstance(node, ast.Compare): + l = eval_expr(node.left) + r = eval_expr(node.comparators[0]) + if isinstance(node.ops[0], ast.Eq): + return 1 if l == r else 0 + if isinstance(node.ops[0], ast.NotEq): + return 1 if l != r else 0 + if isinstance(node.ops[0], ast.Gt): + return 1 if l > r else 0 + if isinstance(node.ops[0], ast.GtE): + return 1 if l >= r else 0 + if isinstance(node.ops[0], ast.Lt): + return 1 if l < r else 0 + if isinstance(node.ops[0], ast.LtE): + return 1 if l <= r else 0 + raise NotImplementedError( + "Operator " + node.ops[0].__class__.__name__ + " not supported.") else: raise TypeError(node) From b20a66dd4bd4612fa9d4d9a10a458a06837e7ca9 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Fri, 17 May 2024 08:02:13 +0100 Subject: [PATCH 15/25] Fix missing node arg in eval op --- py/math_expression.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/math_expression.py b/py/math_expression.py index 95dfdec..eb447a5 100644 --- a/py/math_expression.py +++ b/py/math_expression.py @@ -157,7 +157,7 @@ class MathExpression: lookup = {"a": a, "b": b, "c": c} - def eval_op(l, r): + def eval_op(node, l, r): l = eval_expr(l) r = eval_expr(r) l = l if isinstance(l, int) else float(l) @@ -168,9 +168,9 @@ class MathExpression: if isinstance(node, ast.Constant) or isinstance(node, ast.Num): return node.n elif isinstance(node, ast.BinOp): - return eval_op(node.left, node.right) + return eval_op(node, node.left, node.right) elif isinstance(node, ast.BoolOp): - return eval_op(node.values[0], node.values[1]) + return eval_op(node, node.values[0], node.values[1]) elif isinstance(node, ast.UnaryOp): return operators[type(node.op)](eval_expr(node.operand)) elif isinstance(node, ast.Attribute): From 8ac769973e5104b7aacf52f0a67d873fef8ca003 Mon Sep 17 00:00:00 2001 From: haohaocreates Date: Mon, 20 May 2024 19:33:29 +0000 Subject: [PATCH 16/25] chore(publish): $'Add Github Action for Publishing to Comfy Registry' --- .github/workflows/publish.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..828f300 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,21 @@ +name: Publish to Comfy registry +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "pyproject.toml" + +jobs: + publish-node: + name: Publish Custom Node to registry + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Publish Custom Node + uses: Comfy-Org/publish-node-action@main + with: + ## Add your own personal access token to your Github Repository secrets and reference it here. + personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} \ No newline at end of file From bd30cec68d6583bedf62ef9e32947dbf74bea0ac Mon Sep 17 00:00:00 2001 From: null Date: Mon, 20 May 2024 19:33:30 +0000 Subject: [PATCH 17/25] chore(pyproject): $'Add pyproject.toml for Custom Node Registry' --- pyproject.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..70b05c5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "comfyui-custom-scripts" +description = "" +version = "1.0.0" +license = "LICENSE" + +[project.urls] +Repository = "https://github.com/pythongosssss/ComfyUI-Custom-Scripts" + +[tool.comfy] +PublisherId = "" +DisplayName = "ComfyUI-Custom-Scripts" +Icon = "" +Models = [{location = "/checkpoints/model.safetensor", model_url = "https://example.com/model.zip"}] From a56bf4be063be3e9b31c60c23863221c72f55118 Mon Sep 17 00:00:00 2001 From: haohaocreates <162922985+haohaocreates@users.noreply.github.com> Date: Mon, 20 May 2024 21:25:01 -0400 Subject: [PATCH 18/25] Update pyproject.toml desc --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70b05c5..10cd84d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "comfyui-custom-scripts" -description = "" +description = "This extension provides: Auto Arrange Graph, Workflow SVG, Favicon Status, Image Feed, Latent Upscale By, Lock Nodes & Groups, Lora Subfolders, Preset Text, Show Text, Touch Support, Link Render Mode, Locking, Node Finder, Quick Nodes, Show Image On Menu, Show Text, Workflow Managements, Custom Widget Default Values" version = "1.0.0" license = "LICENSE" @@ -11,4 +11,3 @@ Repository = "https://github.com/pythongosssss/ComfyUI-Custom-Scripts" PublisherId = "" DisplayName = "ComfyUI-Custom-Scripts" Icon = "" -Models = [{location = "/checkpoints/model.safetensor", model_url = "https://example.com/model.zip"}] From 8851e934f9863068f0cb3e5dc54ccf70d0ca774e Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Tue, 21 May 2024 20:04:46 +0100 Subject: [PATCH 19/25] Update pyproject.toml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 10cd84d..5d4ba94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "comfyui-custom-scripts" -description = "This extension provides: Auto Arrange Graph, Workflow SVG, Favicon Status, Image Feed, Latent Upscale By, Lock Nodes & Groups, Lora Subfolders, Preset Text, Show Text, Touch Support, Link Render Mode, Locking, Node Finder, Quick Nodes, Show Image On Menu, Show Text, Workflow Managements, Custom Widget Default Values" +description = "Enhancements & experiments for ComfyUI, mostly focusing on UI features" version = "1.0.0" license = "LICENSE" @@ -8,6 +8,6 @@ license = "LICENSE" Repository = "https://github.com/pythongosssss/ComfyUI-Custom-Scripts" [tool.comfy] -PublisherId = "" +PublisherId = "pythongosssss" DisplayName = "ComfyUI-Custom-Scripts" Icon = "" From 0a59db3de84db935f927abb03c16870cdeb138f2 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Mon, 27 May 2024 10:18:30 +0100 Subject: [PATCH 20/25] Change to just highlight and log --- web/js/presetText.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/web/js/presetText.js b/web/js/presetText.js index ecb072a..51b9066 100644 --- a/web/js/presetText.js +++ b/web/js/presetText.js @@ -5,6 +5,7 @@ import { app } from "../../../scripts/app.js"; let replaceRegex; const id = "pysssss.PresetText.Presets"; +const MISSING = Symbol(); const getPresets = () => { let items; @@ -47,6 +48,19 @@ app.registerExtension({ } }, }); + + const drawNodeWidgets = LGraphCanvas.prototype.drawNodeWidgets + LGraphCanvas.prototype.drawNodeWidgets = function(node) { + const c = LiteGraph.WIDGET_BGCOLOR; + try { + if(node[MISSING]) { + LiteGraph.WIDGET_BGCOLOR = "red" + } + return drawNodeWidgets.apply(this, arguments); + } finally { + LiteGraph.WIDGET_BGCOLOR = c; + } + } }, registerCustomNodes() { class PresetTextNode { @@ -153,10 +167,12 @@ app.registerExtension({ if (!w) continue; const preset = presets.find((p) => p.name === widget.value); if (!preset) { + this[MISSING] = true; + app.graph.setDirtyCanvas(true, true); const msg = `Preset text '${widget.value}' not found. Please fix this and queue again.`; - alert(msg); throw new Error(msg); } + delete this[MISSING]; w.value = preset.value; } } From 24e79cf46349c9e66469afadfd12e3de4c8c94f9 Mon Sep 17 00:00:00 2001 From: Sida Liu Date: Sun, 2 Jun 2024 00:06:04 +0800 Subject: [PATCH 21/25] save and restore the transformation matrix --- web/js/workflowImage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/js/workflowImage.js b/web/js/workflowImage.js index 89b0ef7..e9869e4 100644 --- a/web/js/workflowImage.js +++ b/web/js/workflowImage.js @@ -37,6 +37,7 @@ class WorkflowImage { width: app.canvas.canvas.width, height: app.canvas.canvas.height, offset: app.canvas.ds.offset, + transform: app.canvas.canvas.getContext('2d').getTransform(), // Save the original transformation matrix }; } @@ -45,6 +46,7 @@ class WorkflowImage { app.canvas.canvas.width = this.state.width; app.canvas.canvas.height = this.state.height; app.canvas.ds.offset = this.state.offset; + app.canvas.canvas.getContext('2d').setTransform(this.state.transform); // Reapply the original transformation matrix } updateView(bounds) { From 746fc773a5fdeb57d14d475dbc648440e78ce05d Mon Sep 17 00:00:00 2001 From: Jin Liu Date: Mon, 10 Jun 2024 19:48:10 +0800 Subject: [PATCH 22/25] fix: fetch the image type only if there's a possibility of a video type. --- web/js/common/modelInfoDialog.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/js/common/modelInfoDialog.js b/web/js/common/modelInfoDialog.js index 9a8c989..8655867 100644 --- a/web/js/common/modelInfoDialog.js +++ b/web/js/common/modelInfoDialog.js @@ -249,8 +249,9 @@ export class ModelInfoDialog extends ComfyDialog { }) ); - if (info.images?.length) { - this.img.src = info.images[0].url; + const preview = info.images.find((i) => i.type === "image"); + if (preview) { + this.img.src = preview.url; this.img.style.display = ""; this.imgSave = $el("button", { From 9cbf34074393be3d6f96c37a769c71875afaf824 Mon Sep 17 00:00:00 2001 From: webspam0 <173999334+webspam0@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:59:11 +1000 Subject: [PATCH 23/25] Fix autocomplete opening on unrelated keypresses --- web/js/common/autocomplete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/common/autocomplete.js b/web/js/common/autocomplete.js index cb25b83..d1c17fe 100644 --- a/web/js/common/autocomplete.js +++ b/web/js/common/autocomplete.js @@ -471,7 +471,7 @@ export class TextAreaAutoComplete { this.#hide(); break; } - } else if (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight") { + } else if (e.key.length > 1 && e.key != "Delete" && e.key != "Backspace") { return; } if (!e.defaultPrevented) { From 32b86b86b757891dd0d639c9c31e15ad4a803c80 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 29 Jun 2024 21:47:19 +0100 Subject: [PATCH 24/25] Better support for new menu on left/right --- web/js/imageFeed.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/js/imageFeed.js b/web/js/imageFeed.js index f32bd8d..c13d666 100644 --- a/web/js/imageFeed.js +++ b/web/js/imageFeed.js @@ -37,6 +37,9 @@ $el("style", { min-width: 200px; max-width: calc(var(--max-size, 10) * 1vw); } + .comfyui-body-left .pysssss-image-feed--left, .comfyui-body-right .pysssss-image-feed--right { + height: 100%; + } .pysssss-image-feed--left { left: 0; } From 6b9d196ceb498f56f64506610064a1f3e24f41d9 Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Sat, 13 Jul 2024 16:41:30 +0100 Subject: [PATCH 25/25] =?UTF-8?q?busy=F0=9F=90=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c08a505..f154ae3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ComfyUI-Custom-Scripts +### ⚠️ While these extensions work for the most part, i'm very busy at the moment and so unable to keep on top of everything here, thanks for your patience! + # Installation 1. Clone the repository: