diff --git a/README.md b/README.md
index 6d4636d..11fc303 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,10 @@ If upgrading from before version 7, see the end of this document for the major c
Changes since 7.0
+## 7.5
+
+- Added per-output granularity to any-node broadcasting
+
## 7.4
- Added broadcasting from [any node](#any-node-broadcasting)
@@ -84,6 +88,11 @@ When broadcasting, the node acts like all its outputs were connected to a single
|This...|...does the same as this|
|-|-|
|||
+
+From `7.5`, the outputs that will broadcast can also be set (like inputs):
+
+
+
# Where will the data be sent?
@@ -180,7 +189,7 @@ and connections, it is muted.
-Inputs have a subtle visual clue
+Inputs and outputs have a subtle visual clue
The state of inputs is also represented visually: a black ring and a glow on the input dot indicates it is connectable.
In the image below, `positive` has been set to not accept UE inputs, `steps` has been set to accept them, and `model` has a UE connection.
@@ -190,6 +199,12 @@ In the image below, `positive` has been set to not accept UE inputs, `steps` has
If a widget is getting data from a UE connection, it is grayed out, like `steps` below:

+
+Similarly, if a node has had broadcasting added to it, the outputs that can broadcast are shown:
+here `merged` can broadcast, but isn't, `x` can broadcast and is, and `y` is set to not broadcast.
+
+
+
# Special Case Nodes
diff --git a/docs/output_set.png b/docs/output_set.png
new file mode 100644
index 0000000..b368fca
Binary files /dev/null and b/docs/output_set.png differ
diff --git a/docs/outputs.png b/docs/outputs.png
new file mode 100644
index 0000000..63f57cb
Binary files /dev/null and b/docs/outputs.png differ
diff --git a/js/use_everywhere_classes.js b/js/use_everywhere_classes.js
index 9f9a5a7..f926f83 100644
--- a/js/use_everywhere_classes.js
+++ b/js/use_everywhere_classes.js
@@ -1,5 +1,6 @@
import { i18n_functional, i18n_functional_regex } from "./i18n.js";
import { default_priority } from "./ue_properties.js";
+import { is_able_to_broadcast } from "./use_everywhere_settings.js";
import { connection_from_output_as_input, 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, get_connection } from "./use_everywhere_utilities.js";
@@ -160,10 +161,6 @@ function validity_errors(params) {
export class UseEverywhereList {
constructor() { this.ues = []; this.unmatched_inputs = []; }
- node_sending_anywhere(node) {
- return this.ues.find((ue)=>(ue.controller?.id==node.id && ue.sending_to?.length>0)) ? true : false;
- }
-
differs_from(another_uel) {
if (!another_uel || !another_uel.ues || !this.ues) return true;
if (this.ues.length != another_uel.ues.length) return true;
@@ -258,6 +255,18 @@ export class UseEverywhereList {
return ue_connections;
}
+ node_sending_anywhere(node) {
+ return this.ues.find((ue)=>(ue.controller?.id==node.id && ue.sending_to?.length>0)) ? true : false;
+ }
+
+ all_sending_slots(for_node) {
+ const sending_slots = new Set()
+ this.ues.filter((ue)=>(ue.controller.id == for_node.id) && (ue.sending_to.length>0)).forEach((ue) => {
+ sending_slots.add(ue.control_node_input_index)
+ });
+ return sending_slots;
+ }
+
all_ue_connections() {
const ue_connections = [];
this.ues.forEach((ue) => {
@@ -283,19 +292,25 @@ export class UseEverywhereList {
var the_possibles
var connection_finder
+ var check_if_able_to_broadcast
if (node.properties.ue_convert) {
the_possibles = node.outputs
connection_finder = connection_from_output_as_input
+ check_if_able_to_broadcast = (node, i) => {
+ const name = node.outputs[i].name
+ return is_able_to_broadcast(node, name)
+ }
} else {
the_possibles = node.inputs
connection_finder = get_connection
+ check_if_able_to_broadcast = ()=>{true}
}
const broadcasted_types = new Set()
const duplicated_broadcasted_types = new Set()
for (var i=0; i {
const connection = connection_finder(node, i);
- if (connection.link) {
+ if (connection.link && check_if_able_to_broadcast(node,i)) {
const input_regex = undefined
var additional_requirement = null
if (duplicated_broadcasted_types.has(connection.type)) {
diff --git a/js/use_everywhere_graph_analysis.js b/js/use_everywhere_graph_analysis.js
index dc48b8b..14934cc 100644
--- a/js/use_everywhere_graph_analysis.js
+++ b/js/use_everywhere_graph_analysis.js
@@ -3,7 +3,6 @@ import { node_is_live, is_connected, is_UEnode, Logger, Pausable } from "./use_e
import { convert_to_links } from "./use_everywhere_apply.js";
import { app } from "../../scripts/app.js";
import { settingsCache } from "./use_everywhere_cache.js";
-import { visible_graph } from "./use_everywhere_subgraph_utils.js";
import { is_connectable } from "./use_everywhere_settings.js";
import { for_all_graphs } from "./recursive_callbacks.js";
diff --git a/js/use_everywhere_settings.js b/js/use_everywhere_settings.js
index df5ff45..e22f417 100644
--- a/js/use_everywhere_settings.js
+++ b/js/use_everywhere_settings.js
@@ -127,7 +127,7 @@ function show_connectable(submenu_root, node) {
export function is_connectable(node, input_name){
const input = node.inputs.find(i => i.name==input_name);
if (!input) {
- Logger.log_error(`Can't find input ${label} on node ${node.title}`);
+ Logger.log_error(`Can't find input ${input_name} on node ${node.title}`);
return false;
}
if (input.widget) {
@@ -172,6 +172,50 @@ function widget_ue_submenu(value, options, e, menu, node) {
show_connectable(submenu.root, node)
}
+function show_broadcasting(submenu_root, node) {
+ node.outputs.forEach((input, i) => {
+ const current_element = submenu_root?.querySelector(`:nth-child(${i+1})`);
+ if (current_element) current_element.style.borderLeft = (is_able_to_broadcast(node,input.name)) ? "2px solid #484" : "";
+ })
+}
+
+export function is_able_to_broadcast(node, output_name) {
+ if (!node.properties.ue_convert) return false
+ const output = node.outputs.find(i => i.name==output_name);
+ if (!output) {
+ Logger.log_error(`Can't find output ${output_name} on node ${node.title}`);
+ return false;
+ }
+ return ! (node.properties?.ue_properties?.output_not_broadcasting?.[output_name])
+}
+
+function toggle_broadcasting(node, output_name){
+ const p = node.properties.ue_properties
+ p.output_not_broadcasting[output_name] = !!!p.output_not_broadcasting[output_name]
+}
+
+function output_ue_submenu(value, options, e, menu, node) {
+ if (!(node.properties.ue_properties)) node.properties.ue_properties = {}
+ if (!(node.properties.ue_properties.output_not_broadcasting)) node.properties.ue_properties.output_not_broadcasting = {};
+
+ const label_to_name = {}
+ node.outputs.forEach((output) => {label_to_name[output.label || output.name] = output.name})
+ const submenu = new LiteGraph.ContextMenu(
+ Object.keys(label_to_name),
+ { event: e, callback: function (label) {
+ const name = label_to_name[label];
+ toggle_broadcasting(node, name);
+ shared.linkRenderController.mark_link_list_outdated();
+ show_broadcasting(this.parentElement, node)
+ return true; // keep open
+ },
+ parentMenu: menu, node:node}
+ )
+ show_broadcasting(submenu.root, node)
+}
+
+
+
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;
@@ -244,9 +288,18 @@ function non_ue_menu_settings(options, node) {
{
content: node.properties.ue_convert ? "Remove UE broadcasting" : "Add UE broadcasting",
has_submenu: false,
- callback: () => { node.properties.ue_convert = !!!node.properties.ue_convert },
+ callback: () => { node.properties.ue_convert = !!!node.properties.ue_convert },
}
)
+ if (node.properties.ue_convert) {
+ options.push(
+ {
+ content: "Broadcasting Outputs",
+ has_submenu: true,
+ callback: output_ue_submenu,
+ }
+ )
+ }
}
function remove_ue_nodes(graph) {
diff --git a/js/use_everywhere_ui.js b/js/use_everywhere_ui.js
index ca6a1c3..4cb0fb6 100644
--- a/js/use_everywhere_ui.js
+++ b/js/use_everywhere_ui.js
@@ -3,7 +3,7 @@ import { app } from "../../scripts/app.js";
import { settingsCache } from "./use_everywhere_cache.js";
import { in_visible_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
import { maybe_show_tooltip } from "./tooltip_window.js";
-import { is_connectable } from "./use_everywhere_settings.js";
+import { is_able_to_broadcast, is_connectable } from "./use_everywhere_settings.js";
import { shared } from "./shared.js";
import { any_restrictions } from "./ue_properties.js";
@@ -204,6 +204,8 @@ export class LinkRenderController extends Pausable {
this.pause('highlight_ue_connections')
if (!this._list_ready()) return;
+ // get all the inputs that can be connected and aren't
+ // connected with a 'real' link
const unconnected_connectable_names = new Set(node.inputs
.filter((input)=>is_connectable(node,input.name))
.filter((input)=>(!input.link))
@@ -216,49 +218,35 @@ export class LinkRenderController extends Pausable {
const name_sent_to = node.inputs[ue_connection.input_index].name;
unconnected_connectable_names.delete(name_sent_to); // remove the name from the list of connectables
- var pos2 = node.getConnectionPos(true, ue_connection.input_index, this.slot_pos1);
- pos2[0] -= node.pos[0];
- pos2[1] -= node.pos[1];
- ctx.save();
- ctx.lineWidth = 1;
- var radius=5
- ctx.strokeStyle = LGraphCanvas.link_type_colors[ue_connection.type];
- ctx.shadowColor = "white";
- ctx.shadowBlur = 10;
- ctx.shadowOffsetX = 0;
- ctx.shadowOffsetY = 0;
- ctx.beginPath();
- ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius);
- ctx.stroke();
- ctx.shadowBlur = 0;
- ctx.beginPath();
- ctx.strokeStyle = "black";
- ctx.shadowBlur = 0;
- radius = radius - 1;
- ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius);
- ctx.stroke();
+ const pos2 = this._relative_connection_pos(node, true, ue_connection.input_index);
- ctx.restore();
+ circle(ctx, pos2, {...CONNECTED_1, strokeStyle:LGraphCanvas.link_type_colors[ue_connection.type]})
+ circle(ctx, pos2, {...CONNECTED_2})
});
}
unconnected_connectable_names.forEach((name) => {
const index = node.inputs.findIndex((i) => i.name == name);
- var pos2 = node.getConnectionPos(true, index, this.slot_pos1);
- pos2[0] -= node.pos[0];
- pos2[1] -= node.pos[1];
- ctx.save();
- ctx.strokeStyle = "black";
- ctx.shadowColor = "green";
- ctx.shadowBlur = 10;
- ctx.shadowOffsetX = 0;
- ctx.shadowOffsetY = 0;
- var radius = 3;
- ctx.beginPath();
- ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius);
- ctx.stroke();
- ctx.restore();
+ const pos2 = this._relative_connection_pos(node, true, index);
+ circle(ctx, pos2, {...UNCONNECTED_CONNECTABLE})
})
+
+ if (node.properties.ue_convert) {
+ const sending_slots = this.ue_list.all_sending_slots(node)
+
+ node.outputs.forEach((output,i) => {
+ if (is_able_to_broadcast(node, output.name)) {
+ const pos2 = this._relative_connection_pos(node, false, i);
+ if (sending_slots.has(i)) {
+ circle(ctx, pos2, {...CONNECTED_1, strokeStyle:LGraphCanvas.link_type_colors[node.outputs[i].type]})
+ circle(ctx, pos2, {...CONNECTED_2})
+ } else {
+ circle(ctx, pos2, {...UNCONNECTED_CONNECTABLE})
+ }
+
+ }
+ })
+ }
} catch (e) {
Logger.log_error(e);
} finally {
@@ -266,6 +254,13 @@ export class LinkRenderController extends Pausable {
}
}
+ _relative_connection_pos(node, input, slot) {
+ var pos2 = node.getConnectionPos(input, slot, this.slot_pos1);
+ pos2[0] -= node.pos[0];
+ pos2[1] -= node.pos[1];
+ return pos2
+ }
+
_list_ready() {
if (!shared.graphAnalyser) return false; // we don't have the analyser yet (still loading)
if (!this.ue_list) {
@@ -422,6 +417,49 @@ export class LinkRenderController extends Pausable {
}
}
+const UNCONNECTED_CONNECTABLE = {
+ radius : 3,
+ strokeStyle : "black",
+ shadowColor : "green",
+ shadowBlur : 10,
+ shadowOffsetX : 0,
+ shadowOffsetY : 0,
+}
+
+const CONNECTED_1 = {
+ radius:5,
+ first_last:"first",
+ lineWidth:1,
+ shadowColor : "white",
+ shadowBlur : 10,
+ shadowOffsetX : 0,
+ shadowOffsetY : 0,
+}
+
+const CONNECTED_2 = {
+ radius:4,
+ first_last:"last",
+ shadowBlur : 0,
+ strokeStyle : "black",
+}
+
+function circle(ctx, position, properties) {
+ const p = {...properties}
+
+ const radius = p.radius
+ const first_last = p.first_last
+ delete p.radius
+ delete p.first_last
+
+ if (!first_last || first_last=='first') ctx.save()
+ Object.assign(ctx, p)
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.roundRect(position[0]-radius,position[1]-radius,2*radius,2*radius,radius);
+ ctx.stroke();
+ if (!first_last || first_last=='last') ctx.restore()
+}
+
function modify(c) {
if (c.length==4) return c + "6"
if (c.length==7) return c + "66"