feat: ImpactControlBridge
improve: ImpactQueueTrigger, ImpactSetWidgetValue, ImpactNodeSetMuteState - add signal_opt output fix: robust PreviewBridge, ImageReceiver
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import configparser
|
||||
import os
|
||||
|
||||
|
||||
version = "V4.22"
|
||||
version = "V4.23"
|
||||
|
||||
dependency_version = 14
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user