feat: ImpactControlBridge

improve: ImpactQueueTrigger, ImpactSetWidgetValue, ImpactNodeSetMuteState - add signal_opt output
fix: robust PreviewBridge, ImageReceiver
This commit is contained in:
Dr.Lt.Data
2023-10-15 22:25:24 +09:00
parent d4c9145cea
commit 6db61af3f4
7 changed files with 212 additions and 50 deletions
+5
View File
@@ -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
+2
View File
@@ -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)"
+43
View File
@@ -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);
}
+15 -3
View File
@@ -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) {
+1 -1
View File
@@ -2,7 +2,7 @@ import configparser
import os
version = "V4.22"
version = "V4.23"
dependency_version = 14
+17
View File
@@ -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 = {}
+129 -46
View File
@@ -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)