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| |-|-| |![with](docs/broadcaston.png)|![without](docs/broadcastoff.png)| + +From `7.5`, the outputs that will broadcast can also be set (like inputs): + +![output_set](docs/output_set.png) +
# 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: ![uec](docs/connectable3.png) + +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. + +![outputs](docs/outputs.png) +
# 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"