Merge pull request #351 from chrisgoringe/7.0

7.0
This commit is contained in:
chrisgoringe
2025-08-08 07:33:51 +10:00
committed by GitHub
19 changed files with 1166 additions and 673 deletions
+76 -25
View File
@@ -12,42 +12,54 @@ Shameless plug for my other nodes -> Check out [Image Picker](https://github.com
---
# Update for ComfyUI front end 1.16 and above
# 7.0 start of the new UE
ComfyUI front end 1.16 made a major change to the way that inputs and widgets work, which had a significant impact on the UE nodes.
Over the next few releases I am aiming to reduce the UE zoo down to a single node that can do everything, but is as easy (or easier) to use than the current nodes.
A widget is no longer converted to an input in order to connect to it; instead, you just connect an output to it, and it converts.
The first step in this process is merging the `AnythingEverywhere` and `AnythingEverywhere?` nodes by moving the regex restrictions into a separate dialog that can be used by either. In future iterations the group and color restrictions will also move to this dialog (so people find them!)
This is quite cool - except that it meant that there was no way for UE to tell if a widget was supposed to be an empty input for UE to feed.
---
So there is a new mechanism for this. Here's a picture of a node that I have right-clicked:
# 6.3 Update for ComfyUI front end (version TBC) - subgraphs
![116](docs/116.png)
With the addition of subgraphs, group nodes are no longer supported in UE from 6.3 onwards.
Things to notice:
Instead, UE 6.3 has the following support for subgraphs:
- There is a new menu `UE Connectable Widgets` which lists all the widgets. Those marked as connectable have a green bar to the left of the name.
- Select a widget to toggle its value
- Next to the widget, where an input would be, the connectable widgets have a graphical indication of their state.
- The smaller dark circle (next to steps) indicate a connectable node which is not connected (widget value will be used)
- The light circle (next to cfg) and greying out the widget indicates that a UE node will provide the value
- No icon (all the other widgets) indicates that they are not UE connectable
- Other inputs still have the indications of normal connection or UE connection (here model is a UE connection)
- UE nodes can be used in the main graph and in subgraphs.
- UE nodes only broadcast within the graph or subgraph they are in.
- Subgraph nodes are just nodes. You can connect UE nodes to their outputs, and UE links will connect to their inputs.
- Within a subgraph you can connect from the input panel to a UE node.
- UE nodes within the subgraph will *not* broadcast to the output panel.
- When you convert a set of nodes to a subgraph, UE links will work as long as:
- the UE node (Control) and the node feeding it (Source) are both in the subgraph, or
- neither the Source nor Control are in the subgraph, or
- the Source is in the subgraph, and the Control is not, and no Target nodes are in the subgraph
You'll probably find that turning the `showlinks` option on helps, and the `highlight` option is required for those widget indicators which help a lot.
![options116](docs/options116.png)
## Subgraph creation
Hopefully old workflows will be automatically converted when you load them. Hopefully.
There are three nodes involved in every UE link:
- Source (the link sending the data),
- Control (the UE node connected to the source),
- Target (the node that is receiving the data as an input)
## Naming widgets
This is how those cases are treated:
The `Prompts Everywhere`, `Seed Everywhere`, and `Anything Everywhere?` all make use of the name of an input. On some occasions this might mean you want to rename an input.
|Support|Source|Control|Target||
|-|-|-|-|-|
|Yes|Graph|Graph|Graph|Nothing changes|
|Yes|Graph|Graph|Subgraph|The subgraph will have inputs for the data; in the subgraph the input panel is connected to the Target with a real link|
|No|Graph|Subgraph|Graph|Not supported|
|No*|Graph|Subgraph|Subgraph|Not supported|
|Yes|Subgraph|Graph|Graph|The subgraph will be connected to the Control|
|No|Subgraph|Graph|Subgraph|Not supported|
|Yes|Subgraph|Subgraph|Graph|The Source will be connected to the Control *and* the output panel in the subgraph, the output will be connected to the Target with a real link|
|Yes|Subgraph|Subgraph|Subgraph|All nodes will be connected in the subgraph as they were in the graph|
For an input that can't be a widget, that's trivial - right click on the input dot and select `Rename Slot`. The name will be updated, and the new name displayed next to the dot.
If an input *can* be a widget, you can rename it in the same way, but the new name *does not get displayed*. The name has changed, and the UE nodes will use the new name, but the old name is still shown. I consider this to be a bug in the Comfy front-end, and have raised an issue [here](https://github.com/Comfy-Org/ComfyUI_frontend/issues/3654). Unfortunately it appears that Comfy UI is going to remove the ability to rename a widget entirely, so best to avoid using widget input names if at all possible.
No* indicates a case that does not work, but might get implemented.
No indicates a case I'm unlikely ever to support
---
@@ -75,9 +87,8 @@ UE nodes mostly work with group nodes. But there are a couple of important thing
## Latest updates
6.2 (June 2025)
- Mostly internal issues
- Third party integration (in 6.2.2, July 2025)
6.3 June/July 2025
- support for ComfyUI subgraphs
6.1 (May 2025)
- Mostly some bug-fixes (#216, #217, #300)
@@ -276,3 +287,43 @@ Bypassing and disabling nodes works, but with one catch. If you have a UE nodes
|![1](docs/bypass_catch1.png)|![2](docs/bypass_catch2.png)|
This is unlikely to be fixed, but should be fairly easy to avoid!
---
# Some older information
# Update for ComfyUI front end 1.16 and above
ComfyUI front end 1.16 made a major change to the way that inputs and widgets work, which had a significant impact on the UE nodes.
A widget is no longer converted to an input in order to connect to it; instead, you just connect an output to it, and it converts.
This is quite cool - except that it meant that there was no way for UE to tell if a widget was supposed to be an empty input for UE to feed.
So there is a new mechanism for this. Here's a picture of a node that I have right-clicked:
![116](docs/116.png)
Things to notice:
- There is a new menu `UE Connectable Widgets` which lists all the widgets. Those marked as connectable have a green bar to the left of the name.
- Select a widget to toggle its value
- Next to the widget, where an input would be, the connectable widgets have a graphical indication of their state.
- The smaller dark circle (next to steps) indicate a connectable node which is not connected (widget value will be used)
- The light circle (next to cfg) and greying out the widget indicates that a UE node will provide the value
- No icon (all the other widgets) indicates that they are not UE connectable
- Other inputs still have the indications of normal connection or UE connection (here model is a UE connection)
You'll probably find that turning the `showlinks` option on helps, and the `highlight` option is required for those widget indicators which help a lot.
![options116](docs/options116.png)
Hopefully old workflows will be automatically converted when you load them. Hopefully.
## Naming widgets
The `Prompts Everywhere`, `Seed Everywhere`, and `Anything Everywhere?` all make use of the name of an input. On some occasions this might mean you want to rename an input.
For an input that can't be a widget, that's trivial - right click on the input dot and select `Rename Slot`. The name will be updated, and the new name displayed next to the dot.
If an input *can* be a widget, you can rename it in the same way, but the new name *does not get displayed*. The name has changed, and the UE nodes will use the new name, but the old name is still shown. I consider this to be a bug in the Comfy front-end, and have raised an issue [here](https://github.com/Comfy-Org/ComfyUI_frontend/issues/3654). Unfortunately it appears that Comfy UI is going to remove the ability to rename a widget entirely, so best to avoid using widget input names if at all possible.
+1 -1
View File
@@ -1,6 +1,6 @@
from .use_everywhere import SeedEverywhere, AnythingEverywherePrompts
UE_VERSION = "6.2.2"
UE_VERSION = "7.0"
NODE_CLASS_MAPPINGS = { "Seed Everywhere": SeedEverywhere }
+82
View File
@@ -0,0 +1,82 @@
import { create } from "./use_everywhere_utilities.js"
import { settingsCache } from "./use_everywhere_cache.js"
export class FloatingWindow extends HTMLDivElement {
constructor() {
super()
this.classList.add('ue_editor')
this.header = create('div', 'ue_editor_header', this)
this.body = create('div', 'ue_editor_body', this)
this.footer = create('div', 'ue_editor_footer', this, {innerText:"Close"})
this.header.addEventListener('mousedown',this.start_dragging.bind(this))
this.addEventListener('mousemove', this.mousemovedover.bind(this))
document.addEventListener('mousemove', this.mousemoved.bind(this))
document.addEventListener('mouseup',this.stop_dragging.bind(this))
document.addEventListener('mouseleave',this.stop_dragging.bind(this))
this.footer.addEventListener('click', this.hide.bind(this))
this.dragging = false
this.hide()
document.body.append(this)
}
show() {
this.style.display = 'flex';
const tt = document.getElementById('ue_tooltip')
if (tt) tt.style.display = 'none'
this.showing = true
}
hide() {
this.style.display = 'none'
this.showing = false
}
set_title(title) {
this.header.innerText = title
}
set_body(element) {
this.body.innerHTML = ""
this.body.appendChild(element)
}
maybe_move_to(x,y) {
if (this.position && settingsCache.getSettingValue("Use Everywhere.Graphics.preserve edit window position")) return
this.move_to(x,y)
}
move_to(x,y) {
this.position = {x:x,y:y}
this.style.left = `${this.position.x}px`
this.style.top = `${this.position.y}px`
}
swallow(e) {
e.stopPropagation()
e.preventDefault()
}
start_dragging(e) {
this.dragging = true
this.swallow(e)
}
stop_dragging(e) {
this.dragging = false
}
mousemovedover(e) {
this.mousemoved(e)
this.swallow(e)
}
mousemoved(e) {
if (this.dragging) this.move_to( this.position.x + e.movementX , this.position.y + e.movementY )
}
}
customElements.define('ue-floating', FloatingWindow, {extends: 'div'})
export const edit_window = new FloatingWindow()
+42
View File
@@ -0,0 +1,42 @@
const FUNCTIONAL = {
seed_input_regex : "seed|随机种",
prompt_regex : "(_|\\b)pos(itive|_|\\b)|^prompt|正面",
negative_regex : "(_|\\b)neg(ative|_|\\b)|负面",
}
const DISPLAY = {
title : "title",
input : "input",
group : "group",
Group : "Group",
Color : "Colour",
Priority : "Priority",
prompt_regexes_text : "Prompt Everywhere",
"No restrictions" : "No restrictions",
"Send only within group" : "Send only within group",
"Send only not within group" : "Send only not within group",
"Send only to same color" : "Send only to same color",
"Send only to different color" : "Send only to different color",
}
export const GROUP_RESTRICTION_OPTIONS = [i18n("No restrictions"), i18n("Send only within group"), i18n("Send only not within group")]
export const COLOR_RESTRICTION_OPTIONS = [i18n("No restrictions"), i18n("Send only to same color"), i18n("Send only to different color")]
const toTitleCase = (phrase) => {
return phrase
.toLowerCase()
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};
export function i18n(v, extras) {
var r = DISPLAY[v] || v
if (extras?.titlecase) r = toTitleCase(r)
return r
}
export function default_regex(v) {
return FUNCTIONAL[v]
}
+55
View File
@@ -0,0 +1,55 @@
import { any_restrictions, describe_restrictions } from "./ue_properties.js";
import { app } from "../../scripts/app.js";
import { create } from "./use_everywhere_utilities.js";
import { edit_window } from "./floating_window.js";
import { settingsCache } from "./use_everywhere_cache.js";
const HOVERTIME = 500
var hover_node_id = null
var mouse_pos = [0,0]
const ue_tooltip_element = create('span', 'ue_tooltip', document.body, {id:'ue_tooltip'})
function show_tooltip(node) {
if (!node) return
ue_tooltip_element.style.display = "block"
ue_tooltip_element.style.left = `${mouse_pos.x+10}px`
ue_tooltip_element.style.top = `${mouse_pos.y+5}px`
ue_tooltip_element.innerHTML = ""
ue_tooltip_element.appendChild(describe_restrictions(node))
ue_tooltip_element.showing = node.id
}
function show_on_hover() {
if (mouse_pos.x==app.canvas.mouse[0] && mouse_pos.y==app.canvas.mouse[1]) {
show_tooltip(app.canvas.node_over)
} else if (app.canvas.node_over?.id == hover_node_id) {
mouse_pos = { x:app.canvas.mouse[0], y:app.canvas.mouse[1] }
setTimeout(show_on_hover, HOVERTIME)
}
}
function hide_tooltip() {
var ue_tooltip_element = document.getElementById('ue_tooltip')
if (ue_tooltip_element) {
ue_tooltip_element.style.display = "none"
ue_tooltip_element.showing = null
}
}
export function maybe_show_tooltip() {
if (!settingsCache.getSettingValue('Use Everywhere.Graphics.tooltips')) return
const node = app.canvas?.node_over
hover_node_id = node?.id
if (!node) return hide_tooltip()
if (edit_window.showing) return hide_tooltip()
if (!(node.IS_UE && any_restrictions(node))) return hide_tooltip()
if (ue_tooltip_element.showing) return
mouse_pos = { x:app.canvas.mouse[0], y:app.canvas.mouse[1] }
setTimeout(show_on_hover, HOVERTIME)
}
+55 -37
View File
@@ -1,43 +1,61 @@
@layer ue {
#ue_tooltip {
z-index : 9000;
border: thin solid gray;
color:black;
position: absolute;
padding: 4px;
}
#ue_tooltip table {
border-collapse: collapse;
.litegraph label.ueprompt {
padding:0px;
font-size: 12px;
border-radius: 0;
border: 0;
box-shadow: none !important;
margin:0px;
height:30px;
background-color: rgba(0,0,0,0);
}
}
#ue_tooltip tr:nth-child(odd) {
background-color: rgba(234, 255, 234, 0.67);
}
#ue_tooltip tr:nth-child(even) {
background-color: rgba(255, 234, 234, 0.67);
}
#ue_tooltip th {
text-align: right;
}
.litegraph span.ueprompttext {
margin: 0px;
min-width: 30px;
font-family: Arial, sans-serif;
color: var(--descrip-text);
text-align: right;
padding: 2px 2px 4px 0px;
background-color: inherit;
}
.ue_editor {
z-index : 9000;
border: thin solid gray;
color:white;
position: absolute;
padding: 0px;
display: flex;
flex-direction: column;
align-items: center;
background-color: rgb(45 24 24 / 80%);
}
.litegraph span.uepromptspan {
margin: 0px;
.ue_editor_header {
width: 100%;
padding-left:12px;
background-color: inherit;
}
text-align: center;
background-color: rgb(45 24 24 / 80%);
background-size: 100% 14px;
background-image: repeating-linear-gradient(0deg, #636363, black 1px, rgb(45 24 24 / 80%) 1px, rgb(45 24 24 / 80%) 2px);
padding-top: 4px;
.litegraph input.uepromptinput {
padding: 0 0 0 6px;
font-size: 12px;
border-radius: 12px;
border: 2px solid var(--border-color);
color: var(--input-text);
margin: 0px;
width: 100%;
float: right;
}
}
ue_hide {
display: none;
}
.ue_editor_body {
padding: 0px 4px;
}
.ue_editor_body th {
text-align: right;
}
.ue_editor_body th::after {
content: ':';
}
.ue_editor_footer {
margin: 8px;
border: thin solid white;
padding: 2px 30px;
background-color: rgba(0, 0, 0, 0.75);
}
}
+130
View File
@@ -0,0 +1,130 @@
import { VERSION, version_at_least, is_UEnode, graphConverter, fix_inputs, create } from "./use_everywhere_utilities.js"
import { i18n, default_regex, GROUP_RESTRICTION_OPTIONS, COLOR_RESTRICTION_OPTIONS } from "./i18n.js";
const ALL_REGEXES = ['title', 'input', 'prompt', 'negative', 'group']
function any_regex_restrictions(node) {
if (!node.properties.ue_properties) return false
var restricted = false
ALL_REGEXES.forEach((r)=>{
const reg = node.properties.ue_properties[`${r}_regex`]
restricted = restricted || (reg && reg.length>0)
})
return restricted
}
export function any_restrictions(node) {
return (
node.properties.ue_properties.group_restricted ||
node.properties.ue_properties.color_restricted ||
node.properties.ue_properties.priority ||
any_regex_restrictions(node)
)
}
export function describe_restrictions(node) {
const statements = []
if (node.properties.ue_properties) {
ALL_REGEXES.forEach((r)=>{
const reg = node.properties.ue_properties[`${r}_regex`]
if (reg && reg.length>0) statements.push([`${i18n(r)} regex`, reg])
})
if (node.properties.ue_properties.group_restricted) statements.push(['group',i18n(GROUP_RESTRICTION_OPTIONS[node.properties.ue_properties.group_restricted])])
if (node.properties.ue_properties.color_restricted) statements.push(['color',i18n(COLOR_RESTRICTION_OPTIONS[node.properties.ue_properties.color_restricted])])
}
const table = create('table')
statements.forEach((s)=>{
const row = create('tr', null, table)
create('th', null, row, {innerText:`${i18n(s[0], {titlecase:true})}:`})
create('td', null, row, {innerText:s[1]})
})
return table
}
export function default_priority(node) {
var p = 10
if (node.type === "Seed Everywhere" || node.type === "Prompts Everywhere") p += 10
if (any_regex_restrictions(node)) p += 20
if (node.properties.ue_properties.group_restricted > 0) p += 3
if (node.properties.ue_properties.color_restricted > 0) p += 6
return p
}
const DEFAULT_PROPERTIES = {
version : VERSION,
group_restricted : 0,
color_restricted : 0,
widget_ue_connectable : {},
title_regex : null,
input_regex : null,
group_regex : null,
priority : undefined,
}
/*
Called whenever any node is created.
If the graph is still being configured, then that means the node is being created as part of a load.
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)
}
}
/*
Convert node properties when loaded.
*/
export function setup_ue_properties_onload(node) {
if (node.IS_UE) {
if ( !version_at_least(node.properties?.ue_properties?.version, "7.0") ) {
// convert a pre 7.0 UE node
node.properties.ue_properties = {
version : VERSION,
group_restricted : node.properties.group_restricted,
color_restricted : node.properties.color_restricted,
widget_ue_connectable : node.properties.widget_ue_connectable,
title_regex : node.widgets_values?.[0],
input_regex : node.widgets_values?.[1],
group_regex : node.widgets_values?.[2],
priority : undefined,
}
delete node.properties.group_restricted
delete node.properties.color_restricted
delete node.properties.widget_ue_connectable
}
convert_node_types(node)
}
}
function convert_node_types(node) {
if (!node.IS_UE) return
if (node.type=="Anything Everywhere?") {
node.widgets.forEach((w)=>{w.hidden=true})
if (node.title=="Anything Everywhere?") node.title = "Anything Everywhere"
node.type = "Anything Everywhere"
} else if (node.type=="Anything Everywhere3") {
if (node.title=="Anything Everywhere3") node.title = "Anything Everywhere"
node.type = "Anything Everywhere"
} else if (node.type=="Seed Everywhere") {
node.properties.ue_properties.fixed_inputs = true
node.properties.ue_properties.seed_inputs = true
node.properties.ue_properties.input_regex = node.properties.ue_properties.input_regex || default_regex('seed_input_regex')
} else if (node.type=="Prompts Everywhere") {
node.properties.ue_properties.fixed_inputs = true
node.properties.ue_properties.prompt_regexes = true
}
ALL_REGEXES.forEach((r)=>{
const rname = `${r}_regex`
if (node.properties.ue_properties[rname]==".*") node.properties.ue_properties[rname] = undefined
})
fix_inputs(node)
}
+118
View File
@@ -0,0 +1,118 @@
import { LinkRenderController } from "./use_everywhere_ui.js";
import { i18n, default_regex } from "./i18n.js";
import { app } from "../../scripts/app.js";
import { default_priority } from "./ue_properties.js";
import { GROUP_RESTRICTION_OPTIONS, COLOR_RESTRICTION_OPTIONS } from "./i18n.js";
import { edit_window } from "./floating_window.js";
const REGEXES = ['title', 'input', 'group']
const P_REGEXES = ['prompt', 'negative']
export function edit_restrictions(a,b,c,d, node) {
edit_window.set_body(create_editor_html(node))
edit_window.set_title(`Restrictions for node #${node.id}`)
edit_window.maybe_move_to(app.canvas.mouse[0]+10, app.canvas.mouse[1]+10)
edit_window.show()
}
function add_row(table, header) {
const row = document.createElement('tr')
table.appendChild(row)
const header_elem = document.createElement('th')
header_elem.innerText = header
row.appendChild(header_elem)
return row
}
function add_cell(row, cell) {
const td = document.createElement('td')
row.appendChild(td)
td.appendChild(cell)
}
function changed(node, property, value) {
node.properties.ue_properties[property] = value
if (!node.properties.ue_properties.priority) {
document.getElementById('priority_value').value = `${default_priority(node)}`
}
if (node.properties.ue_properties.prompt_regexes) {
for (var i=0; i<2; i++) {
if (!node.properties.ue_properties[`${P_REGEXES[i]}_regex`]) {
document.getElementById(`${P_REGEXES[i]}_regex_value`).value = default_regex(`${P_REGEXES[i]}_regex`)
}
}
}
const elem = document.getElementById(`${property}_value`)
if (elem) elem.style.opacity = (value) ? "1" : "0.5"
LinkRenderController.instance().mark_link_list_outdated()
app.canvas.setDirty(true,true)
}
function create_editor_html(node) {
const table = document.createElement('table')
for (var i=0; i<=2; i++) {
if (i==1 && node.properties.ue_properties.prompt_regexes) {
for (var j=0; j<2; j++) {
const name = P_REGEXES[j]
const row = add_row(table, `${i18n(name)} regex`)
const input = document.createElement('input')
input.value = node.properties.ue_properties[`${name}_regex`] || default_regex(`${name}_regex`)
if (!node.properties.ue_properties[`${name}_regex`]) input.style.opacity = 0.5
input.id = `${name}_regex_value`
input.style.width = '250px'
input.addEventListener('input', ()=>{ changed(node, `${name}_regex`, input.value)})
add_cell(row,input)
}
} else {
const name = REGEXES[i]
const row = add_row(table, `${i18n(name)} regex`)
const input = document.createElement('input')
input.value = node.properties.ue_properties[`${name}_regex`] || ''
input.addEventListener('input', ()=>{ changed(node, `${name}_regex`, input.value)})
add_cell(row,input)
}
}
const gr_row = add_row(table, i18n("Group"))
const gr_select = document.createElement('select')
add_cell(gr_row,gr_select)
GROUP_RESTRICTION_OPTIONS.forEach((gro, i)=>{
const gr_option = document.createElement('option')
gr_option.value = `${i}`
gr_option.innerText = gro
gr_select.appendChild(gr_option)
})
gr_select.value = `${node.properties.ue_properties.group_restricted || 0}`
gr_select.addEventListener('input', ()=>{ changed(node, `group_restricted`, parseInt(gr_select.value))})
const col_row = add_row(table, i18n("Color"))
const col_select = document.createElement('select')
add_cell(col_row,col_select)
COLOR_RESTRICTION_OPTIONS.forEach((cro, i)=>{
const col_option = document.createElement('option')
col_option.value = `${i}`
col_option.innerText = cro
col_select.appendChild(col_option)
})
col_select.value = `${node.properties.ue_properties.color_restricted || 0}`
col_select.addEventListener('input', ()=>{ changed(node, `color_restricted`, parseInt(col_select.value))})
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)}`
priority_edit.addEventListener('input', ()=>{
const p = parseInt(priority_edit.value)
if (p) changed(node, `priority`, p)
if (priority_edit.value=='') changed(node, `priority`, undefined)
})
priority_edit.id = 'priority_value'
if (!node.properties.ue_properties.priority) priority_edit.style.opacity = 0.5
add_cell(priority_row,priority_edit)
return table
}
+118 -68
View File
@@ -1,14 +1,17 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { is_UEnode, is_helper, inject, Logger, get_real_node, defineProperty, graphConverter } from "./use_everywhere_utilities.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 { LinkRenderController } from "./use_everywhere_ui.js";
import { GraphAnalyser } from "./use_everywhere_graph_analysis.js";
import { node_menu_settings, canvas_menu_settings, non_ue_menu_settings, SETTINGS } from "./use_everywhere_settings.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 { any_restrictions, setup_ue_properties_oncreate, setup_ue_properties_onload } from "./ue_properties.js";
import { edit_restrictions } from "./ue_properties_editor.js";
/*
The ui component that looks after the link rendering
@@ -30,6 +33,20 @@ function inject_outdating_into_object_method(object, methodname, tracetext) {
if (object) inject(object, methodname, tracetext, linkRenderController.mark_link_list_outdated, linkRenderController);
}
class Deferred {
constructor() { this.deferred_actions = [] }
push(x) { this.deferred_actions.push(x) } // add action of the form: { fn:function, args:array }
execute() {
while (this.deferred_actions.length>0) {
const action = this.deferred_actions.pop()
try { action?.fn(...action?.args) }
catch (e) { Logger.log_error(e) }
}
}
}
const deferred_actions = new Deferred()
app.registerExtension({
name: "cg.customnodes.use_everywhere",
settings: SETTINGS,
@@ -42,60 +59,78 @@ app.registerExtension({
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (side,slot,connect,link_info,output) {
if (this.IS_UE && side==1) { // side 1 is input
if (this.type=="Anything Everywhere?" && slot!=0) {
// don't do anything for the regexs
} else {
const type = (connect && link_info) ? get_real_node(link_info?.origin_id)?.outputs[link_info?.origin_slot]?.type : undefined;
this.input_type[slot] = type;
if (link_info) link_info.type = type ? type : "*";
update_input_label(this, slot, app);
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) {
// do the fix at the end of graph change
deferred_actions.push( { fn:fix_inputs, args:[this,]} )
// disconnecting doesn't trigger graphChange call?
setTimeout(deferred_actions.execute.bind(deferred_actions), 100)
}
}
linkRenderController.mark_link_list_outdated();
onConnectionsChange?.apply(this, arguments);
};
/*
Reject duplicated inputs for now
*/
if (is_UEnode(nodeType)) {
const onConnectInput = nodeType.prototype.onConnectInput
nodeType.prototype.onConnectInput = function (index, type) {
if (!this.properties.ue_properties.fixed_inputs) {
if (this.inputs.find((i, j)=>(i.type==type && j!=index))) return false
}
return onConnectInput?.apply(this, arguments)
}
}
/*
Extra menu options are the node right click menu.
We add to this list, and also insert a link list outdate to everything.
*/
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
getExtraMenuOptions?.apply(this, arguments);
if (is_UEnode(this)) {
node_menu_settings(options, this);
} else {
non_ue_menu_settings(options, this);
add_extra_menu_items(nodeType.prototype, inject_outdating_into_object_method)
if (is_UEnode(nodeType)) {
const original_onDrawTitleBar = nodeType.prototype.onDrawTitleBar;
nodeType.prototype.onDrawTitleBar = function(ctx, title_height) {
original_onDrawTitleBar?.apply(this, arguments);
if (any_restrictions(this)) indicate_restriction(ctx, title_height);
}
inject_outdating_into_objects(options,'callback',`menu option on ${this.id}`);
}
/*
When a UE node is created, we set the group and color restriction properties.
We also create pseudo-widgets for all the inputs so that they can be searched
and to avoid other code throwing errors.
*/
if (is_UEnode(nodeType)) {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
if (!this.properties) this.properties = {}
this.properties.group_restricted = 0;
this.properties.color_restricted = 0;
/*if (!this.properties) this.properties = {}
if (this.inputs) {
if (!this.widgets) this.widgets = [];
for (const input of this.inputs) {
if (input.widget && !this.widgets.find((w) => w.name === input.widget.name)) this.widgets.push(input.widget)
}
}
}*/
Logger.log_detail(`Node ${this.id} created`)
setup_ue_properties_oncreate(this)
return r;
}
}
},
async nodeCreated(node) {
// TODO see if we still need this
if (!node.__mode) {
node.__mode = node.mode
defineProperty(node, "mode", {
@@ -103,42 +138,16 @@ app.registerExtension({
set: (v)=>{node.__mode = v; node.afterChangeMade?.('mode', v);}
})
}
if (!node.__bgcolor) {
node.__bgcolor = node.bgcolor
defineProperty(node,"bgcolor", {
get: ( )=>{return node.__bgcolor},
set: (v)=>{node.__bgcolor = v; node.afterChangeMade?.('bgcolor', v);}
})
}
const acm = node.afterChangeMade
const original_afterChangeMade = node.afterChangeMade
node.afterChangeMade = (p, v) => {
acm?.(p,v)
//if (p==='bgcolor') {
// if (node._being_drawn) return; // the bgcolor gets set by the built in node draw...
// linkRenderController.mark_link_list_outdated();
//}
original_afterChangeMade?.(p,v)
if (p==='mode') {
linkRenderController.mark_link_list_outdated();
node.widgets?.forEach((widget) => {widget.onModeChange?.(v)}); // no idea why I have this?
}
}
node.IS_UE = is_UEnode(node);
if (node.IS_UE) {
node.input_type = [undefined, undefined, undefined]; // for dynamic input types
// If a widget on a UE node is edited, link list is dirty
inject_outdating_into_objects(node.widgets,'callback',`widget callback on ${node.id}`);
// draw the indication of group restrictions
const original_onDrawTitleBar = node.onDrawTitleBar;
node.onDrawTitleBar = function(ctx, title_height) {
original_onDrawTitleBar?.apply(this, arguments);
if (node.properties.group_restricted || node.properties.color_restricted) indicate_restriction(ctx, title_height);
}
}
if (is_helper(node)) { // editing a helper node makes the list dirty
inject_outdating_into_objects(node.widgets,'callback',`widget callback on ${this.id}`);
}
@@ -146,7 +155,8 @@ app.registerExtension({
// removing a node makes the list dirty
inject_outdating_into_object_method(node, 'onRemoved', `node ${node.id} removed`)
graphConverter.on_node_created(node)
// check if the extra menu_items have been added (catch subgraph niode creation)
add_extra_menu_items(node, inject_outdating_into_object_method)
// creating a node makes the link list dirty - but give the system a moment to finish
setTimeout( ()=>{linkRenderController.mark_link_list_outdated()}, 100 );
@@ -154,11 +164,22 @@ app.registerExtension({
// When a graph node is loaded convert it if needed
loadedGraphNode(node) {
if (graphConverter.running_116_plus()) { graphConverter.convert_if_pre_116(node); }
if (graphConverter.running_116_plus()) {
graphConverter.convert_if_pre_116(node);
if (node.isSubgraphNode?.()) {
node.subgraph.nodes.forEach((n) => {
graphConverter.convert_if_pre_116(n);
})
}
}
setup_ue_properties_onload(node)
},
async setup() {
create('link', null, document.getElementsByTagName('HEAD')[0],
{'rel':'stylesheet', 'type':'text/css', 'href': new URL("./ue.css", import.meta.url).href } )
api.addEventListener("status", ({detail}) => {
if (linkRenderController) linkRenderController.note_queue_size(detail ? detail.exec_info.queue_remaining : 0)
});
@@ -184,7 +205,6 @@ app.registerExtension({
*/
const original_drawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function(node, ctx) {
try {
linkRenderController.pause('drawNode')
const v = original_drawNode.apply(this, arguments);
@@ -268,19 +288,26 @@ app.registerExtension({
graphAnalyser = GraphAnalyser.instance();
linkRenderController = LinkRenderController.instance(graphAnalyser);
const original_afterChange = app.graph.afterChange
app.graph.afterChange = function () {
deferred_actions.execute()
original_afterChange?.apply(this, arguments)
}
var prompt_being_queued = false;
const original_graphToPrompt = app.graphToPrompt;
app.graphToPrompt = async function () {
if (prompt_being_queued) {
return await graphAnalyser.graph_to_prompt( graphAnalyser.analyse_graph(true, true) );
return await graphAnalyser.graph_to_prompt( );
} else {
return await original_graphToPrompt.apply(app, arguments);
}
}
app.ue_modified_prompt = async function () {
return await graphAnalyser.graph_to_prompt( graphAnalyser.analyse_graph(true, true) );
return await graphAnalyser.graph_to_prompt();
}
const original_queuePrompt = app.queuePrompt;
@@ -299,6 +326,21 @@ app.registerExtension({
set: (v)=>{app.canvas.__node_over = v; linkRenderController.node_over_changed(v)}
} )
app.canvas.canvas.addEventListener('litegraph:set-graph', ()=>{
linkRenderController.mark_link_list_outdated()
setTimeout(()=>{app.canvas.setDirty(true,true)},200)
})
app.canvas.canvas.addEventListener('litegraph:canvas', (e)=>{
if (e?.detail?.subType=='node-double-click') {
const node = e.detail.node
if (node.IS_UE) {
if (app.ui.settings.getSettingValue('Comfy.Node.DoubleClickTitleToEdit') && e.detail.originalEvent.canvasY<node.pos[1]) return
edit_restrictions(null, null, null, null, node)
}
}
})
if (false) add_debug();
const export_api_label = Array.from(document.getElementsByClassName('p-menubar-item-label')).find((e)=>e.innerText=='Export (API)')
@@ -320,11 +362,18 @@ app.registerExtension({
const original_subgraph = app.graph.convertToSubgraph
app.graph.convertToSubgraph = function () {
const cur_list = graphAnalyser.analyse_graph(true, true)
const mods = convert_to_links(cur_list, -1);
const r = original_subgraph.apply(this, arguments);
mods.restorer()
return r
const ctb_was = graphAnalyser.connect_to_bypassed
graphAnalyser.connect_to_bypassed = true
try {
const cur_list = graphAnalyser.wait_to_analyse_visible_graph()
const mods = convert_to_links(cur_list, null, visible_graph());
const r = original_subgraph.apply(this, arguments);
mods.restorer()
return r
} finally {
graphAnalyser.connect_to_bypassed = ctb_was
}
}
},
@@ -335,7 +384,8 @@ app.registerExtension({
},
afterConfigureGraph() {
graphConverter.remove_saved_ue_links()
graphConverter.remove_saved_ue_links_recursively(app.graph)
//convert_old_nodes(app.graph)
graphConverter.graph_being_configured = false
}
+73 -19
View File
@@ -1,19 +1,20 @@
import { app } from "../../scripts/app.js";
import { display_name } from "./use_everywhere_classes.js";
import { master_graph, node_graph } from "./use_everywhere_subgraph_utils.js";
import { is_UEnode, get_real_node, Logger } from "./use_everywhere_utilities.js";
function _convert_to_links(ue, added_links, removed_links) {
const output_node_id = ue.output[0];
const output_index = ue.output[1];
const output_node = get_real_node(output_node_id);
const output_node = get_real_node(output_node_id, ue.graph);
Logger.log_info("Adding links for " + ue.description);
ue.sending_to.forEach((st) => {
const input_node_id = st.node.id;
const input_node = get_real_node(input_node_id);
const input_node = get_real_node(input_node_id, ue.graph);
const input_index = st.input_index;
if (input_node.inputs[input_index].link) {
const llink = app.graph.links[input_node.inputs[input_index].link]
if (input_node.inputs[input_index].link) { // why would this be happening?
const llink = ue.graph.links[input_node.inputs[input_index].link]
removed_links.push( {...llink} )
}
const new_link = output_node.connect(output_index, input_node, input_index);
@@ -22,44 +23,97 @@ function _convert_to_links(ue, added_links, removed_links) {
`${output_node_id}[${output_index}] -> ` +
`${input_node_id}[${input_index}].`);
else { // Memorize the links we are adding to remove them later
if (added_links) added_links.push(new_link.id);
if (added_links) {
new_link.control_node = ue.controller;
added_links.push(new_link);
}
Logger.log_info(` -> ${display_name(st.node)}, ${st.input.name} ` +
`(${st.node.id}.${st.input_index}) (ID: ${new_link.id})`);
}
});
}
function find_link_to_subgraph_node(id) {
const last_node = app.graph._nodes_by_id[app.graph.last_node_id];
const the_input = last_node.inputs.find(input => input.linkIds.includes(id));
if (!the_input) throw new Error(`Link ID ${id} not found in inputs of most recently added node`);
return the_input.link
function convert_to_links(ues, control_node, graph) {
if (control_node) {
if (!graph) graph = node_graph(control_node)
return _convert_graph_to_links(graph, ues, control_node.id );
} else {
if (!graph) graph = master_graph();
return _convert_graph_to_links(graph, ues, undefined);
}
}
function convert_to_links(ues, control_node_id) {
function _convert_graph_to_links(graph, ues, control_node_id) {
if (!ues?.ues) return {restorer:function(){}, added_links:[]}
const added_links = []
const removed_links = []
ues.ues.forEach((ue)=> {
if (control_node_id==-1 || ue.controller.id == control_node_id) _convert_to_links(ue, added_links, removed_links);
if (control_node_id==undefined || ue.controller.id == control_node_id) _convert_to_links(ue, added_links, removed_links);
});
const restorer = function() {
added_links.forEach(id => {
const links_within_graph_ids = new Set()
const links_within_subgraph_ids = new Set()
const links_into_subgraph_ids = new Set()
const links_out_of_subgraph = new Set()
const problem_links = new Set()
const new_subgraph_node = graph._nodes_by_id[graph.last_node_id];
const new_subgraph = new_subgraph_node?.subgraph;
added_links.forEach(added_link => {
var id = added_link.id
try {
if (app.graph.links[id]) {
app.graph.removeLink(id); // the link still exists, so remove it
} else {
id = find_link_to_subgraph_node(id)
app.graph.removeLink(id)
if (graph.links[id]) {
links_within_graph_ids.add(id);
} else {
const link_to_subgraph_node = new_subgraph_node?.inputs.find(input => input.linkIds.includes(id))?.link
if (link_to_subgraph_node) {
links_into_subgraph_ids.add(link_to_subgraph_node)
} else {
if (new_subgraph.links[id]) {
if (new_subgraph.links[id].target_id==-20) {
links_out_of_subgraph.add(added_link)
} else {
links_within_subgraph_ids.add(id)
}
} else problem_links.add(added_link)
}
}
} catch (e) {
problem_links.add(added_link)
Logger.log_error(e);
}
});
links_out_of_subgraph.forEach((llink)=>{
const control_node_id = llink.control_node?.id
const control_node_in_graph = graph._nodes_by_id[control_node_id]
if (control_node_in_graph) {
// the control node is outside, so we should disconnect anything else that is connected to the same output of the subgraph node
new_subgraph_node.outputs[new_subgraph.links[llink.id].target_slot].links.forEach((link_id)=>{
if (graph.links[link_id].target_id==control_node_id) {
// leave the link to the UE node
} else {
// remove others
links_within_graph_ids.add(link_id)
}
})
} else {
// control node is inside, leave the link, since we can't UE connect to the output panel
}
})
links_within_graph_ids.forEach((lid)=>{graph.removeLink(lid)})
links_into_subgraph_ids.forEach((lid)=>{graph.removeLink(lid)})
links_within_subgraph_ids.forEach((lid)=>{new_subgraph.removeLink(lid)})
if (problem_links.size>0) {
Logger.log_problem("Failed to work out how to remove some temporary links:", problem_links)
}
removed_links.forEach(llink => {
app.graph._nodes_by_id[llink.origin_id].connect(llink.origin_slot, app.graph._nodes_by_id[llink.target_id], llink.target_slot)
graph._nodes_by_id[llink.origin_id].connect(llink.origin_slot, graph._nodes_by_id[llink.target_id], llink.target_slot)
})
};
+68 -91
View File
@@ -1,13 +1,26 @@
import { default_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";
import { Logger, node_is_live, get_real_node } from "./use_everywhere_utilities.js";
import { Logger, node_is_live, get_real_node, get_connection } from "./use_everywhere_utilities.js";
function display_name(node) {
export function display_name(node) {
if (node?.title) return node.title;
if (node?.type) return node.type;
if (node?.properties['Node name for S&R']) return node.properties['Node name for S&R'];
return "un-nameable node";
}
function regex_for(node, k) {
try {
const w0 = node.properties.ue_properties[`${k}_regex`]
return (w0 && w0!='.*') ? new RegExp(w0) : null;
} catch (e) {
return null
}
}
/*
The UseEverywhere object represents a single 'broadcast'. It generally contains
controller - the UE node that controls the broadcase
@@ -16,16 +29,24 @@ The UseEverywhere object represents a single 'broadcast'. It generally contains
output - the output that is being rebroadcast as a list (node_id, output_index)
title_regex, input_regex - the UE? matching rules
priority - priorty :)
graph - the graph or subgraph
*/
class UseEverywhere {
constructor() {
this.sending_to = [];
Object.assign(this, arguments[0]);
if (this.priority === undefined) this.priority = 0;
const from_node = get_real_node(this?.output[0]);
const to_node = get_real_node(this?.controller.id);
if (this.graph === undefined) this.graph = visible_graph();
const from_node = get_real_node(this?.output[0], this.graph);
const to_node = get_real_node(this?.controller.id, this.graph)
try {
if (this.control_node_input_index>=0) {
if (this.output[0]==-10) {
this.description = `source subgraph input slot ${this?.output[1]} ` +
`-> control "${display_name(to_node)}", ${to_node.inputs[this?.control_node_input_index].name} (${this?.controller.id}.${this?.control_node_input_index}) ` +
`"${this.type}" <- (priority ${this.priority})`;
}
else if (this.control_node_input_index>=0) {
this.description = `source "${display_name(from_node)}", ${from_node.outputs[this?.output[1]].name} (${this?.output[0]}.${this?.output[1]}) ` +
`-> control "${display_name(to_node)}", ${to_node.inputs[this?.control_node_input_index].name} (${this?.controller.id}.${this?.control_node_input_index}) ` +
`"${this.type}" <- (priority ${this.priority})`;
@@ -37,7 +58,6 @@ class UseEverywhere {
// for breakpointing
throw e;
}
// this.description = `source ${this?.output[0]}.${this?.output[1]} -> control ${this?.controller.id}.${this?.control_node_input_index} "${this.type}" <- (priority ${this.priority})`;
if (this.title_regex) this.description += ` - node title regex '${this.title_regex.source}'`;
if (this.input_regex) this.description += ` - input name regex '${this.input_regex.source}'`;
}
@@ -102,12 +122,12 @@ class UseEverywhere {
}
function validity_errors(params) {
if (!node_is_live(params.controller)) return `UE node ${params.output[0]} is not alive`;
if (!node_is_live(get_real_node(params.output[0]))) return `upstream node ${params.output[0]} is not alive`;
if (!node_is_live(params.controller)) return `UE node ${params.controller.id} is not alive`;
if (params.output[0]!=-10 && !node_is_live(get_real_node(params.output[0], params.graph))) return `upstream node ${params.output[0]} is not alive`;
return "";
}
class UseEverywhereList {
export class UseEverywhereList {
constructor() { this.ues = []; this.unmatched_inputs = []; }
differs_from(another_uel) {
@@ -119,42 +139,26 @@ class UseEverywhereList {
return false;
}
add_ue(node, control_node_input_index, type, output, title_regex, input_regex, group_regex, priority) {
add_ue(node, control_node_input_index, type, output, input_regex_override) {
const params = {
controller: node,
control_node_input_index: control_node_input_index,
type: type,
output: output,
title_regex: title_regex,
input_regex: input_regex,
group_regex: group_regex,
priority: priority
title_regex: regex_for(node, 'title'),
input_regex: input_regex_override || regex_for(node, 'input'),
group_regex: regex_for(node, 'group'),
priority: node.properties.ue_properties.priority || default_priority(node),
graph: node_graph(node),
};
const real_node = get_real_node(node.id);
if (!real_node) {
Logger.log_problem(`Node ${node.id} not found`, params);
return;
}
if (real_node.properties.group_restricted == 1) {
params.restrict_to = nodes_in_my_group(node.id);
params.priority += 0.1;
}
if (real_node.properties.group_restricted == 2) {
params.restrict_to = nodes_not_in_my_group(node.id);
params.priority += 0.1;
}
if (real_node.properties.color_restricted == 1) {
params.restrict_to = nodes_my_color(node.id, params.restrict_to);
params.priority += 0.3;
}
if (real_node.properties.color_restricted == 2) {
params.restrict_to = nodes_not_my_color(node.id, params.restrict_to);
params.priority += 0.3;
}
if (group_regex) {
params.restrict_to = nodes_in_groups_matching(group_regex, params.restrict_to);
}
if (real_node.properties["priority_boost"]) params.priority += real_node.properties["priority_boost"];
if (node.properties.ue_properties.group_restricted == 1) params.restrict_to = nodes_in_my_group(node);
if (node.properties.ue_properties.group_restricted == 2) params.restrict_to = nodes_not_in_my_group(node);
if (node.properties.ue_properties.color_restricted == 1) params.restrict_to = nodes_my_color(node, params.restrict_to);
if (node.properties.ue_properties.color_restricted == 2) params.restrict_to = nodes_not_my_color(node, params.restrict_to);
if (params.group_regex) params.restrict_to = nodes_in_groups_matching(params.group_regex, params.restrict_to, graph);
var error = ""
var ue = null;
@@ -168,7 +172,7 @@ class UseEverywhereList {
this.ues.push(ue);
Logger.log_detail(`Added ${ue.description}`)
} else {
Logger.log_problem(`Rejected ${ue?.description} because ${error}`, params);
Logger.log_problem(`Rejected ${ue?.description} because ${error}`);
}
}
@@ -200,38 +204,6 @@ class UseEverywhereList {
return matches[0];
}
print_all() {
this.ues.forEach((ue) => { console.log(ue.describe()); });
}
all_unmatched_inputs(type) {
return this.unmatched_inputs.filter((ui)=>ui.input.type==type);
}
all_nodes_with_unmatched_input(type) {
const result = new Set();
this.all_unmatched_inputs(type).forEach((ui) => {
result.add(display_name(ui.node));
})
return result;
}
all_unmatched_input_names(type) {
const result = new Set();
this.all_unmatched_inputs(type).forEach((ui) => {
result.add(ui.input.label ? ui.input.label : ui.input.name);
})
return result;
}
all_group_names() {
const result = new Set();
app.graph._groups.forEach((group) => {
result.add(group.title);
})
return result;
}
all_connected_inputs(for_node) {
const ue_connections = [];
this.ues.forEach((ue) => {
@@ -240,7 +212,7 @@ class UseEverywhereList {
ue_connections.push({
type : ue.type,
input_index : st.input_index,
control_node : get_real_node(ue.controller.id),
control_node : get_real_node(ue.controller.id, ue.graph),
control_node_input_index : ue.control_node_input_index,
sending_to : st.node,
});
@@ -257,32 +229,37 @@ class UseEverywhereList {
ue_connections.push({
type : ue.type,
input_index : st.input_index,
control_node : get_real_node(ue.controller.id),
control_node : get_real_node(ue.controller.id, ue.graph),
control_node_input_index : ue.control_node_input_index,
sending_to : st.node,
sending_to : st.node,
graph: ue.graph,
});
});
});
return ue_connections;
}
all_ue_connections_for(node_id) {
const ue_connections = [];
this.ues.forEach((ue) => {
ue.sending_to.forEach((st) => {
if (get_real_node(st.node.id).id==node_id || get_real_node(ue.controller.id).id==node_id) {
ue_connections.push({
type : ue.type,
input_index : st.input_index,
control_node : get_real_node(ue.controller.id),
control_node_input_index : ue.control_node_input_index,
sending_to : st.node,
});
add_ue_from_node(node) {
if (node.properties.ue_properties.seed_inputs) {
this.add_ue(node, -1, "INT", [node.id.toString(),0], regex_for(node, 'input'));
} else {
for (var i=0; i<node.inputs.length; i++) {
const connection = get_connection(node, i);
if (connection.link) {
const input_regex = (node.properties.ue_properties.prompt_regexes) ? prompt_regex(node,i) : undefined
this.add_ue(node, i, connection.type, [connection.link.origin_id.toString(), connection.link.origin_slot], input_regex);
}
});
});
return ue_connections;
}
}
}
}
export {UseEverywhereList, display_name}
const P_REGEXES = ['prompt', 'negative']
const PROMPT_REGEXES = [new RegExp(default_regex('prompt_regex')), new RegExp(default_regex('negative_regex'))]
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]
}
+75 -93
View File
@@ -1,10 +1,9 @@
import { GroupNodeHandler } from "../core/groupNode.js";
import { UseEverywhereList } from "./use_everywhere_classes.js";
import { add_ue_from_node, add_ue_from_node_in_group } from "./use_everywhere_nodes.js";
import { node_in_loop, node_is_live, is_connected, is_UEnode, Logger, get_real_node, 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";
import { master_graph, node_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
class GraphAnalyser extends Pausable {
static _instance;
@@ -17,20 +16,29 @@ class GraphAnalyser extends Pausable {
super('GraphAnalyser')
this.original_graphToPrompt = app.graphToPrompt;
this.ambiguity_messages = [];
this.latest_ues = null
}
async graph_to_prompt(cur_list) {
modify_graphs_recursively(graph, mods) {
const modifications = convert_to_links( this.analyse_graph(graph), null, graph )
mods.push( modifications );
if (!graph.extra) graph.extra = {}
graph.extra['links_added_by_ue'] = modifications.added_links.map(x=>x.id)
graph.nodes.filter((node)=>(node.subgraph)).forEach((node) => {this.modify_graphs_recursively(node.subgraph, mods);});
}
async graph_to_prompt() {
var p;
// Convert the virtual links into real connections
this.pause('graph_to_prompt')
try { // For each UseEverywhere object add its connections
const mods = convert_to_links(cur_list, -1);
try {
const mods = []
this.modify_graphs_recursively(master_graph(), mods);
// Now create the prompt using the ComfyUI original functionality and the patched graph
app.graph.extra['links_added_by_ue'] = mods.added_links;
p = await this.original_graphToPrompt.apply(app);
// Remove the added virtual links
mods.restorer()
//addedLinks.forEach(id => { app.graph.removeLink(id); });
mods.forEach((mod)=>{mod.restorer()})
} catch (e) {
Logger.log_error(e)
} finally {
@@ -45,92 +53,23 @@ class GraphAnalyser extends Pausable {
return p;
}
analyse_graph(wait_if_blocked, about_to_submit) {
analyse_visible_graph() { return this.analyse_graph(visible_graph()); }
analyse_master_graph() { return this.analyse_graph(master_graph()); }
wait_to_analyse_visible_graph() { return this.wait_to_analyse_graph(visible_graph()); }
wait_to_analyse_master_graph() { return this.wait_to_analyse_graph(master_graph()); }
wait_to_analyse_graph(graph) {
if (this.paused()) {
if (wait_if_blocked) {
Logger.log_problem("Don't know how to wait")
}
return null
Logger.log_problem("Don't know how to wait", null, true);
}
this.ambiguity_messages = [];
var p = { workflow:app.graph.serialize() };
const treat_bypassed_as_live = settingsCache.getSettingValue("Use Everywhere.Options.connect_to_bypassed")
const live_nodes = p.workflow.nodes.filter((node) => node_is_live(node, treat_bypassed_as_live))
// Create a UseEverywhereList and populate it from all live (not bypassed) UE nodes
const ues = new UseEverywhereList();
live_nodes.filter((node) => is_UEnode(node)).forEach(node => { add_ue_from_node(ues, node); })
// and nodes in group nodes
live_nodes.filter((node) => (get_real_node(node.id) && GroupNodeHandler.isGroupNode(get_real_node(node.id)))).forEach( groupNode => {
const group_data = GroupNodeHandler.getGroupData(get_real_node(groupNode.id));
group_data.nodeData.nodes.filter((node) => is_UEnode(node)).forEach(node => {
add_ue_from_node_in_group(ues, node, groupNode.id, group_data);
})
})
// List all unconnected inputs on non-UE nodes which are connectable
const connectable = []
live_nodes.filter((node) => !is_UEnode(node)).forEach(node => {
const real_node = get_real_node(node.id);
return this.analyse_graph(graph);
}
if (real_node && !real_node.properties.rejects_ue_links) {
var gpData = GroupNodeHandler.getGroupData(real_node);
const isGrp = !!gpData;
const o2n = isGrp ? Object.entries(gpData.oldToNewInputMap) : null;
//if (!real_node._widget_name_map) real_node._widget_name_map = real_node.widgets?.map(w => w.name) || [];
real_node.inputs?.forEach((input,index) => {
if (is_connected(input, treat_bypassed_as_live)) return;
if (real_node.reject_ue_connection && real_node.reject_ue_connection(input)) return;
if (real_node._getWidgetByName(input.name) && !(real_node.properties['widget_ue_connectable'] && real_node.properties['widget_ue_connectable'][input.name])) return;
connectable.push({node, input, index, isGrp, real_node, o2n});
})
}
})
// see if we can connect them
const links_added = new Set();
connectable.forEach(({node, input, index, isGrp, real_node, o2n}) => {
/*
if isGrp, then
node refers to the outer node (group node), real_node to the inner node.
input and index are on real_node
if not isGRp, node==real_node
*/
var ue = ues.find_best_match(real_node, input, this.ambiguity_messages);
if (ue) {
const upstream_node_id = ue.output[0]
const real_upstream_node = get_real_node(upstream_node_id);
var effective_output = [ue.output[0], ue.output[1]]; // [node_id, slot]
if (upstream_node_id!=real_upstream_node.id) { // the upstream node is a group node, so get the inner node and slot
const upGpData = GroupNodeHandler.getGroupData(real_upstream_node);
const up_inner_node = upGpData.newToOldOutputMap[ue.output[1]].node;
const up_inner_node_index = up_inner_node.index;
const up_inner_node_id = real_upstream_node.getInnerNodes()[up_inner_node_index].id;
const up_inner_node_slot = upGpData.newToOldOutputMap[ue.output[1]].slot;
effective_output = [`${up_inner_node_id}`, up_inner_node_slot];
}
links_added.add({
//"downstream":real_target_node.id, "downstream_slot":real_target_node_slot,
"downstream":real_node.id, "downstream_slot":index,
"upstream":effective_output[0], "upstream_slot":effective_output[1],
"controller":ue.controller.id,
"type":ue.type
});
}
});
app.graph.extra['ue_links'] = Array.from(links_added)
if (this.ambiguity_messages.length) Logger.log_problem("Ambiguous connections", this.ambiguity_messages, true);
// if there are loops report them and raise an exception
if (about_to_submit && settingsCache.getSettingValue('Use Everywhere.Options.checkloops')) {
maybe_check_for_loops() {
if (settingsCache.getSettingValue('Use Everywhere.Options.checkloops')) {
try {
node_in_loop(live_nodes, links_added);
} catch (e) {
@@ -143,8 +82,51 @@ class GraphAnalyser extends Pausable {
throw new Error(`Loop Detected ${e.stack}, ${e.ues}`, {"cause":e});
}
}
}
return ues;
analyse_graph(graph) {
this.ambiguity_messages = [];
const treat_bypassed_as_live = settingsCache.getSettingValue("Use Everywhere.Options.connect_to_bypassed") || this.connect_to_bypassed
const live_nodes = graph.nodes.filter((node) => node_is_live(node, treat_bypassed_as_live))
// Create a UseEverywhereList and populate it from all live (not bypassed) UE nodes
const ues = new UseEverywhereList();
live_nodes.filter((node) => is_UEnode(node)).filter((node)=>node_is_live(node,false)).forEach(node => { ues.add_ue_from_node(node); })
// List all unconnected inputs on non-UE nodes which are connectable
const connectable = []
live_nodes.filter((node) => !is_UEnode(node)).forEach(node => {
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 (is_connected(input, treat_bypassed_as_live, node_graph(node))) return;
if (node.reject_ue_connection && node.reject_ue_connection(input)) return;
if (node._getWidgetByName(input.name) && !(node.properties?.ue_properties?.widget_ue_connectable && node.properties.ue_properties.widget_ue_connectable[input.name])) return;
connectable.push({node, input, index});
})
}
})
// see if we can connect them
const links_added = new Set();
connectable.forEach(({node, input, index}) => {
var ue = ues.find_best_match(node, input, this.ambiguity_messages);
if (ue) {
links_added.add({
"downstream":node.id, "downstream_slot":index,
"upstream":ue.output[0], "upstream_slot":ue.output[1],
"controller":ue.controller.id,
"type":ue.type
});
}
});
graph.extra['ue_links'] = Array.from(links_added)
if (this.ambiguity_messages.length) Logger.log_problem("Ambiguous connections", this.ambiguity_messages, true);
this.latest_ues = ues;
return this.latest_ues;
}
}
-113
View File
@@ -1,113 +0,0 @@
import { handle_bypass, get_real_node, get_group_node } from "./use_everywhere_utilities.js";
import { app } from "../../scripts/app.js";
const CONVERTED_TYPE = "converted-widget";
// import {CONVERTED_TYPE} from "../../extensions/core/widgetInputs.js"
/*
If a widget hasn't been converted, just get it's value
If it has, *try* to go upstream
*/
function get_widget_or_input_values(node_obj, widget_id) {
if (node_obj.widgets[widget_id]?.type.startsWith(CONVERTED_TYPE)) {
try {
const name = node_obj.widgets[widget_id].name;
const input_id = node_obj.inputs.findIndex((input) => input.name==name);
const connection = get_connection(node_obj, input_id, "STRING");
const upstream_node_obj = get_real_node(connection.link.origin_id.toString());
const widget = upstream_node_obj.widgets.find((w) => w.name.toLowerCase() == upstream_node_obj.outputs[connection.link.origin_slot].name.toLowerCase());
return widget.value;
} catch (error) {
return "NOT CONNECTED DONT MATCH";
}
}
return node_obj.widgets[widget_id].value;
}
function add_ue_from_node_in_group(ues, node, group_node_id, group_data) {
const group_node = get_real_node(group_node_id);
const ue_node = group_node.getInnerNodes()[node.index];
ue_node.in_group_with_data = group_data;
ue_node.getInnerNodesOfGroup = group_node.getInnerNodes;
add_ue_from_node(ues, ue_node)
}
function get_available_input_name(inputs, the_input, type) {
const used_names = [];
inputs.forEach((input) => { if (input!=the_input) used_names.push(input.name); });
const base = `UE ${type.toLowerCase()}`;
if (!used_names.includes(base)) return base;
for (var i=2; ;i++) {
if (!used_names.includes(`${base}${i}`)) return `${base}${i}`;
}
}
function get_connection(node, i, override_type) {
const in_link = node?.inputs[i]?.link;
var type = override_type;
var link = undefined;
if (in_link) {
if (!override_type) type = get_real_node(node.id.toString())?.input_type[i];
link = handle_bypass(app.graph.links[in_link],type);
} else if (node.in_group_with_data) {
if (node.in_group_with_data.linksTo[node.index] && node.in_group_with_data.linksTo[node.index][i]) {
const group_style_link = node.in_group_with_data.linksTo[node.index][i];
link = { "origin_id":node.getInnerNodesOfGroup()[group_style_link[0]].id, "origin_slot" : group_style_link[1] };
if (!override_type) type = group_style_link[5];
} else { // group external input
const group_node = get_group_node(node.id);
const group_node_input = group_node.inputs[node.in_group_with_data.oldToNewInputMap[node.index][i]];
const link_n = group_node_input.link;
if (link_n) {
link = app.graph.links[link_n];
if (!override_type) type = app.graph._nodes_by_id[link.origin_id].outputs[link.origin_slot].type;
// update the group input node... and the link type
group_node_input.type = type;
group_node_input.name = get_available_input_name(group_node.inputs, group_node_input, type);
link.type = type;
}
}
}
return { link:link, type:type }
}
/*
Add UseEverywhere broadcasts from this node to the list
*/
function add_ue_from_node(ues, node) {
if (node.type === "Seed Everywhere") ues.add_ue(node, -1, "INT", [node.id.toString(),0],
undefined, new RegExp("seed|随机种"), undefined, 5);
if (node.type === "Anything Everywhere?") {
const connection = get_connection(node, 0);
if (connection.link) {
const node_obj = get_real_node(node.id.toString());
const w0 = get_widget_or_input_values(node_obj,0);
const r0 = new RegExp(w0);
const w1 = get_widget_or_input_values(node_obj,1);
const r1 = (w1.startsWith('+')) ? w1 : new RegExp(w1);
const w2 = get_widget_or_input_values(node_obj,2);
const r2 = (w2 && w2!=".*") ? new RegExp(w2) : null;
ues.add_ue(node, 0, connection.type, [connection.link.origin_id.toString(), connection.link.origin_slot], r0, r1, r2, 10);
}
}
if (node.type === "Prompts Everywhere") {
for (var i=0; i<2; i++) {
const connection = get_connection(node, i);
if (connection.link) ues.add_ue(node, i, connection.type, [connection.link.origin_id.toString(), connection.link.origin_slot],
undefined, new RegExp(["(_|\\b)pos(itive|_|\\b)|^prompt|正面","(_|\\b)neg(ative|_|\\b)|负面"][i]), undefined, 5);
}
}
if (node.type === "Anything Everywhere") {
const connection = get_connection(node, 0);
if (connection.link) ues.add_ue(node, 0, connection.type, [connection.link.origin_id.toString(),connection. link.origin_slot], undefined, undefined, undefined, 2);
}
if (node.type === "Anything Everywhere3") {
for (var i=0; i<3; i++) {
const connection = get_connection(node, i);
if (connection.link) ues.add_ue(node, i, connection.type, [connection.link.origin_id.toString(), connection.link.origin_slot]);
}
}
}
export {add_ue_from_node, add_ue_from_node_in_group}
+48 -49
View File
@@ -2,13 +2,16 @@ 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 { has_priority_boost } from "./use_everywhere_utilities.js";
import { VERSION } 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";
export const SETTINGS = [
{
id: "Use Everywhere.About",
name: "Version 6.2.2",
name: `Version ${VERSION}`,
type: () => {return document.createElement('span')},
},
{
@@ -50,6 +53,21 @@ export const SETTINGS = [
defaultValue: true,
onChange: settingsCache.onSettingChange,
},
{
id: "Use Everywhere.Graphics.preserve edit window position",
name: "Save restrictions edit window position",
type: "boolean",
defaultValue: false,
onChange: settingsCache.onSettingChange,
tooltip: "If off, the edit window appears where the mouse is"
},
{
id: "Use Everywhere.Graphics.tooltips",
name: "Show restrictions as tooltip",
type: "boolean",
defaultValue: true,
onChange: settingsCache.onSettingChange,
},
{
id: "Use Everywhere.Options.connect_to_bypassed",
name: "Connect to bypassed nodes",
@@ -112,35 +130,11 @@ function submenu(properties, property, options, e, menu, node) {
}
}
const GROUP_RESTRICTION_OPTIONS = ["No restrictions", "Send only within group", "Send only not within group"]
function group_restriction_submenu(value, options, e, menu, node) {
submenu(node.properties, "group_restricted", GROUP_RESTRICTION_OPTIONS, e, menu, node);
}
const COLOR_RESTRICTION_OPTIONS = ["No restrictions", "Send only to same color", "Send only to different color"]
function color_restriction_submenu(value, options, e, menu, node) {
submenu(node.properties, "color_restricted", COLOR_RESTRICTION_OPTIONS, e, menu, node);
}
function priority_boost_submenu(value, options, e, menu, node) {
const current = (node.properties["priority_boost"] ? node.properties["priority_boost"] : 0) + 1;
const submenu = new LiteGraph.ContextMenu(
[0,1,2,3,4,5,6,7,8,9],
{ event: e, callback: function (v) {
node.properties["priority_boost"] = parseInt(v);
LinkRenderController.instance().mark_link_list_outdated();
},
parentMenu: menu, node:node}
)
const current_element = submenu.root.querySelector(`:nth-child(${current})`);
if (current_element) current_element.style.borderLeft = "2px solid #484";
}
function highlight_selected(submenu_root, node, names) {
names.forEach((name, i) => {
const current_element = submenu_root?.querySelector(`:nth-child(${i+1})`);
if (current_element) {
if (node.properties['widget_ue_connectable'][name]) {
if (node.properties.ue_properties['widget_ue_connectable'][name]) {
current_element.style.borderLeft = "2px solid #484";
} else {
current_element.style.borderLeft = "";
@@ -152,7 +146,8 @@ function highlight_selected(submenu_root, node, names) {
}
function widget_ue_submenu(value, options, e, menu, node) {
if (!(node.properties['widget_ue_connectable'])) node.properties['widget_ue_connectable'] = {};
if (!(node.properties.ue_properties)) node.properties.ue_properties = {}
if (!(node.properties.ue_properties.widget_ue_connectable)) node.properties.ue_properties.widget_ue_connectable = {};
const linkedWidgets = new Set()
node.widgets
@@ -169,7 +164,7 @@ function widget_ue_submenu(value, options, e, menu, node) {
const submenu = new LiteGraph.ContextMenu(
names,
{ event: e, callback: function (v) {
node.properties['widget_ue_connectable'][v] = !!!node.properties['widget_ue_connectable'][v];
node.properties.ue_properties.widget_ue_connectable[v] = !!!node.properties.ue_properties.widget_ue_connectable[v];
LinkRenderController.instance().mark_link_list_outdated();
highlight_selected(this.parentElement, node, names)
return true; // keep open
@@ -179,6 +174,21 @@ function widget_ue_submenu(value, options, e, menu, node) {
highlight_selected(submenu.root, node, names)
}
export function add_extra_menu_items(node_or_node_type, ioio) {
if (node_or_node_type.ue_extra_menu_items_added) return
const getExtraMenuOptions = node_or_node_type.getExtraMenuOptions;
node_or_node_type.getExtraMenuOptions = function(_, options) {
getExtraMenuOptions?.apply(this, arguments);
if (is_UEnode(this)) {
node_menu_settings(options, this);
} else {
non_ue_menu_settings(options, this);
}
ioio(options,'callback',`menu option on ${this.id}`);
}
node_or_node_type.ue_extra_menu_items_added = true
}
export function non_ue_menu_settings(options, node) {
options.push(null);
options.push(
@@ -202,30 +212,19 @@ export function non_ue_menu_settings(options, node) {
export function node_menu_settings(options, node) {
options.push(null);
if (has_priority_boost(node)) options.push(
options.push(
{
content: "Priority Boost",
has_submenu: true,
callback: priority_boost_submenu,
}
content: "Edit restrictions",
callback: edit_restrictions,
}
)
options.push(
{
content: "Group Restrictions",
has_submenu: true,
callback: group_restriction_submenu,
},
{
content: "Color Restrictions",
has_submenu: true,
callback: color_restriction_submenu,
},
{
content: "Convert to real links",
callback: async () => {
const ues = GraphAnalyser.instance().analyse_graph(true);
convert_to_links(ues, node.id);
app.graph.remove(node);
const ues = GraphAnalyser.instance().wait_to_analyse_visible_graph();
convert_to_links(ues, node);
visible_graph().remove(node);
}
}
)
@@ -246,10 +245,10 @@ export function canvas_menu_settings(options) {
content: "Convert all UEs to real links",
callback: async () => {
if (window.confirm("This will convert all links created by Use Everywhere to real links, and delete all the Use Everywhere nodes. Is that what you want?")) {
const ues = GraphAnalyser.instance().analyse_graph(true);
const ues = GraphAnalyser.instance().wait_to_analyse_visible_graph();
LinkRenderController.instance().pause("convert");
try {
convert_to_links(ues, -1);
convert_to_links(ues, visible_graph());
remove_all_ues(true);
} finally {
app.graph.change();
+37 -4
View File
@@ -1,6 +1,39 @@
import { app } from "../../scripts/app.js";
export function visible_graph() { return app.canvas.graph }
export function visible_graph_id() { return app.canvas.graph.id }
export function node_graph_id(node) { return node.graph.id }
export function in_visible_graph(node) { return node_graph_id(node) == visible_graph_id() }
export function master_graph() { return app.graph }
export function master_graph_id() { return master_graph().id }
export function visible_graph() { return app.canvas.graph }
export function visible_graph_id() { return visible_graph().id }
export function node_graph(node) { return node.graph }
export function node_graph_id(node) { return node_graph(node).id }
export function in_visible_graph(node) {
try {
return node_graph_id(node) == visible_graph_id()
} catch {
return false
}
}
export function get_subgraph_input_type(graph, slot) { return graph.inputNode.slots[slot].type }
export function link_is_from_subgraph_input(link) { return link.origin_id==-10 }
class WrappedInputNode {
constructor(subgraph_input_node) {
this.subgraph_input_node = subgraph_input_node;
this.graph = subgraph_input_node.subgraph;
}
connect(output_index, input_node, input_index) {
this.graph.last_link_id += 1
this.graph.links[this.graph.last_link_id] = new LLink(this.graph.last_link_id, this.subgraph_input_node.slots[output_index].type, -10, output_index, input_node.id, input_index)
input_node.inputs[input_index].link = this.graph.last_link_id;
return this.graph.links[this.graph.last_link_id]
}
}
export function wrap_input(subgraph_input_node) {
return new WrappedInputNode(subgraph_input_node);
}
+38 -32
View File
@@ -1,25 +1,25 @@
import { Logger, get_real_node, get_group_node, Pausable } from "./use_everywhere_utilities.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { Logger, get_real_node, Pausable } from "./use_everywhere_utilities.js";
import { app } from "../../scripts/app.js";
import { settingsCache } from "./use_everywhere_cache.js";
import { in_visible_graph } from "./use_everywhere_subgraph_utils.js";
import { in_visible_graph, node_graph } from "./use_everywhere_subgraph_utils.js";
import { maybe_show_tooltip } from "./tooltip_window.js";
function nodes_in_my_group(node_id) {
function nodes_in_my_group(node) {
const nodes_in = new Set();
app.graph._groups.forEach((group) => {
node_graph(node)._groups.forEach((group) => {
if (!app.canvas.selected_group_moving) group.recomputeInsideNodes();
if (group._nodes?.find((node) => { return (node.id===node_id) } )) {
group._nodes.forEach((node) => { nodes_in.add(node.id) } )
if (group._nodes?.find((nd) => { return (nd.id===node.id) } )) {
group._nodes.forEach((nd) => { nodes_in.add(nd.id) } )
}
});
return [...nodes_in];
}
function nodes_not_in_my_group(node_id) {
const nid = nodes_in_my_group(node_id);
function nodes_not_in_my_group(node) {
const nid = nodes_in_my_group(node);
const nodes_not_in = [];
app.graph._nodes.forEach((node) => {
if (!nid.includes(node.id)) nodes_not_in.push(node.id);
node_graph(node)._nodes.forEach((nd) => {
if (!nid.includes(nd.id)) nodes_not_in.push(nd.id);
});
return nodes_not_in;
}
@@ -44,31 +44,31 @@ function nodes_in_groups_matching(regex, already_limited_to) {
}
function nodes_my_color(node_id, already_limited_to) {
function nodes_my_color(node, already_limited_to) {
const nodes_in = new Set();
const color = get_real_node(node_id).color;
const color = node.color;
if (already_limited_to) {
already_limited_to.forEach((nid) => {
if (get_real_node(nid).color==color) nodes_in.add(nid)
if (get_real_node(nid, node_graph(node)).color==color) nodes_in.add(nid)
})
} else {
app.graph._nodes.forEach((node) => {
if (node.color==color) nodes_in.add(node.id)
node_graph(node)._nodes.forEach((nd) => {
if (nd.color==color) nodes_in.add(nd.id)
})
}
return [...nodes_in];
}
function nodes_not_my_color(node_id, already_limited_to) {
function nodes_not_my_color(node, already_limited_to) {
const nodes_in = new Set();
const color = get_real_node(node_id).color;
const color = get_real_node(node.id).color;
if (already_limited_to) {
already_limited_to.forEach((nid) => {
if (get_real_node(nid).color!=color) nodes_in.add(nid)
if (get_real_node(nid, node_graph(node)).color!=color) nodes_in.add(nid)
})
} else {
app.graph._nodes.forEach((node) => {
if (node.color!=color) nodes_in.add(node.id)
node_graph(node)._nodes.forEach((nd) => {
if (nd.color!=color) nodes_in.add(nd.id)
})
}
return [...nodes_in];
@@ -85,9 +85,10 @@ function indicate_restriction(ctx, title_height) {
}
function update_input_label(node, slot, app) {
if (node.input_type[slot]) {
node.inputs[slot].label = node.input_type[slot];
node.inputs[slot].color_on = app.canvas.default_connection_color_byType[node.input_type[slot]];
const type = node.inputs[slot].type
if (type) {
node.inputs[slot].label = type;
node.inputs[slot].color_on = app.canvas.default_connection_color_byType[type];
} else {
node.inputs[slot].label = "anything";
node.inputs[slot].color_on = undefined;
@@ -152,7 +153,7 @@ class LinkRenderController extends Pausable {
_request_link_list_update() {
try {
const ues = this.the_graph_analyser.analyse_graph(false)
const ues = this.the_graph_analyser.analyse_visible_graph()
if (ues==null) return false // graph analyser was paused
this.ue_list = ues;
if (this.ue_list.differs_from(this.last_used_ue_list)) app.graph.change();
@@ -194,7 +195,7 @@ class LinkRenderController extends Pausable {
try {
this.pause('highlight_ue_connections')
if (!this._list_ready()) return;
const unconnected_connectables = node.properties?.widget_ue_connectable ? new Set(Object.keys(node.properties.widget_ue_connectable).filter((name) => (node.properties.widget_ue_connectable[name]))) : new Set()
const unconnected_connectables = node.properties?.ue_properties?.widget_ue_connectable ? new Set(Object.keys(node.properties.ue_properties.widget_ue_connectable).filter((name) => (node.properties.ue_properties.widget_ue_connectable[name]))) : new Set()
node.inputs.filter((input)=>(input.link)).forEach((input) => { unconnected_connectables.delete(input.name) });
if (this.ue_list.all_connected_inputs) {
@@ -268,8 +269,8 @@ class LinkRenderController extends Pausable {
}
node_in_ueconnection(ue_connection, id) {
if (ue_connection.control_node && get_group_node(ue_connection.control_node.id)?.id == id) return true
if (ue_connection.sending_to && get_group_node(ue_connection.sending_to.id)?.id == id) return true
if (ue_connection.control_node?.id == id) return true
if (ue_connection.sending_to?.id == id) return true
}
any_node_in_ueconnection(ue_connection, list_of_nodes) {
@@ -277,7 +278,7 @@ class LinkRenderController extends Pausable {
}
render_all_ue_links(ctx) {
maybe_show_tooltip()
if (this.paused()) return;
try {
this.pause('render_all_ue_links')
@@ -340,8 +341,8 @@ class LinkRenderController extends Pausable {
_render_ue_link(ue_connection, ctx, animate) {
try {
const node = get_real_node(ue_connection.sending_to.id);
const graph = ue_connection.graph
const node = get_real_node(ue_connection.sending_to.id, graph);
/* this is the end node; get the position of the input */
var pos2 = node.getConnectionPos(true, ue_connection.input_index, this.slot_pos1);
@@ -350,7 +351,12 @@ class LinkRenderController extends Pausable {
which is displayed with an output: the class records control_node_input_index as -ve (-1 => 0, -2 => 1...) */
const input_source = (ue_connection.control_node_input_index >= 0);
const source_index = input_source ? ue_connection.control_node_input_index : -1-ue_connection.control_node_input_index;
const pos1 = get_group_node(ue_connection.control_node.id).getConnectionPos(input_source, source_index, this.slot_pos2);
const pos1 = graph._nodes_by_id[ue_connection.control_node.id]?.getConnectionPos(input_source, source_index, this.slot_pos2);
if (!pos1) {
Logger.problem(`Couldn't find position for UE link ${ue_connection}.`,null,true)
return;
}
/* get the direction that we start and end */
const delta_x = pos2[0] - pos1[0];
+147 -140
View File
@@ -1,6 +1,53 @@
import { app } from "../../scripts/app.js";
import { GroupNodeHandler } from "../core/groupNode.js";
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";
export const VERSION = "7.0"
export function create( tag, clss, parent, properties ) {
const nd = document.createElement(tag);
if (clss) clss.split(" ").forEach((s) => nd.classList.add(s))
if (parent) parent.appendChild(nd);
if (properties) Object.assign(nd, properties);
return nd;
}
/*
Return 1 if x is a later version than y (or y is not defined)
Return -1 if x is an earlier version than y (or x is not defined)
Return 0 if they are the same version
*/
function version_compare(x,y) {
if (x==y) return 0
if (!y) return 1
if (!x) return -1
const xbits = x.split('.')
const ybits = y.split('.')
var result = 0
for (var i=0; result!=0 && i<Math.min(xbits.length, ybits.length); i++) {
if (parseInt(xbits[i]) < parseInt(ybits[i])) result = -1
if (parseInt(xbits[i]) > parseInt(ybits[i])) result = 1
}
if (result==0) {
if (xbits.length < ybits.length) result = -1
if (xbits.length > ybits.length) result = 1
}
return result
}
export function version_at_least(x,y) {
return (version_compare(x,y) >= 0)
}
/*
Return the node object for this node_id.
*/
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 {
static LIMITED_LOG_BLOCKED = false;
@@ -9,10 +56,14 @@ class Logger {
static log_error(message) { console.error(message) }
static log(message, array, limited) {
static log(message, foreachable, limited) {
if (limited && Logger.check_limited()) return
console.log(message);
if (array) for (var i=0; i<array.length; i++) { console.log(array[i]) }
try {
foreachable?.forEach((x)=>{console.log(x)})
} catch {
let a;
}
}
static check_limited() {
@@ -64,32 +115,8 @@ class GraphConverter {
Logger.log_detail("stored node_input_map", this.node_input_map);
}
on_node_created(node) {
if (this.graph_being_configured) {
/*
If the graph is being configured, we are still loading old nodes.
These might need to be converted, but we can't do that yet
*/
return;
}
if (!(node.properties)) node.properties = {};
if (node.properties.widget_ue_connectable) {
console.log(`already has widget_ue_connectable`)
return
}
node.properties['widget_ue_connectable'] = {}
console.log(`added widget_ue_connectable`)
}
clean_ue_node(node) {
const gpData = GroupNodeHandler.getGroupData(node)
const isGrp = !!gpData;
if (isGrp) {
let a;
}
var expected_inputs = 1
if (node.type == "Seed Everywhere") expected_inputs = 0
if (node.type == "Prompts Everywhere") expected_inputs = 2
@@ -97,8 +124,8 @@ class GraphConverter {
if (node.type == "Anything Everywhere?") expected_inputs = 4
// remove all the 'anything' inputs (because they may be duplicated)
const removed = node.inputs.filter(i=>i.label=='anything')
node.inputs = node.inputs.filter(i=>i.label!='anything')
const removed = node.inputs.filter(i=>(i.label=='anything' || i.label=='*'))
node.inputs = node.inputs.filter(i=>(i.label!='anything' && i.label!='*'))
// add them back as required
while (node.inputs.length < expected_inputs) { node.inputs.push(removed.pop()) }
// the input comes before the regex widgets in UE?
@@ -109,59 +136,65 @@ class GraphConverter {
}
// fix the localized names
node.inputs = node.inputs.map((input) => {
if (input.localized_name=='anything') input.localized_name = input.name
if (!input.localized_name || input.localized_name.startsWith('anything')) input.localized_name = input.name
return input;
})
// set types to match
node.input_type = node.inputs.map((i)=>{
var type = i.type;
if (type=='*') {
if (i.link) type = app.graph.links[i.link].type;
else type = (i.label && i.label!='anything') ? i.label : i.name;
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 = (input.label && input.label!='anything') ? input.label : input.name
}
}
return type
})
});
Logger.log_detail(`clean_ue_node ${node.id} (${node.type})`, node.inputs, node.input_type);
Logger.log_detail(`clean_ue_node ${node.id} (${node.type})`, node.inputs);
}
convert_if_pre_116(node) {
if (!node) return;
if (!(node.properties)) node.properties = {};
if (node.IS_UE) this.clean_ue_node(node)
if (node.properties.widget_ue_connectable) return
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 (!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;
}
node.properties['widget_ue_connectable'] = {}
if (!node.properties.ue_properties) node.properties.ue_properties = {}
node.properties.ue_properties['widget_ue_connectable'] = {}
const widget_names = node.widgets?.map(w => w.name) || [];
if (!(this.node_input_map[node.id])) {
Logger.log_detail(`node ${node.id} (${node.type} has no node_input_map`);
} else {
this.node_input_map[node.id].filter((input_name)=>widget_names.includes(input_name)).forEach((input_name) => {
node.properties['widget_ue_connectable'][input_name] = true;
node.properties.ue_properties['widget_ue_connectable'][input_name] = true;
this.did_conversion = true;
Logger.log_info(`node ${node.id} widget ${input_name} marked as accepting UE because it was an input when saved`);
});
}
//node.properties.ue116converted = true;
}
remove_saved_ue_links() {
if (app.graph.extra?.links_added_by_ue) {
app.graph.extra.links_added_by_ue.forEach((link) => { app.graph.links.delete(link); })
remove_saved_ue_links_recursively(graph) {
if (graph.extra?.links_added_by_ue) {
graph.extra.links_added_by_ue.forEach((link_id) => { app.graph.links.delete(link_id); })
}
graph.nodes.filter((node)=>(node.subgraph)).forEach((node) => {this.remove_saved_ue_links_recursively(node.subgraph);});
}
}
export const graphConverter = GraphConverter.instance();
@@ -175,9 +208,8 @@ class LoopError extends Error {
}
}
function find_all_upstream(node_id, links_added) {
function find_all_upstream(node, links_added) {
const all_upstream = [];
const node = get_real_node(node_id);
node?.inputs?.forEach((input) => { // normal links
const link_id = input.link;
if (link_id) {
@@ -186,48 +218,23 @@ function find_all_upstream(node_id, links_added) {
}
});
links_added.forEach((la)=>{ // UE links
if (get_real_node(la.downstream).id==node.id) {
if (la.downstream==node.id) {
all_upstream.push({id:la.upstream, slot:la.upstream_slot, ue:la.controller.toString()})
}
});
if (node.id != get_group_node(node.id).id) { // node is in group
const grp_nd = get_group_node(node.id).id;
const group_data = GroupNodeHandler.getGroupData(get_group_node(node.id));
const indx = group_data.nodeData.nodes.findIndex((n)=>n.pos[0]==node.pos[0] && n.pos[1]==node.pos[1]);
if (indx>=0) {
if (GroupNodeHandler.getGroupData(app.graph._nodes_by_id[grp_nd])?.linksTo?.[indx] ) { // links within group
Object.values(GroupNodeHandler.getGroupData(app.graph._nodes_by_id[grp_nd]).linksTo[indx]).forEach((internal_link) => {
all_upstream.push({id:`${grp_nd}:${internal_link[0]}`, slot:internal_link[1]});
});
}
if (GroupNodeHandler.getGroupData(app.graph._nodes_by_id[grp_nd]).oldToNewInputMap?.[indx]) { // links out of group
Object.values(GroupNodeHandler.getGroupData(app.graph._nodes_by_id[grp_nd]).oldToNewInputMap?.[indx]).forEach((groupInput) => {
const link_id = get_group_node(node.id).inputs?.[groupInput]?.link;
if (link_id) {
const link = app.graph.links[link_id];
if (link) all_upstream.push({id:link.origin_id, slot:link.origin_slot});
}
})
}
}
}
return all_upstream;
}
function recursive_follow(node_id, start_node_id, links_added, stack, nodes_cleared, ues, count, slot) {
const node = get_real_node(node_id);
if (slot>=0 && GroupNodeHandler.isGroupNode(node)) { // link into group
const mapped = GroupNodeHandler.getGroupData(node).newToOldOutputMap[slot];
return recursive_follow(`${node.id}:${mapped.node.index}`, start_node_id, links_added, stack, nodes_cleared, ues, count, mapped.slot);
}
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.id, links_added).forEach((upstream) => {
find_all_upstream(node, links_added).forEach((upstream) => {
if (upstream.ue) ues.push(upstream.ue);
count = recursive_follow(upstream.id, start_node_id, links_added, stack, nodes_cleared, ues, count, upstream.slot);
count = recursive_follow(upstream, links_added, stack, nodes_cleared, ues, count, upstream.slot);
if (upstream.ue) ues.pop();
})
@@ -244,11 +251,11 @@ 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(get_real_node(n.id).id));
live_nodes.forEach((n)=>nodes_to_check.push(n));
var count = 0;
while (nodes_to_check.length>0) {
const node_id = nodes_to_check.pop();
count += recursive_follow(node_id, node_id, links_added, [], nodes_cleared, [], 0, -1);
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`)
@@ -274,10 +281,11 @@ Given a link object, and the type of the link,
go upstream, following links with the same type, until you find a parent node which isn't bypassed.
If either type or original link is null, or if the upstream thread ends, return null
*/
function handle_bypass(original_link, type) {
function handle_bypass(original_link, type, graph) {
if (!type || !original_link) return null;
var link = original_link;
var parent = get_real_node(link.origin_id);
if (link_is_from_subgraph_input(link)) return link
var parent = get_real_node(link.origin_id, graph);
if (!parent) return null;
while (node_is_bypassed(parent)) {
if (!parent.inputs) return null;
@@ -285,69 +293,25 @@ function handle_bypass(original_link, type) {
if (parent?.inputs[link.origin_slot]?.type == type) link_id = parent.inputs[link.origin_slot].link; // try matching number first
else link_id = parent.inputs.find((input)=>input.type==type)?.link;
if (!link_id) { return null; }
link = app.graph.links[link_id];
parent = get_real_node(link.origin_id);
link = graph.links[link_id];
parent = get_real_node(link.origin_id, graph);
}
return link;
}
function all_group_nodes() {
return app.graph._nodes.filter((node) => GroupNodeHandler.isGroupNode(node));
}
function is_in_group(node_id, group_node) {
return group_node.getInnerNodes().find((inner_node) => (inner_node.id==node_id));
}
/*
Return the group node if this node_id is part of a group, else return the node itself.
Returns a full node object
*/
function get_group_node(node_id) {
const nid = node_id.toString();
var gn = app.graph._nodes_by_id[nid];
if (!gn && nid.includes(':')) gn = app.graph._nodes_by_id[nid.split(':')[0]];
if (!gn) gn = all_group_nodes().find((group_node) => is_in_group(nid, group_node));
if (!gn) Logger.log_error(`get_group node couldn't find ${nid}`)
return gn;
}
/*
Return the node object for this node_id.
- if it's in _nodes_by_id return it
- if it is of the form x:y find it in group node x
- if it is the real node number of something in a group, get it from the group
*/
function get_real_node(node_id) {
const nid = node_id.toString();
var rn = app.graph._nodes_by_id[nid];
if (!rn && nid.includes(':')) rn = app.graph._nodes_by_id[nid.split(':')[0]]?.getInnerNodes()[nid.split(':')[1]]
if (!rn) {
all_group_nodes().forEach((node) => {
if (!rn) rn = node.getInnerNodes().find((inner_node) => (inner_node.id==nid));
})
}
if (!rn) Logger.log_info(`get_real_node couldn't find ${node_id} - ok during loading, shortly after node deletion etc.`)
return rn;
}
function get_all_nodes_within(node_id) {
const node = get_group_node(node_id);
if (GroupNodeHandler.isGroupNode(node)) return node.getInnerNodes();
return [];
}
/*
Does this input connect upstream to a live node?
*/
function is_connected(input, treat_bypassed_as_live) {
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 = app.graph.links[link_id];
var the_link = graph.links[link_id];
if (!the_link) return false;
if (treat_bypassed_as_live) return true;
the_link = handle_bypass(the_link, the_link.type); // find the link upstream of bypasses
the_link = handle_bypass(the_link, the_link.type, graph); // find the link upstream of bypasses
if (!the_link) return false; // no source for data.
return true;
}
@@ -359,14 +323,11 @@ 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")))
}
function has_priority_boost(node_or_nodeType) {
const title = node_or_nodeType.type || node_or_nodeType.comfyClass;
return ((title) && (title == "Anything Everywhere?"))
}
/*
Inject a call into a method on object with name methodname.
@@ -382,7 +343,7 @@ 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, get_group_node, get_all_nodes_within, has_priority_boost}
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);
@@ -437,4 +398,50 @@ export class Pausable {
return (this.pause_depth>0)
}
on_unpause(){}
}
export function get_connection(node, i, override_type) {
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:"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}`)
}
}
}
+1 -1
View File
@@ -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 = "6.2.2"
version = "7.0"
license = { file = "LICENSE" }
[project.urls]
+2
View File
@@ -46,6 +46,7 @@ class AnythingEverywherePrompts(Base):
return ()
class AnythingEverywhereTriplet(Base):
CATEGORY = "everywhere/deprecated"
@classmethod
def INPUT_TYPES(s):
return {"required":{},
@@ -55,6 +56,7 @@ class AnythingEverywhereTriplet(Base):
return ()
class AnythingSomewhere(Base):
CATEGORY = "everywhere/deprecated"
@classmethod
def INPUT_TYPES(s):
return {"required":{},