@@ -0,0 +1,5 @@
|
||||
{
|
||||
"python-envs.defaultEnvManager": "ms-python.python:conda",
|
||||
"python-envs.defaultPackageManager": "ms-python.python:conda",
|
||||
"python-envs.pythonProjects": []
|
||||
}
|
||||
@@ -31,11 +31,30 @@ This is what the default wan 2.2 s2v video workflow looks like:
|
||||
|
||||
---
|
||||
|
||||
# 7.x Updates
|
||||
|
||||
## 7.2
|
||||
|
||||
- Added support for [Combo inputs](#combo-clone) via the `Combo Clone` helper node
|
||||
- Fixed a number of issues related to subgraphs
|
||||
- Improved handling of disconnect
|
||||
- Added new options for handling multiple inputs of same type
|
||||
- Added internationalisation options
|
||||
|
||||
## 7.1
|
||||
|
||||
- Allowed individual inputs to block UE connections
|
||||
- Added support for multiple inputs of the same type
|
||||
- Fixed a number of minor subgraph issues
|
||||
- Fixed a serious bug with UE on Safari [details](https://github.com/chrisgoringe/cg-use-everywhere/issues/359)
|
||||
|
||||
---
|
||||
|
||||
# Anything Everywhere v7
|
||||
|
||||
Version 7 is a major update to the Anything Everywhere nodes, so the documentation below is all new. If you are looking for the old docs, you can find them [here](https://github.com/chrisgoringe/cg-use-everywhere/README-old).
|
||||
|
||||
If you are new to Anything Everywhere, skip to [Anything Everywhere]().
|
||||
If you are new to Anything Everywhere, skip to [Anything Everywhere](#anything-everywhere).
|
||||
|
||||
## Major changes
|
||||
|
||||
@@ -114,6 +133,8 @@ so you can match `seed` to `seed` or `noise_seed` by naming the `Anything Everyw
|
||||
|
||||
You can rename input slots by right-clicking on the input dot - but you can't rename widget inputs - this is a limitation imposed by ComfyUI ([discussion](https://github.com/Comfy-Org/ComfyUI_frontend/issues/3654)). The work-around is to rename the target node (or use multiple `Anything Everywhere` nodes with other constraints, especially color matching).
|
||||
|
||||
`String to Combos` (default `no`) can be used to allow a `STRING` input to be sent to a `COMBO` widget. Since there may be a lot of combo widgets, this should be used with care - you will almost certainly want to use other restrictions (such as an `input regex`, or the `Repeated Types` constraint withj multiple strings (eg `sampler_name` and `scheduler`)). *No validation takes place* to ensure that the string sent is one of the combo options!
|
||||
|
||||
## Resolving clashes
|
||||
|
||||
What if two or more `Anything Everywhere` nodes can send to the same input?
|
||||
@@ -155,11 +176,41 @@ The top set, `Graphics`, modify the visual appearance only.
|
||||
|
||||
The bottom set, `Options`, modify behaviour:
|
||||
|
||||
- When connecting, use the output slot's name as the input name. When a new connection is made to a UE node, the default is to name the input with the type. Select this option to use the output name of the node the link is from.
|
||||
- Block workflow validation. This prevents other nodes from complaining about the lack of connections, or creating them. If you turn this off, there may be unexpected consequences.
|
||||
- Logging. Increase the logging level if you are asked to help debug.
|
||||
- Check loops before submitting will attempt to check for loops in the workflow created by Use Everywhere links. Comfy is much better at handling this now, so I would recommend leaving it off.
|
||||
- Connect to bypassed nodes. When off, Use Everywhere will not connect to a bypassed node, and will attempt to work out whether an input is connected when upstream nodes are bypassed. I recommend turning this on.
|
||||
|
||||
---
|
||||
|
||||
# Primitives and COMBOs and the like
|
||||
|
||||

|
||||
|
||||
UE nodes work with the primitives added in more recent versions of Comfy (in the `primitive` submenu),
|
||||
but not the old-style `Primitive`, or reroute nodes.
|
||||
|
||||
It is very unlikely this will ever change, as it relates to some issues deep within Comfy.
|
||||
|
||||
As of version 7.2, Combo inputs are now supported via a new helper node, `Combo Clone`.
|
||||
|
||||
## Combo Clone
|
||||
|
||||
For each Combo type you want to use, add a Combo Clone node. Here we want to be able to broadcast `sampler_name` and `scheduler`.
|
||||
|
||||

|
||||
|
||||
Connect the `Combo Clone` output to a widget you want it to replicate. The `Combo Clone` copies the options from that widget, and the widget name.
|
||||
|
||||

|
||||
|
||||
Disconnect the `Combo Clone` and connect it to an `Anything Everywhere` node. The name copying means you can connect multiple Combos to a single `Anything Everywhere` node.
|
||||
|
||||

|
||||
|
||||
It can now broadcast to any node with the same input type (but remember you will have to mark the Combo widget as UE Connectable, since widgets are not connectable by default).
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Other features
|
||||
@@ -195,18 +246,6 @@ If you want to share a workflow without UE nodes being required, or to save an A
|
||||
|
||||
This can be done for a single node by right-clicking on it and selecting `Convert to real links`, or for all UE nodes in a workflow by right-clicking the background and selecting `Convert all UEs to real links`.
|
||||
|
||||
---
|
||||
|
||||
# Primitives and COMBOs and the like
|
||||
|
||||

|
||||
|
||||
UE nodes work with the primitives added in more recent versions of Comfy (in the `primitive` submenu), but not the old-style `Primitive`.
|
||||
They also do not work with COMBOs (the data type used for dropdown lists, which are also a type of primitive within Comfy), or reroute nodes.
|
||||
|
||||
It is very unlikely this will ever change, as it relates to some issues deep within Comfy...
|
||||
|
||||
For more on this, see [this discussion](https://github.com/chrisgoringe/cg-use-everywhere/issues/69)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
from .use_everywhere import SeedEverywhere, AnythingEverywherePrompts
|
||||
from typing import Any
|
||||
|
||||
UE_VERSION = "7.1"
|
||||
UE_VERSION = "7.2"
|
||||
|
||||
NODE_CLASS_MAPPINGS:dict[str,Any] = { "Seed Everywhere": SeedEverywhere }
|
||||
|
||||
from .use_everywhere import AnythingEverywhere, AnythingSomewhere, AnythingEverywhereTriplet, SimpleString
|
||||
from .use_everywhere import AnythingEverywhere, AnythingSomewhere, AnythingEverywhereTriplet, SimpleString, ComboClone
|
||||
NODE_CLASS_MAPPINGS["Anything Everywhere"] = AnythingEverywhere
|
||||
NODE_CLASS_MAPPINGS["Anything Everywhere3"] = AnythingEverywhereTriplet
|
||||
NODE_CLASS_MAPPINGS["Anything Everywhere?"] = AnythingSomewhere
|
||||
NODE_CLASS_MAPPINGS["Prompts Everywhere"] = AnythingEverywherePrompts
|
||||
NODE_CLASS_MAPPINGS["Simple String"] = SimpleString
|
||||
NODE_CLASS_MAPPINGS["Combo Clone"] = ComboClone
|
||||
|
||||
import os, shutil
|
||||
import folder_paths
|
||||
|
||||
# temporary code to remove old javascript installs
|
||||
module_js_directory = os.path.join(os.path.dirname(os.path.realpath(__file__)), "js")
|
||||
application_root_directory = os.path.dirname(folder_paths.__file__)
|
||||
old_code_location = os.path.join(application_root_directory, "web", "extensions", "use_everywhere")
|
||||
if os.path.exists(old_code_location):
|
||||
shutil.rmtree(old_code_location)
|
||||
|
||||
old_code_location = os.path.join(application_root_directory, "web", "extensions", "cg-nodes", "use_everywhere.js")
|
||||
if os.path.exists(old_code_location):
|
||||
os.remove(old_code_location)
|
||||
# end of temporary code
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,42 @@
|
||||
import { shared } from "./shared.js"
|
||||
import { Logger } from "./use_everywhere_utilities.js"
|
||||
|
||||
function update_me(node) {
|
||||
if (node.properties.comboclone) {
|
||||
Logger.log_problem(`Reseting combo clone node ${node.id}`)
|
||||
node.widgets[0].options.values = [...node.properties.comboclone.options]
|
||||
node.outputs[0].type = "COMBO"
|
||||
node.outputs[0].label = node.properties.comboclone.name
|
||||
}
|
||||
}
|
||||
|
||||
export function is_combo_clone(node) {
|
||||
return (node.type == "Combo Clone")
|
||||
}
|
||||
|
||||
export function reset_comboclone_on_load(node) {
|
||||
if (is_combo_clone(node)) update_me(node)
|
||||
}
|
||||
|
||||
export function comboclone_on_connection(node, link_info, connect) {
|
||||
if (!is_combo_clone(node)) return Logger.log_problem(`comboclone_on_connection called for node ${node.id} of type ${node.type}`)
|
||||
if (shared.graph_being_configured) return
|
||||
if (connect) {
|
||||
if (!link_info) return
|
||||
const target_node = node.graph.getNodeById(link_info.target_id)
|
||||
const input_name = target_node?.inputs[link_info.target_slot].name
|
||||
const widget = target_node?.widgets?.find((w)=>(w.name==input_name))
|
||||
|
||||
if (widget?.type=="combo" && widget?.options?.values) {
|
||||
node.properties.comboclone = {
|
||||
options : [...widget.options.values],
|
||||
name : input_name
|
||||
}
|
||||
update_me(node)
|
||||
node.widgets[0].value = widget.value
|
||||
link_info.type = "COMBO"
|
||||
}
|
||||
} else {
|
||||
// no action when we disconnect
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { link_is_from_subgraph_input, get_subgraph_input_type, node_graph } from "./use_everywhere_subgraph_utils.js"
|
||||
import { get_real_node, is_UEnode, Logger } from "./use_everywhere_utilities.js";
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { i18n } from "./i18n.js";
|
||||
import { shared } from "./shared.js";
|
||||
import { reset_comboclone_on_load, is_combo_clone } from "./combo_clone.js";
|
||||
|
||||
function get_type(node, link_info) {
|
||||
var type = null
|
||||
if (link_info) {
|
||||
if (link_is_from_subgraph_input(link_info)) { // input slot of subgraph
|
||||
type = get_subgraph_input_type(node_graph(node), link_info.origin_slot)
|
||||
} else {
|
||||
type = get_real_node(link_info.origin_id, node_graph(node))?.outputs[link_info.origin_slot]?.type
|
||||
}
|
||||
if (type != link_info.type && link_info.type != '*') {
|
||||
Logger.log_problem(`Type detected from upstream, ${type} != type on link_info ${link_info.type}`)
|
||||
}
|
||||
} else {
|
||||
Logger.log_problem(`input_changed with connect true, graph_being_configured false, and no link_info`)
|
||||
}
|
||||
type = type || '*'
|
||||
if (type!='*' && link_info) link_info.type = type
|
||||
if (type=='*' && link_info) type = link_info.type
|
||||
return type
|
||||
}
|
||||
|
||||
/*
|
||||
Called by onConnectionsChange for a UE_Node when side == 1 (input).
|
||||
*/
|
||||
|
||||
export function input_changed(node, slot, connect, link_info) {
|
||||
if (shared.graph_being_configured) return
|
||||
|
||||
if (connect) {
|
||||
const type = get_type(node, link_info)
|
||||
if (app.ui.settings.getSettingValue("Use Everywhere.Options.use_output_name") && link_info) {
|
||||
var out_slot
|
||||
if (link_info.origin_id==-10) {
|
||||
out_slot = node.graph.inputNode?.allSlots[link_info.origin_slot]
|
||||
} else {
|
||||
out_slot = node.graph.getNodeById(link_info.origin_id)?.outputs[link_info.origin_slot]
|
||||
}
|
||||
node.inputs[slot].label = out_slot?.label || out_slot?.localized_name || out_slot?.name || i18n(type);
|
||||
} else {
|
||||
node.inputs[slot].label = i18n(type);
|
||||
}
|
||||
node.inputs[slot].color_on = app.canvas.default_connection_color_byType[type];
|
||||
node.inputs[slot].type = type
|
||||
} else {
|
||||
node.inputs[slot].label = i18n('anything');
|
||||
node.inputs[slot].color_on = undefined;
|
||||
node.inputs[slot].type = '*'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function post_configure_fixes(graph) {
|
||||
graph.nodes.forEach((node) => {
|
||||
if (is_combo_clone(node)) reset_comboclone_on_load(node)
|
||||
if (is_UEnode(node)) fix_inputs(node, "post_configure_fixes")
|
||||
if (node.subgraph) post_configure_fixes(node.subgraph)
|
||||
})
|
||||
}
|
||||
|
||||
function add_new_input(node) {
|
||||
Logger.log_info(`Adding new anything input to node ${node.id} (${fix_call_message})`)
|
||||
try {
|
||||
node.properties.ue_properties.next_input_index = (node.properties.ue_properties.next_input_index || 10) + 1
|
||||
node.addInput(`anything${node.properties.ue_properties.next_input_index}`, "*", {label:i18n('anything')})
|
||||
return true
|
||||
} catch (e) { Logger.log_error(e) }
|
||||
return false
|
||||
}
|
||||
|
||||
function remove_excess_input(node) {
|
||||
const idx = node.inputs.findIndex((inputslot)=>(inputslot.type=='*'))
|
||||
if (idx>=0) {
|
||||
try {
|
||||
Logger.log_info(`Removing excess anything input from node ${node.id} (${fix_call_message})`)
|
||||
node.removeInput(idx)
|
||||
return true
|
||||
} catch (e) { Logger.log_error(e) }
|
||||
} else { Logger.log_problem(`Something very odd happened in fix_inputs for ${node.id}`) }
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
This is called in various places (node load, creation, link change) to ensure there is exactly one empty input
|
||||
*/
|
||||
|
||||
var fix_call_message;
|
||||
|
||||
function fix_star_inputs(node) {
|
||||
node.inputs.filter((input)=>(!input.link)).forEach((input)=>{
|
||||
input.type = '*'
|
||||
})
|
||||
node.inputs.filter((input)=>(input.type=='*' && input.link)).forEach((input)=>{
|
||||
const llink = node.graph.links[input.link]
|
||||
if (llink.type) input.type = llink.type
|
||||
})
|
||||
}
|
||||
|
||||
export function fix_inputs(node, message) {
|
||||
fix_call_message = message
|
||||
if (!node.graph) return // node has been deleted prior to the fix
|
||||
if (shared.graph_being_configured) return
|
||||
if (node.properties.ue_properties.fixed_inputs) return
|
||||
|
||||
fix_star_inputs(node)
|
||||
|
||||
const empty_inputs = node.inputs.filter((inputslot)=>(inputslot.type=='*'))
|
||||
var excess_inputs = empty_inputs.length - 1
|
||||
|
||||
if (excess_inputs<0) {
|
||||
if (add_new_input(node)) fix_inputs(node)
|
||||
} else if (excess_inputs>0) {
|
||||
if (remove_excess_input(node)) fix_inputs(node)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,13 +4,9 @@ export var REPEATED_TYPE_OPTIONS;
|
||||
export var GROUP_RESTRICTION_OPTIONS;
|
||||
export var COLOR_RESTRICTION_OPTIONS;
|
||||
|
||||
const _FUNCTIONAL = {
|
||||
'en':{
|
||||
seed_input_regex : "seed|随机种",
|
||||
prompt_regex : "(_|\\b)pos(itive|_|\\b)|^prompt|正面",
|
||||
negative_regex : "(_|\\b)neg(ative|_|\\b)|负面",
|
||||
},
|
||||
}
|
||||
|
||||
var _FUNCTIONAL = null
|
||||
var _FUNCTIONAL_REGEX = null
|
||||
|
||||
const _DISPLAY = {
|
||||
'en' : {
|
||||
@@ -28,16 +24,9 @@ function current_language() {
|
||||
}
|
||||
|
||||
export function language_changed(is_now, was_before) {
|
||||
if (was_before) {
|
||||
app.graph.nodes.filter((node)=>node.IS_UE).forEach((node)=>{
|
||||
// translate node inputs
|
||||
node.inputs?.forEach((input)=>{
|
||||
if (input.label && input.label.startsWith(i18n('anything', was_before))) {
|
||||
input.label = input.label.replace(i18n('anything', {language:was_before}), i18n('anything', {language:is_now}))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
_FUNCTIONAL = null
|
||||
_FUNCTIONAL_REGEX = null
|
||||
|
||||
REPEATED_TYPE_OPTIONS = [
|
||||
i18n("Exact match of input names"),
|
||||
i18n("Match start of input names"),
|
||||
@@ -58,8 +47,33 @@ export function language_changed(is_now, was_before) {
|
||||
]
|
||||
}
|
||||
|
||||
function get_functional() {
|
||||
const nd = LiteGraph.createNode('KSampler')
|
||||
if (nd) {
|
||||
_FUNCTIONAL = {
|
||||
seed_input_regex : `seed|${nd.inputs.find((i)=>(i.name=='positive')).localized_name}`,
|
||||
prompt_regex : `(_|\\b)pos(itive|_|\\b)|${nd.inputs.find((i)=>(i.name=='positive')).localized_name}`,
|
||||
negative_regex : `(_|\\b)neg(ative|_|\\b)|${nd.inputs.find((i)=>(i.name=='negative')).localized_name}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_functional_regex() {
|
||||
if (!_FUNCTIONAL) get_functional()
|
||||
_FUNCTIONAL_REGEX = {
|
||||
prompt_regex : new RegExp(_FUNCTIONAL.prompt_regex),
|
||||
negative_regex : new RegExp(_FUNCTIONAL.negative_regex),
|
||||
}
|
||||
}
|
||||
|
||||
export function i18n_functional(v) {
|
||||
return _FUNCTIONAL[current_language()]?.[v] || _FUNCTIONAL['en'][v]
|
||||
if (!_FUNCTIONAL) get_functional()
|
||||
return _FUNCTIONAL?.[v] || v
|
||||
}
|
||||
|
||||
export function i18n_functional_regex(v) {
|
||||
if (!_FUNCTIONAL_REGEX) get_functional_regex()
|
||||
return _FUNCTIONAL_REGEX[v]
|
||||
}
|
||||
|
||||
const all_requested_stings = new Set()
|
||||
@@ -86,54 +100,3 @@ const toTitleCase = (phrase) => {
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
/*
|
||||
"Version 7.1"
|
||||
"Show links"
|
||||
"All off"
|
||||
"Selected nodes"
|
||||
"Mouseover node"
|
||||
"Selected and mouseover nodes"
|
||||
"All on"
|
||||
"Statically distinguish UE links"
|
||||
"Render UE links, when shown, differently from normal links. Much lower performance cost than animation."
|
||||
"Animate UE links"
|
||||
"Animating links may have a negative impact on UI performance. Consider using Statically distinguish UE links instead."
|
||||
"Off"
|
||||
"Dots"
|
||||
"Pulse"
|
||||
"Both"
|
||||
"Turn animation off when workflow is running"
|
||||
"Highlight connected and connectable inputs"
|
||||
"Save restrictions edit window position"
|
||||
"If off, the edit window appears where the mouse is"
|
||||
"Show restrictions as tooltip"
|
||||
"Connect to bypassed nodes"
|
||||
"By default UE links are made to the node downstream of bypassed nodes."
|
||||
"Check for loops before submitting"
|
||||
"Check to see if UE links have created a loop that wasn't there before"
|
||||
"Logging"
|
||||
"Errors Only"
|
||||
"Problems"
|
||||
"Information"
|
||||
"Detail"
|
||||
"Block workflow validation"
|
||||
"Turn off workflow validation (which tends to replace UE links with real ones)"
|
||||
"Exact match of input names"
|
||||
"Match start of input names"
|
||||
"Match end of input names"
|
||||
"Inputs matches target node name"
|
||||
"No restrictions"
|
||||
"Send only within group"
|
||||
"Send only not within group"
|
||||
"Send only to same color"
|
||||
"Send only to different color"
|
||||
"anything"
|
||||
"title"
|
||||
"input"
|
||||
"group"
|
||||
"Group"
|
||||
"Color"
|
||||
"Repeated Types"
|
||||
"Priority"
|
||||
*/
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
class Shared {
|
||||
graph_being_configured = false
|
||||
}
|
||||
|
||||
export const shared = new Shared()
|
||||
|
||||
export const VERSION = "7.2"
|
||||
@@ -1,5 +1,8 @@
|
||||
import { VERSION, version_at_least, is_UEnode, graphConverter, fix_inputs, create } from "./use_everywhere_utilities.js"
|
||||
import { version_at_least, create } from "./use_everywhere_utilities.js"
|
||||
import { i18n, i18n_functional, GROUP_RESTRICTION_OPTIONS, COLOR_RESTRICTION_OPTIONS } from "./i18n.js";
|
||||
import { shared } from "./shared.js";
|
||||
import { fix_inputs } from "./connections.js";
|
||||
import { VERSION } from "./shared.js";
|
||||
|
||||
const ALL_REGEXES = ['title', 'input', 'prompt', 'negative', 'group']
|
||||
|
||||
@@ -61,6 +64,7 @@ const DEFAULT_PROPERTIES = {
|
||||
group_regex : null,
|
||||
priority : undefined,
|
||||
repeated_type_rule : 0,
|
||||
string_to_combo : 0,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -70,13 +74,11 @@ If the graph is still being configured, then that means the node is being create
|
||||
In that case the properties need to be set later, in setup_ue_properties_onload
|
||||
*/
|
||||
export function setup_ue_properties_oncreate(node) {
|
||||
node.IS_UE = is_UEnode(node)
|
||||
if (graphConverter.graph_being_configured) return
|
||||
if (node.IS_UE) {
|
||||
if (!node.properties) node.properties = {}
|
||||
node.properties.ue_properties = {...DEFAULT_PROPERTIES}
|
||||
convert_node_types(node)
|
||||
}
|
||||
node.IS_UE = true
|
||||
if (shared.graph_being_configured) return
|
||||
if (!node.properties) node.properties = {}
|
||||
node.properties.ue_properties = {...DEFAULT_PROPERTIES}
|
||||
convert_node_types(node)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -138,5 +140,5 @@ function convert_node_types(node) {
|
||||
if (node.properties.ue_properties[rname]==".*") node.properties.ue_properties[rname] = undefined
|
||||
})
|
||||
|
||||
fix_inputs(node)
|
||||
fix_inputs(node, "convert_node_types")
|
||||
}
|
||||
|
||||
@@ -97,6 +97,13 @@ function create_editor_html(node) {
|
||||
add_select_options(node, repeated_type_select, REPEATED_TYPE_OPTIONS, `repeated_type_rule`)
|
||||
}
|
||||
|
||||
if (node.inputs.find((i)=>(i.type=="STRING"))) {
|
||||
const send_to_combos_row = add_row(table, i18n("String to Combos"))
|
||||
const send_to_combos_select = document.createElement('select')
|
||||
add_cell(send_to_combos_row, send_to_combos_select)
|
||||
add_select_options(node, send_to_combos_select, ["no", "yes"], `string_to_combo`)
|
||||
}
|
||||
|
||||
const priority_row = add_row(table, i18n("Priority"))
|
||||
const priority_edit = document.createElement("input")
|
||||
priority_edit.value = `${node.properties.ue_properties.priority || default_priority(node)}`
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
import { is_UEnode, is_helper, inject, Logger, get_real_node, defineProperty, graphConverter, fix_inputs, create } from "./use_everywhere_utilities.js";
|
||||
import { update_input_label, indicate_restriction } from "./use_everywhere_ui.js";
|
||||
import { shared } from "./shared.js";
|
||||
|
||||
import { is_UEnode, inject, Logger, defineProperty, graphConverter, create } from "./use_everywhere_utilities.js";
|
||||
import { indicate_restriction } from "./use_everywhere_ui.js";
|
||||
import { LinkRenderController } from "./use_everywhere_ui.js";
|
||||
import { GraphAnalyser } from "./use_everywhere_graph_analysis.js";
|
||||
import { canvas_menu_settings, SETTINGS, add_extra_menu_items } from "./use_everywhere_settings.js";
|
||||
import { add_debug } from "./ue_debug.js";
|
||||
import { settingsCache } from "./use_everywhere_cache.js";
|
||||
import { convert_to_links } from "./use_everywhere_apply.js";
|
||||
import { get_subgraph_input_type, link_is_from_subgraph_input, node_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
|
||||
import { master_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
|
||||
import { any_restrictions, setup_ue_properties_oncreate, setup_ue_properties_onload } from "./ue_properties.js";
|
||||
import { edit_restrictions } from "./ue_properties_editor.js";
|
||||
import { language_changed } from "./i18n.js";
|
||||
import { input_changed, fix_inputs, post_configure_fixes } from "./connections.js";
|
||||
import { reset_comboclone_on_load, comboclone_on_connection, is_combo_clone } from "./combo_clone.js";
|
||||
|
||||
/*
|
||||
The ui component that looks after the link rendering
|
||||
@@ -58,24 +62,14 @@ app.registerExtension({
|
||||
If it is a UE node, we need to update it as well
|
||||
*/
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (side,slot,connect,link_info,output) {
|
||||
nodeType.prototype.onConnectionsChange = function (side,slot,connect,link_info,output) {
|
||||
if (is_combo_clone(this)) comboclone_on_connection(this, link_info, connect)
|
||||
if (this.IS_UE && side==1) { // side 1 is input
|
||||
var type = '*'
|
||||
if (connect && link_info) {
|
||||
var connected_type
|
||||
if (link_is_from_subgraph_input(link_info)) { // input slot of subgraph
|
||||
connected_type = get_subgraph_input_type(node_graph(this), link_info.origin_slot)
|
||||
} else {
|
||||
connected_type = get_real_node(link_info.origin_id, node_graph(this))?.outputs[link_info.origin_slot]?.type
|
||||
}
|
||||
if (connected_type) type = connected_type;
|
||||
};
|
||||
this.inputs[slot].type = type;
|
||||
if (link_info) link_info.type = type
|
||||
update_input_label(this, slot, app);
|
||||
if (!graphConverter.graph_being_configured) {
|
||||
input_changed(this, slot, connect, link_info)
|
||||
|
||||
if (!shared.graph_being_configured) {
|
||||
// do the fix at the end of graph change
|
||||
deferred_actions.push( { fn:fix_inputs, args:[this,]} )
|
||||
deferred_actions.push( { fn:fix_inputs, args:[this,"deferred onConnectionsChange",]} )
|
||||
// disconnecting doesn't trigger graphChange call?
|
||||
setTimeout(deferred_actions.execute.bind(deferred_actions), 100)
|
||||
}
|
||||
@@ -83,6 +77,17 @@ app.registerExtension({
|
||||
linkRenderController?.mark_link_list_outdated();
|
||||
onConnectionsChange?.apply(this, arguments);
|
||||
};
|
||||
|
||||
/* Combo Clone can connect to COMBO or to UE nodes */
|
||||
if (nodeData.name=="Combo Clone") {
|
||||
const onConnectOutput = nodeType.prototype.onConnectOutput
|
||||
nodeType.prototype.onConnectOutput = function(outputIndex, type, input, inputNode, inputIndex) {
|
||||
if (!(type=="COMBO" || is_UEnode(inputNode))) return false;
|
||||
return onConnectOutput?.apply(this,arguments)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Extra menu options are the node right click menu.
|
||||
@@ -110,15 +115,9 @@ app.registerExtension({
|
||||
},
|
||||
|
||||
async nodeCreated(node) {
|
||||
/* TODO see if we still need this
|
||||
if (!node.__mode) {
|
||||
node.__mode = node.mode
|
||||
defineProperty(node, "mode", {
|
||||
get: ( )=>{return node.__mode},
|
||||
set: (v)=>{node.__mode = v; node.afterChangeMade?.('mode', v);}
|
||||
})
|
||||
}*/
|
||||
|
||||
if (!node.properties.ue_properties && !is_UEnode(node) && !shared.graph_being_configured) {
|
||||
node.properties.ue_properties = { widget_ue_connectable : {}, input_ue_unconnectable : {} }
|
||||
}
|
||||
const original_afterChangeMade = node.afterChangeMade
|
||||
node.afterChangeMade = (p, v) => {
|
||||
original_afterChangeMade?.(p,v)
|
||||
@@ -128,10 +127,6 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
if (is_helper(node)) { // editing a helper node makes the list dirty
|
||||
inject_outdating_into_objects(node.widgets,'callback',`widget callback on ${this.id}`);
|
||||
}
|
||||
|
||||
// removing a node makes the list dirty
|
||||
inject_outdating_into_object_method(node, 'onRemoved', `node ${node.id} removed`)
|
||||
|
||||
@@ -151,8 +146,8 @@ app.registerExtension({
|
||||
graphConverter.convert_if_pre_116(n);
|
||||
})
|
||||
}
|
||||
}
|
||||
setup_ue_properties_onload(node)
|
||||
}
|
||||
setup_ue_properties_onload(node)
|
||||
},
|
||||
|
||||
async setup() {
|
||||
@@ -244,7 +239,7 @@ app.registerExtension({
|
||||
return options;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
Finding a widget by it's name is something done a lot of times in rendering,
|
||||
so add a method that caches the names that can be used deep in the rendering code.
|
||||
|
||||
@@ -368,13 +363,14 @@ app.registerExtension({
|
||||
beforeConfigureGraph() {
|
||||
linkRenderController.pause("before configure", 1000)
|
||||
graphAnalyser.pause("before configure", 1000)
|
||||
graphConverter.graph_being_configured = true
|
||||
shared.graph_being_configured = true
|
||||
},
|
||||
|
||||
afterConfigureGraph() {
|
||||
graphConverter.remove_saved_ue_links_recursively(app.graph)
|
||||
//convert_old_nodes(app.graph)
|
||||
graphConverter.graph_being_configured = false
|
||||
shared.graph_being_configured = false
|
||||
post_configure_fixes(master_graph())
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { i18n_functional } from "./i18n.js";
|
||||
import { i18n_functional, i18n_functional_regex } from "./i18n.js";
|
||||
import { default_priority } from "./ue_properties.js";
|
||||
import { node_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
|
||||
import { nodes_in_my_group, nodes_not_in_my_group, nodes_my_color, nodes_not_my_color, nodes_in_groups_matching } from "./use_everywhere_ui.js";
|
||||
@@ -37,6 +37,7 @@ class UseEverywhere {
|
||||
Object.assign(this, arguments[0]);
|
||||
if (this.priority === undefined) this.priority = 0;
|
||||
if (this.graph === undefined) this.graph = visible_graph();
|
||||
this.string_to_combo = (this.controller.properties.ue_properties.string_to_combo > 0)
|
||||
|
||||
const from_node = get_real_node(this?.output[0], this.graph);
|
||||
const to_node = get_real_node(this?.controller.id, this.graph)
|
||||
@@ -83,7 +84,7 @@ class UseEverywhere {
|
||||
|
||||
if (this.output[0] == node.id) return false;
|
||||
if (this.restrict_to && !this.restrict_to.includes(node.id)) return false;
|
||||
const input_label = input.label ? input.label : input.name;
|
||||
const input_label = input.label || input.localized_name || input.name;
|
||||
const node_label = node.title ? node.title : (node.properties['Node name for S&R'] ? node.properties['Node name for S&R'] : node.type);
|
||||
if (this.title_regex) {
|
||||
if (!(this.title_regex.test(node_label))) return false;
|
||||
@@ -102,7 +103,15 @@ class UseEverywhere {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.type != input.type) return false;
|
||||
if (this.type != input.type) {
|
||||
if (this.type=="STRING" &&
|
||||
input.type=="COMBO" &&
|
||||
this.string_to_combo) {
|
||||
// allow a string to be sent to a COMBO
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (this.input_regex && typeof this.input_regex==='string') return false; // input_regex started '+', which targets Highway nodes only
|
||||
if (this.input_regex && !this.input_regex.test(input_label)) return false;
|
||||
|
||||
@@ -303,6 +312,6 @@ const PROMPT_REGEXES = [new RegExp(i18n_functional('prompt_regex')), new RegExp(
|
||||
function prompt_regex(node, i) {
|
||||
const reg = node.properties.ue_properties[`${P_REGEXES[i]}_regex`]
|
||||
if (reg) return new RegExp(reg)
|
||||
else return PROMPT_REGEXES[i]
|
||||
else return i18n_functional_regex(`${P_REGEXES[i]}_regex`)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UseEverywhereList } from "./use_everywhere_classes.js";
|
||||
import { node_in_loop, node_is_live, is_connected, is_UEnode, Logger, get_real_node, Pausable } from "./use_everywhere_utilities.js";
|
||||
import { node_is_live, is_connected, is_UEnode, Logger, Pausable } from "./use_everywhere_utilities.js";
|
||||
import { convert_to_links } from "./use_everywhere_apply.js";
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { settingsCache } from "./use_everywhere_cache.js";
|
||||
@@ -69,22 +69,6 @@ class GraphAnalyser extends Pausable {
|
||||
return this.analyse_graph(graph);
|
||||
}
|
||||
|
||||
maybe_check_for_loops() {
|
||||
if (settingsCache.getSettingValue('Use Everywhere.Options.checkloops')) {
|
||||
try {
|
||||
node_in_loop(live_nodes, links_added);
|
||||
} catch (e) {
|
||||
if (!e.stack) throw e;
|
||||
if (e.ues && e.ues.length > 0){
|
||||
alert(`Loop (${e.stack}) with broadcast (${e.ues}) - not submitting workflow`);
|
||||
} else {
|
||||
alert(`Loop (${e.stack}) - not submitting workflow`);
|
||||
}
|
||||
throw new Error(`Loop Detected ${e.stack}, ${e.ues}`, {"cause":e});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analyse_graph(graph) {
|
||||
this.ambiguity_messages = [];
|
||||
const treat_bypassed_as_live = settingsCache.getSettingValue("Use Everywhere.Options.connect_to_bypassed") || this.connect_to_bypassed
|
||||
@@ -100,6 +84,7 @@ class GraphAnalyser extends Pausable {
|
||||
if (node && !node.properties.rejects_ue_links) {
|
||||
//if (!real_node._widget_name_map) real_node._widget_name_map = real_node.widgets?.map(w => w.name) || [];
|
||||
node.inputs?.forEach((input,index) => {
|
||||
if (!input) return; // NoteNode has input = [undefined,] !
|
||||
if (is_connected(input, treat_bypassed_as_live, node_graph(node))) return;
|
||||
if (node.reject_ue_connection && node.reject_ue_connection(input)) return;
|
||||
if (is_connectable(node, input.name)) connectable.push({node, input, index});
|
||||
|
||||
@@ -2,12 +2,13 @@ import { app } from "../../scripts/app.js";
|
||||
import { GraphAnalyser } from "./use_everywhere_graph_analysis.js";
|
||||
import { LinkRenderController } from "./use_everywhere_ui.js";
|
||||
import { convert_to_links, remove_all_ues } from "./use_everywhere_apply.js";
|
||||
import { Logger, VERSION } from "./use_everywhere_utilities.js";
|
||||
import { Logger } from "./use_everywhere_utilities.js";
|
||||
import { settingsCache } from "./use_everywhere_cache.js";
|
||||
import { visible_graph } from "./use_everywhere_subgraph_utils.js";
|
||||
import { edit_restrictions } from "./ue_properties_editor.js";
|
||||
import { is_UEnode } from "./use_everywhere_utilities.js";
|
||||
import { i18ify_settings } from "./i18n.js";
|
||||
import { VERSION } from "./shared.js";
|
||||
|
||||
const _SETTINGS = [
|
||||
{
|
||||
@@ -77,14 +78,6 @@ const _SETTINGS = [
|
||||
onChange: settingsCache.onSettingChange,
|
||||
tooltip: "By default UE links are made to the node downstream of bypassed nodes."
|
||||
},
|
||||
{
|
||||
id: "Use Everywhere.Options.checkloops",
|
||||
name: "Check for loops before submitting",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
onChange: settingsCache.onSettingChange,
|
||||
tooltip: "Check to see if UE links have created a loop that wasn't there before"
|
||||
},
|
||||
{
|
||||
id: "Use Everywhere.Options.logging",
|
||||
name: "Logging",
|
||||
@@ -101,6 +94,14 @@ const _SETTINGS = [
|
||||
tooltip: "Turn off workflow validation (which tends to replace UE links with real ones)",
|
||||
onChange: settingsCache.onSettingChange,
|
||||
},
|
||||
{
|
||||
id: "Use Everywhere.Options.use_output_name",
|
||||
name: "When connecting, use the output slot's name as the input name",
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
tooltip: "By default the link type is used as the name",
|
||||
onChange: settingsCache.onSettingChange,
|
||||
},
|
||||
]
|
||||
|
||||
export const SETTINGS = i18ify_settings(_SETTINGS)
|
||||
|
||||
@@ -86,17 +86,6 @@ function indicate_restriction(ctx, title_height) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function update_input_label(node, slot, app) {
|
||||
const type = node.inputs[slot].type
|
||||
if (type) {
|
||||
if (!node.inputs[slot].label || node.inputs[slot].label==i18n('anything')) node.inputs[slot].label = type;
|
||||
node.inputs[slot].color_on = app.canvas.default_connection_color_byType[type];
|
||||
} else {
|
||||
node.inputs[slot].label = i18n('anything');
|
||||
node.inputs[slot].color_on = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class LinkRenderController extends Pausable {
|
||||
static _instance;
|
||||
static instance(tga) {
|
||||
@@ -167,26 +156,35 @@ class LinkRenderController extends Pausable {
|
||||
}
|
||||
|
||||
disable_all_connected_widgets( disable ) {
|
||||
const graph = app.canvas.graph
|
||||
if (disable) {
|
||||
app.graph.extra['ue_links']?.forEach((uel) => {
|
||||
const node = app.graph._nodes_by_id[uel.downstream]
|
||||
graph.extra['ue_links']?.forEach((uel) => {
|
||||
const node = graph._nodes_by_id[uel.downstream]
|
||||
if (node) {
|
||||
try {
|
||||
const name = node.inputs[uel.downstream_slot].name;
|
||||
const widget = node._getWidgetByName(name)
|
||||
if (widget && !widget.disabled) {
|
||||
this.widgets_disabled.push(widget)
|
||||
widget.disabled = true;
|
||||
const name = node.inputs[uel.downstream_slot]?.name;
|
||||
if (name) {
|
||||
const widget = node._getWidgetByName(name)
|
||||
if (widget) {
|
||||
if (!widget.disabled) {
|
||||
this.widgets_disabled.push(widget)
|
||||
widget.disabled = true;
|
||||
}
|
||||
widget.linkedWidgets?.filter((w)=>!w.disabled).forEach((w)=>{
|
||||
this.widgets_disabled.push(w)
|
||||
w.disabled = true;
|
||||
})
|
||||
} else {
|
||||
// it's an input not a widget, so ignore
|
||||
}
|
||||
} else {
|
||||
Logger.log_problem(`In disable_all_connected_widgets, for downstream node ${node.id}, slot ${uel.downstream_slot} did not return a name`)
|
||||
}
|
||||
widget?.linkedWidgets?.filter((w)=>!w.disabled).forEach((w)=>{
|
||||
this.widgets_disabled.push(w)
|
||||
w.disabled = true;
|
||||
})
|
||||
} catch(e) {
|
||||
Logger.log_error(e);
|
||||
}
|
||||
} else {
|
||||
Logger.log_info(`Couldn't find node ${uel.downstream}`)
|
||||
Logger.log_problem(`In disable_all_connected_widgets, couldn't find downstream node ${uel.downstream}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@@ -417,5 +415,5 @@ function modify(c) {
|
||||
|
||||
|
||||
|
||||
export {update_input_label, nodes_in_my_group, nodes_not_in_my_group, nodes_in_groups_matching, nodes_my_color, nodes_not_my_color, indicate_restriction}
|
||||
export {/*update_input_label,*/ nodes_in_my_group, nodes_not_in_my_group, nodes_in_groups_matching, nodes_my_color, nodes_not_my_color, indicate_restriction}
|
||||
export{ LinkRenderController}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { settingsCache } from "./use_everywhere_cache.js";
|
||||
import { link_is_from_subgraph_input, node_graph, visible_graph, wrap_input } from "./use_everywhere_subgraph_utils.js";
|
||||
import { i18n } from "./i18n.js";
|
||||
|
||||
export const VERSION = "7.1"
|
||||
|
||||
export function create( tag, clss, parent, properties ) {
|
||||
const nd = document.createElement(tag);
|
||||
if (clss) clss.split(" ").forEach((s) => nd.classList.add(s))
|
||||
@@ -43,14 +41,14 @@ export function version_at_least(x,y) {
|
||||
/*
|
||||
Return the node object for this node_id.
|
||||
*/
|
||||
function get_real_node(node_id, graph) {
|
||||
export function get_real_node(node_id, graph) {
|
||||
if (!graph) graph = visible_graph()
|
||||
const nid = node_id.toString();
|
||||
if (nid==-10) return wrap_input(graph.inputNode); // special case for subgraph input
|
||||
return graph._nodes_by_id[nid];
|
||||
}
|
||||
|
||||
class Logger {
|
||||
export class Logger {
|
||||
static LIMITED_LOG_BLOCKED = false;
|
||||
static LIMITED_LOG_MS = 5000;
|
||||
static level; // 0 for errors only, 1 activates 'log_problem', 2 activates 'log_info', 3 activates 'log_detail'
|
||||
@@ -102,7 +100,6 @@ class GraphConverter {
|
||||
this.node_input_map = {};
|
||||
this.given_message = false;
|
||||
this.did_conversion = false;
|
||||
this.graph_being_configured = false;
|
||||
}
|
||||
|
||||
running_116_plus() {
|
||||
@@ -136,39 +133,48 @@ class GraphConverter {
|
||||
}
|
||||
}
|
||||
// fix the localized names
|
||||
node.inputs = node.inputs.map((input) => {
|
||||
if (!input.localized_name || input.localized_name.startsWith(i18n('anything'))) input.localized_name = input.name
|
||||
return input;
|
||||
node.inputs = node.inputs.forEach((input) => {
|
||||
if (input) {
|
||||
if (!input.localized_name || input.localized_name.startsWith(i18n('anything'))) input.localized_name = input.name
|
||||
} else {
|
||||
let brteakpoint;
|
||||
}
|
||||
})
|
||||
|
||||
// set types to match
|
||||
node.inputs.forEach((input) => {
|
||||
if (input.type=='*') {
|
||||
const graph = node_graph(node);
|
||||
if (input.link) {
|
||||
const llink = graph.links[input.link];
|
||||
if (link_is_from_subgraph_input(llink)) {
|
||||
input.type = get_subgraph_input_type(graph, llink.origin_slot);
|
||||
if (node.inputs) {
|
||||
node.inputs.forEach((input) => {
|
||||
if (input.type=='*') {
|
||||
const graph = node_graph(node);
|
||||
if (input.link) {
|
||||
const llink = graph.links[input.link];
|
||||
if (link_is_from_subgraph_input(llink)) {
|
||||
input.type = get_subgraph_input_type(graph, llink.origin_slot);
|
||||
} else {
|
||||
input.type = llink.type;
|
||||
}
|
||||
} else {
|
||||
input.type = llink.type;
|
||||
input.type = (input.label && input.label!=i18n('anything')) ? input.label : input.name
|
||||
}
|
||||
} else {
|
||||
input.type = (input.label && input.label!=i18n('anything')) ? input.label : input.name
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Logger.log_detail(`clean_ue_node ${node.id} (${node.type})`, node.inputs);
|
||||
Logger.log_detail(`clean_ue_node ${node.id} (${node.type})`, node.inputs);
|
||||
}
|
||||
}
|
||||
|
||||
convert_if_pre_116(node) {
|
||||
if (!node) return;
|
||||
|
||||
if (node.IS_UE) this.clean_ue_node(node)
|
||||
|
||||
if (node.properties?.ue_properties?.widget_ue_connectable) return
|
||||
if (node.properties?.widget_ue_connectable) return // pre 7.0 node which will be converted
|
||||
|
||||
if (node.IS_UE) {
|
||||
if (node.properties?.ue_properties?.version) return
|
||||
} else {
|
||||
this.clean_ue_node(node)
|
||||
}
|
||||
|
||||
if (!this.given_message) {
|
||||
Logger.log_info(`Graph was saved with a version of ComfyUI before 1.16, so Anything Everywhere will try to work out which widgets are connectable`);
|
||||
this.given_message = true;
|
||||
@@ -200,72 +206,10 @@ class GraphConverter {
|
||||
|
||||
export const graphConverter = GraphConverter.instance();
|
||||
|
||||
class LoopError extends Error {
|
||||
constructor(id, stack, ues) {
|
||||
super("Loop detected");
|
||||
this.id = id;
|
||||
this.stack = [...stack];
|
||||
this.ues = [...ues];
|
||||
}
|
||||
}
|
||||
|
||||
function find_all_upstream(node, links_added) {
|
||||
const all_upstream = [];
|
||||
node?.inputs?.forEach((input) => { // normal links
|
||||
const link_id = input.link;
|
||||
if (link_id) {
|
||||
const link = app.graph.links[link_id];
|
||||
if (link) all_upstream.push({id:link.origin_id, slot:link.origin_slot});
|
||||
}
|
||||
});
|
||||
links_added.forEach((la)=>{ // UE links
|
||||
if (la.downstream==node.id) {
|
||||
all_upstream.push({id:la.upstream, slot:la.upstream_slot, ue:la.controller.toString()})
|
||||
}
|
||||
});
|
||||
|
||||
return all_upstream;
|
||||
}
|
||||
|
||||
function recursive_follow(node, links_added, stack, nodes_cleared, ues, count, slot) {
|
||||
count += 1;
|
||||
if (stack.includes(node.id.toString())) throw new LoopError(node.id, new Set(stack), new Set(ues));
|
||||
if (nodes_cleared.has(node.id.toString())) return;
|
||||
stack.push(node.id.toString());
|
||||
|
||||
find_all_upstream(node, links_added).forEach((upstream) => {
|
||||
if (upstream.ue) ues.push(upstream.ue);
|
||||
count = recursive_follow(upstream, links_added, stack, nodes_cleared, ues, count, upstream.slot);
|
||||
if (upstream.ue) ues.pop();
|
||||
})
|
||||
|
||||
nodes_cleared.add(node.id.toString());
|
||||
stack.pop();
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
Throw a LoopError if there is a loop.
|
||||
live_nodes is a list of all live (ie not bypassed) nodes in the graph
|
||||
links_added is a list of the UE virtuals links
|
||||
*/
|
||||
function node_in_loop(live_nodes, links_added) {
|
||||
var nodes_to_check = [];
|
||||
const nodes_cleared = new Set();
|
||||
live_nodes.forEach((n)=>nodes_to_check.push(n));
|
||||
var count = 0;
|
||||
while (nodes_to_check.length>0) {
|
||||
const node = nodes_to_check.pop();
|
||||
count += recursive_follow(node, links_added, [], nodes_cleared, [], 0, -1);
|
||||
nodes_to_check = nodes_to_check.filter((nid)=>!nodes_cleared.has(nid.toString()));
|
||||
}
|
||||
console.log(`node_in_loop made ${count} checks`)
|
||||
}
|
||||
|
||||
/*
|
||||
Is a node alive (ie not bypassed or set to never)
|
||||
*/
|
||||
function node_is_live(node, treat_bypassed_as_live){
|
||||
export function node_is_live(node, treat_bypassed_as_live){
|
||||
if (!node) return false;
|
||||
if (node.mode===0) return true;
|
||||
if (node.mode===2 || node.mode===4) return !!treat_bypassed_as_live;
|
||||
@@ -301,12 +245,10 @@ function handle_bypass(original_link, type, graph) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Does this input connect upstream to a live node?
|
||||
*/
|
||||
function is_connected(input, treat_bypassed_as_live, graph) {
|
||||
export function is_connected(input, treat_bypassed_as_live, graph) {
|
||||
const link_id = input.link;
|
||||
if (link_id === null) return false; // no connection
|
||||
var the_link = graph.links[link_id];
|
||||
@@ -320,22 +262,17 @@ function is_connected(input, treat_bypassed_as_live, graph) {
|
||||
/*
|
||||
Is this a UE node?
|
||||
*/
|
||||
function is_UEnode(node_or_nodeType) {
|
||||
export function is_UEnode(node_or_nodeType) {
|
||||
const title = node_or_nodeType.type || node_or_nodeType.comfyClass;
|
||||
return ((title) && (title.startsWith("Anything Everywhere") || title==="Seed Everywhere" || title==="Prompts Everywhere"))
|
||||
}
|
||||
|
||||
function is_helper(node_or_nodeType) {
|
||||
const title = node_or_nodeType.type || node_or_nodeType.comfyClass;
|
||||
return ((title) && (title.startsWith("Simple String")))
|
||||
}
|
||||
|
||||
/*
|
||||
Inject a call into a method on object with name methodname.
|
||||
The injection is added at the end of the existing method (if the method didn't exist, it is created)
|
||||
injectionthis and injectionarguments are passed into the apply call (as the this and the arguments)
|
||||
*/
|
||||
function inject(object, methodname, tracetext, injection, injectionthis, injectionarguments) {
|
||||
export function inject(object, methodname, tracetext, injection, injectionthis, injectionarguments) {
|
||||
const original = object[methodname];
|
||||
object[methodname] = function() {
|
||||
original?.apply(this, arguments);
|
||||
@@ -343,9 +280,6 @@ function inject(object, methodname, tracetext, injection, injectionthis, injecti
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export { node_in_loop, handle_bypass, node_is_live, is_connected, is_UEnode, is_helper, inject, Logger, get_real_node }
|
||||
|
||||
export function defineProperty(instance, property, desc) {
|
||||
const existingDesc = Object.getOwnPropertyDescriptor(instance, property);
|
||||
if (existingDesc?.configurable === false) {
|
||||
@@ -401,48 +335,18 @@ export class Pausable {
|
||||
on_unpause(){}
|
||||
}
|
||||
|
||||
export function get_connection(node, i, override_type) {
|
||||
export function get_connection(node, i) {
|
||||
const graph = node_graph(node)
|
||||
const in_link = node?.inputs[i]?.link;
|
||||
var type = override_type;
|
||||
var link = undefined;
|
||||
if (in_link) {
|
||||
if (!override_type) type = node.inputs[i].type;
|
||||
link = handle_bypass(graph.links[in_link], type, graph);
|
||||
}
|
||||
return { link:link, type:type }
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
This is called in various places (node load, creation, link change) to ensure there is exactly one empty input
|
||||
*/
|
||||
export function fix_inputs(node) {
|
||||
if (!node.graph) return // node has been deleted prior to the fix
|
||||
if (node.properties.ue_properties.fixed_inputs) return
|
||||
|
||||
const empty_inputs = node.inputs.filter((inputslot)=>(inputslot.type=='*'))
|
||||
var excess_inputs = empty_inputs.length - 1
|
||||
|
||||
if (excess_inputs<0) {
|
||||
try {
|
||||
node.properties.ue_properties.next_input_index = (node.properties.ue_properties.next_input_index || 10) + 1
|
||||
node.addInput(`anything${node.properties.ue_properties.next_input_index}`, "*", {label:i18n('anything')})
|
||||
fix_inputs(node)
|
||||
} catch (e) {
|
||||
Logger.log_error(e)
|
||||
}
|
||||
} else if (excess_inputs>0) {
|
||||
const idx = node.inputs.findIndex((inputslot)=>(inputslot.type=='*'))
|
||||
if (idx>=0) {
|
||||
try {
|
||||
node.removeInput(idx)
|
||||
fix_inputs(node)
|
||||
} catch (e) {
|
||||
Logger.log_error(e)
|
||||
}
|
||||
} else {
|
||||
Logger.log_problem(`Something very odd happened in fix_inputs for ${node.id}`)
|
||||
var llink = graph.links[in_link]
|
||||
llink = handle_bypass(llink, llink.type, graph)
|
||||
if (!llink) {
|
||||
Logger.log_problem(`handle_bypass failing - subgraph issue?`)
|
||||
llink = graph.links[in_link]
|
||||
}
|
||||
return { link:llink, type:llink.type }
|
||||
} else {
|
||||
return { link:undefined, type:undefined }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "cg-use-everywhere"
|
||||
description = "A set of nodes that allow data to be 'broadcast' to some or all unconnected inputs. Greatly reduces link spaghetti."
|
||||
version = "7.1"
|
||||
version = "7.2"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"comfyui-frontend-package>=1.25.0"
|
||||
|
||||
@@ -6,6 +6,20 @@ class Base():
|
||||
CATEGORY = "everywhere"
|
||||
RETURN_TYPES = ()
|
||||
|
||||
class ComboClone(Base):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":{ "combo": (['connect me to a combo widget'], {}) }}
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, combo):
|
||||
return isinstance(combo,str)
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
|
||||
def func(self,combo):
|
||||
return (combo,)
|
||||
|
||||
class SimpleString(Base):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||