diff --git a/README.md b/README.md index c67e9fb..045b177 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,11 @@ This takes latent as input and outputs latent as the result. * Queue Trigger - When this node is executed, it adds a new queue to assist with repetitive tasks. It will only execute if the signal's status changes. * Set Widget Value - This node sets one of the optional inputs to the specified node's widget. An error may occur if the types do not match. * Set Mute State - This node changes the mute state of a specific node. + * Control Bridge - Depending on whether the mode is set to `block` or `pass`, it changes the mute status of connected nodes. If there are nodes that require a change, the current execution is paused, the mute status is updated, and a new prompt queue is inserted. + * **Limitation**: Due to these characteristics, it does not function correctly when the batch count exceeds 1. Additionally, it does not guarantee proper operation when the seed is randomized or when the state of nodes is altered by actions such as `Queue Trigger`, `Set Widget Value`, `Set Mute`, before the Control Bridge. + * When utilizing this node, please structure the workflow in such a way that `Queue Trigger`, `Set Widget Value`, `Set Mute State`, and similar actions are executed at the end of the workflow. + * If you want to change the value of the seed at each iteration, please ensure that Set Widget Value is executed at the end of the workflow instead of using randomization. + * It is not a problem if the seed changes due to randomization as long as it occurs after the Control Bridge section. * You can find the `node_id` by checking through [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) using the format `Badge: #ID Nickname`. ## MMDet nodes diff --git a/__init__.py b/__init__.py index de45ba8..88b50a9 100644 --- a/__init__.py +++ b/__init__.py @@ -258,6 +258,7 @@ NODE_CLASS_MAPPINGS = { "ImpactQueueTrigger": ImpactQueueTrigger, "ImpactSetWidgetValue": ImpactSetWidgetValue, "ImpactNodeSetMuteState": ImpactNodeSetMuteState, + "ImpactControlBridge": ImpactControlBridge, } @@ -348,6 +349,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ImpactQueueTrigger": "Queue Trigger", "ImpactSetWidgetValue": "Set Widget Value", "ImpactNodeSetMuteState": "Set Mute State", + "ImpactControlBridge": "Control Bridge", "LatentSwitch": "Switch (latent/legacy)", "SEGSSwitch": "Switch (SEGS/legacy)" diff --git a/js/common.js b/js/common.js index b0886fb..1b14397 100644 --- a/js/common.js +++ b/js/common.js @@ -1,4 +1,23 @@ import { api } from "../../scripts/api.js"; +import { app } from "../../scripts/app.js"; + +let original_show = app.ui.dialog.show; + +function dialog_show_wrapper(html) { + if (typeof html === "string") { + if(html.includes("IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE")) { + return; + } + + this.textElement.innerHTML = html; + } else { + this.textElement.replaceChildren(html); + } + this.element.style.display = "flex"; +} + +app.ui.dialog.show = dialog_show_wrapper; + function nodeFeedbackHandler(event) { let nodes = app.graph._nodes_by_id; @@ -28,6 +47,30 @@ function setMuteState(event) { api.addEventListener("impact-node-mute-state", setMuteState); +async function bridgeContinue(event) { + let nodes = app.graph._nodes_by_id; + let node = nodes[event.detail.id]; + if(node) { + const mutes = new Set(event.detail.mutes); + const actives = new Set(event.detail.actives); + + for(let i in app.graph._nodes_by_id) { + let this_node = app.graph._nodes_by_id[i]; + if(mutes.has(i)) { + this_node.mode = 2; + } + else if(actives.has(i)) { + this_node.mode = 0; + } + } + + await app.queuePrompt(0, 1); + } +} + +api.addEventListener("impact-bridge-continue", bridgeContinue); + + function addQueue(event) { app.queuePrompt(0, 1); } diff --git a/js/impact-image-util.js b/js/impact-image-util.js index 1a7199e..c51581c 100644 --- a/js/impact-image-util.js +++ b/js/impact-image-util.js @@ -37,13 +37,20 @@ app.registerExtension({ if(node.comfyClass == "PreviewBridge") { let w = node.widgets.find(obj => obj.name === 'image'); Object.defineProperty(w, 'value', { - set(v) { + async set(v) { w._value = v; let image = new Image(); try { let item = getFileItem('temp', v); - image.src = `view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`; + let params = `?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`; + + let res = await api.fetchApi('/view/validate'+params, { cache: "no-store" }); + if(res.status == 200) { + image.src = 'view'+params; + } + else + w._value = undefined; } catch { w._value = undefined; @@ -123,7 +130,12 @@ app.registerExtension({ try { let item = getFileItem('temp', path_widget.value); - image.src = `view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`; + let params = `?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`; + + let res = api.fetchApi('/view/validate'+params, { cache: "no-store" }).then(response => response); + if(res.status == 200) { + image.src = 'view'+params; + } this._img = [new Image()]; // placeholder image.onload = function(v) { diff --git a/modules/impact/config.py b/modules/impact/config.py index a3a072d..08246dd 100644 --- a/modules/impact/config.py +++ b/modules/impact/config.py @@ -2,7 +2,7 @@ import configparser import os -version = "V4.22" +version = "V4.23" dependency_version = 14 diff --git a/modules/impact/impact_server.py b/modules/impact/impact_server.py index 4e93bd6..8d19643 100644 --- a/modules/impact/impact_server.py +++ b/modules/impact/impact_server.py @@ -225,6 +225,23 @@ async def segs_picker(request): return web.Response(status=400) +@server.PromptServer.instance.routes.get("/view/validate") +async def segs_picker(request): + if "filename" in request.rel_url.query: + filename = request.rel_url.query["filename"] + filename, output_dir = folder_paths.annotated_filepath(filename) + + if filename[0] == '/' or '..' in filename: + return web.Response(status=400) + + file = os.path.join(output_dir, filename) + + if os.path.isfile(file): + return web.Response(status=200) + else: + return web.Response(status=400) + + def onprompt_for_switch(json_data): inversed_switch_info = {} onprompt_switch_info = {} diff --git a/modules/impact/logics.py b/modules/impact/logics.py index df1ea54..6e32def 100644 --- a/modules/impact/logics.py +++ b/modules/impact/logics.py @@ -1,6 +1,11 @@ import sys + +import execution +import folder_paths +import impact.impact_server from server import PromptServer from impact.utils import any_typ +import impact.core as core class ImpactCompare: @@ -251,51 +256,46 @@ class ImpactQueueTrigger: @classmethod def INPUT_TYPES(cls): return {"required": { - "signal": (any_typ,), - "mode": ("BOOLEAN", {"default": True, "label_on": "Trigger", "label_off": "Don't trigger"}), - } - } - - FUNCTION = "doit" - - CATEGORY = "ImpactPack/Logic/_for_test" - RETURN_TYPES = () - OUTPUT_NODE = True - - def doit(self, signal, mode): - if(mode): - PromptServer.instance.send_sync("impact-add-queue", {}) - - return {} - - # @classmethod - # def IS_CHANGED(cls, *args): - # # This value will be compared with previous 'IS_CHANGED' outputs - # # If inequal, then this node will be considered as modified - # # NaN is never equal to itself - # return float("NaN") - - -class ImpactSetWidgetValue: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "signal": (any_typ,), - "node_id": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - "widget_name": ("STRING", {"multiline": False}), - }, - "optional": { - "boolean_value": ("BOOLEAN", {"forceInput": True}), - "int_value": ("INT", {"forceInput": True}), - "float_value": ("FLOAT", {"forceInput": True}), - "string_value": ("STRING", {"forceInput": True}), + "signal": (any_typ,), + "mode": ("BOOLEAN", {"default": True, "label_on": "Trigger", "label_off": "Don't trigger"}), } } FUNCTION = "doit" CATEGORY = "ImpactPack/Logic/_for_test" - RETURN_TYPES = () + RETURN_TYPES = (any_typ,) + RETURN_NAMES = ("signal_opt",) + OUTPUT_NODE = True + + def doit(self, signal, mode): + if(mode): + PromptServer.instance.send_sync("impact-add-queue", {}) + + return (signal,) + + +class ImpactSetWidgetValue: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "signal": (any_typ,), + "node_id": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "widget_name": ("STRING", {"multiline": False}), + }, + "optional": { + "boolean_value": ("BOOLEAN", {"forceInput": True}), + "int_value": ("INT", {"forceInput": True}), + "float_value": ("FLOAT", {"forceInput": True}), + "string_value": ("STRING", {"forceInput": True}), + } + } + + FUNCTION = "doit" + + CATEGORY = "ImpactPack/Logic/_for_test" + RETURN_TYPES = (any_typ,) + RETURN_NAMES = ("signal_opt",) OUTPUT_NODE = True def doit(self, signal, node_id, widget_name, boolean_value=None, int_value=None, float_value=None, string_value=None, ): @@ -319,25 +319,108 @@ class ImpactSetWidgetValue: PromptServer.instance.send_sync("impact-node-feedback", {"id": node_id, "widget_name": widget_name, "type": kind, "value": value}) - return {} + return (signal,) class ImpactNodeSetMuteState: @classmethod def INPUT_TYPES(cls): return {"required": { - "signal": (any_typ,), - "node_id": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - "set_state": ("BOOLEAN", {"default": True, "label_on": "active", "label_off": "mute"}), - }} + "signal": (any_typ,), + "node_id": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "set_state": ("BOOLEAN", {"default": True, "label_on": "active", "label_off": "mute"}), + } + } FUNCTION = "doit" CATEGORY = "ImpactPack/Logic/_for_test" - RETURN_TYPES = () + RETURN_TYPES = (any_typ,) + RETURN_NAMES = ("signal_opt",) OUTPUT_NODE = True def doit(self, signal, node_id, set_state): PromptServer.instance.send_sync("impact-node-mute-state", {"id": node_id, "is_active": set_state}) - return {} + return (signal,) + + +error_skip_flag = False +try: + import sys + def filter_message(str): + global error_skip_flag + + if "IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE" in str: + return True + elif error_skip_flag and "ERROR:root:!!! Exception during processing !!!\n" == str: + error_skip_flag = False + return True + else: + return False + + sys.__comfyui_manager_register_message_collapse(filter_message) + +except Exception as e: + print(f"e: {e}") + pass + + +def workflow_to_map(workflow): + nodes = {} + links = {} + for link in workflow['links']: + links[link[0]] = link[1:] + for node in workflow['nodes']: + nodes[str(node['id'])] = node + + return nodes, links + +class ImpactControlBridge: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "value": (any_typ,), + "mode": ("BOOLEAN", {"default": True, "label_on": "pass", "label_off": "block"}), + }, + "hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"} + } + + FUNCTION = "doit" + + CATEGORY = "ImpactPack/Logic/_for_test" + RETURN_TYPES = (any_typ,) + OUTPUT_NODE = True + + def doit(self, value, mode, unique_id, prompt, extra_pnginfo): + global error_skip_flag + + nodes, links = workflow_to_map(extra_pnginfo['workflow']) + + outputs = [str(links[link][2]) for link in nodes[unique_id]['outputs'][0]['links']] + + prompt_set = set(prompt.keys()) + output_set = set(outputs) + + if mode: + should_active_but_muted = output_set - prompt_set + if len(should_active_but_muted) > 0: + PromptServer.instance.send_sync("impact-bridge-continue", {"id": unique_id, 'actives': list(should_active_but_muted)}) + error_skip_flag = True + raise Exception("IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE\nIf you see this message, your ComfyUI-Manager is outdated. Please update it.") + else: + should_muted_but_active = prompt_set.intersection(output_set) + if len(should_muted_but_active) > 0: + PromptServer.instance.send_sync("impact-bridge-continue", {"id": unique_id, 'mutes': list(should_muted_but_active)}) + error_skip_flag = True + raise Exception("IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE\nIf you see this message, your ComfyUI-Manager is outdated. Please update it.") + + return (value, ) + + +original_handle_execution = execution.PromptExecutor.handle_execution_error + + +def handle_execution_error(**kwargs): + print(f" handled") + execution.PromptExecutor.handle_execution_error(**kwargs)