From 37afd4be0cb0ff02a2e6fe9f1abe192a2b679ff3 Mon Sep 17 00:00:00 2001 From: Shaun Johnston Date: Sun, 28 Dec 2025 06:53:58 +1000 Subject: [PATCH] nested pipes --- friendly-pipe-sandbox.json | 1 + js/friendly_pipe.js | 109 +++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 friendly-pipe-sandbox.json diff --git a/friendly-pipe-sandbox.json b/friendly-pipe-sandbox.json new file mode 100644 index 0000000..508c405 --- /dev/null +++ b/friendly-pipe-sandbox.json @@ -0,0 +1 @@ +{"id":"7f10e289-6a5d-4520-80ff-f0fded044626","revision":0,"last_node_id":7,"last_link_id":5,"nodes":[{"id":5,"type":"7f36590c-c931-44f4-a37b-ec33ef4051a0","pos":[9625,-2725],"size":[140,26],"flags":{},"order":4,"mode":0,"inputs":[{"name":"pipe","type":"FRIENDLY_PIPE","link":2}],"outputs":[],"properties":{"proxyWidgets":[],"cnr_id":"comfy-core","ver":"0.4.0","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.2"}},"widgets_values":[]},{"id":7,"type":"FriendlyPipeIn","pos":[8950,-2625],"size":[210,150],"flags":{},"order":2,"mode":0,"inputs":[{"label":"in_1","localized_name":"slot_1","name":"slot_1","shape":7,"type":"*","link":3},{"label":"in_2","name":"slot_2","type":"*","link":4}],"outputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","links":[5]}],"properties":{"ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.2"},"cnr_id":"comfyui-friendly-pipe","ver":"72f0ea3d7f686488045950d1c0bfc4c72b2d677a","Node name for S&R":"FriendlyPipeIn"},"widgets_values":["in_1","in_2"],"slotCount":2,"slotNames":{"1":"in_1","2":"in_2"},"slotTypes":{"1":"FRIENDLY_PIPE","2":"FRIENDLY_PIPE"}},{"id":1,"type":"FriendlyPipeIn","pos":[8650,-2725],"size":[210,150],"flags":{},"order":0,"mode":0,"inputs":[{"label":"in_1_1","localized_name":"slot_1","name":"slot_1","shape":7,"type":"*","link":null},{"label":"in_1_2","name":"slot_2","type":"*","link":null}],"outputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","links":[3]}],"properties":{"cnr_id":"comfyui-friendly-pipe","ver":"72f0ea3d7f686488045950d1c0bfc4c72b2d677a","Node name for S&R":"FriendlyPipeIn","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.2"}},"widgets_values":["in_1_1","in_1_2"],"slotCount":2,"slotNames":{"1":"in_1_1","2":"in_1_2"},"slotTypes":{}},{"id":6,"type":"FriendlyPipeIn","pos":[8650,-2500],"size":[210,150],"flags":{},"order":1,"mode":0,"inputs":[{"label":"in_2_1","localized_name":"slot_1","name":"slot_1","shape":7,"type":"*","link":null},{"label":"in_2_2","name":"slot_2","type":"*","link":null}],"outputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","links":[4]}],"properties":{"ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.2"},"cnr_id":"comfyui-friendly-pipe","ver":"72f0ea3d7f686488045950d1c0bfc4c72b2d677a","Node name for S&R":"FriendlyPipeIn"},"widgets_values":["in_2_1","in_2_2"],"slotCount":2,"slotNames":{"1":"in_2_1","2":"in_2_2"},"slotTypes":{}},{"id":3,"type":"dddf7678-010f-4040-81b4-1236387d7c23","pos":[9275,-2725],"size":[140,26],"flags":{},"order":3,"mode":0,"inputs":[{"name":"pipe","type":"FRIENDLY_PIPE","link":5}],"outputs":[{"name":"pipe","type":"FRIENDLY_PIPE","links":[2]}],"properties":{"proxyWidgets":[],"cnr_id":"comfy-core","ver":"0.4.0","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.2"}},"widgets_values":[]}],"links":[[2,3,0,5,0,"FRIENDLY_PIPE"],[3,1,0,7,0,"FRIENDLY_PIPE"],[4,6,0,7,1,"FRIENDLY_PIPE"],[5,7,0,3,0,"FRIENDLY_PIPE"]],"groups":[],"definitions":{"subgraphs":[{"id":"dddf7678-010f-4040-81b4-1236387d7c23","version":1,"state":{"lastGroupId":0,"lastNodeId":8,"lastLinkId":9,"lastRerouteId":0},"revision":0,"config":{},"name":"Subgraph 1","inputNode":{"id":-10,"bounding":[9120,-2597,120,60]},"outputNode":{"id":-20,"bounding":[9850,-2600,120,60]},"inputs":[{"id":"8bafb261-0838-48c1-acaf-51aa6443182b","name":"pipe","type":"FRIENDLY_PIPE","linkIds":[1,8],"pos":[9220,-2577]}],"outputs":[{"id":"69e97dba-064c-482b-a6e2-79ed9e663c23","name":"pipe","type":"FRIENDLY_PIPE","linkIds":[9],"pos":[9870,-2580]}],"widgets":[],"nodes":[{"id":6,"type":"FriendlyPipeOut","pos":[9500,-2750],"size":[153.390234375,46],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","link":6}],"outputs":[{"label":"in_1","localized_name":"slot_1","name":"slot_1","type":"*","links":null},{"label":"in_2","name":"slot_2","type":"*","links":null}],"properties":{"ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{}},"cnr_id":"comfyui-friendly-pipe","ver":"292cb622a7776887081a38137a99a43ef5ff2917","Node name for S&R":"FriendlyPipeOut"},"slotCount":2,"slotNames":{"1":"in_1","2":"in_2"},"slotTypes":{}},{"id":2,"type":"FriendlyPipeOut","pos":[9275,-2675],"size":[153.390234375,46],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","link":1}],"outputs":[{"label":"in_1","localized_name":"slot_1","name":"slot_1","type":"*","links":[6]},{"label":"in_2","name":"slot_2","type":"*","links":[7]}],"properties":{"cnr_id":"comfyui-friendly-pipe","ver":"72f0ea3d7f686488045950d1c0bfc4c72b2d677a","Node name for S&R":"FriendlyPipeOut","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{}}},"widgets_values":[],"slotCount":2,"slotNames":{"1":"in_1","2":"in_2"},"slotTypes":{}},{"id":7,"type":"FriendlyPipeOut","pos":[9475,-2625],"size":[153.390234375,46],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","link":7}],"outputs":[{"label":"in_1","localized_name":"slot_1","name":"slot_1","type":"*","links":null},{"label":"in_2","name":"slot_2","type":"*","links":null}],"properties":{"ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{}},"cnr_id":"comfyui-friendly-pipe","ver":"292cb622a7776887081a38137a99a43ef5ff2917","Node name for S&R":"FriendlyPipeOut"},"slotCount":2,"slotNames":{"1":"in_1","2":"in_2"},"slotTypes":{}},{"id":8,"type":"FriendlyPipeEdit","pos":[9450,-2500],"size":[210,126],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","link":8},{"label":"slot_1","localized_name":"slot_1","name":"slot_1","shape":7,"type":"*","link":null}],"outputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","links":[9]}],"properties":{"ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{}},"cnr_id":"comfyui-friendly-pipe","ver":"292cb622a7776887081a38137a99a43ef5ff2917","Node name for S&R":"FriendlyPipeEdit"},"widgets_values":["slot_1"],"slotCount":1,"slotNames":{"1":"slot_1"},"slotTypes":{}}],"groups":[],"links":[{"id":1,"origin_id":-10,"origin_slot":0,"target_id":2,"target_slot":0,"type":"FRIENDLY_PIPE"},{"id":6,"origin_id":2,"origin_slot":0,"target_id":6,"target_slot":0,"type":"FRIENDLY_PIPE"},{"id":7,"origin_id":2,"origin_slot":1,"target_id":7,"target_slot":0,"type":"FRIENDLY_PIPE"},{"id":8,"origin_id":-10,"origin_slot":0,"target_id":8,"target_slot":0,"type":"FRIENDLY_PIPE"},{"id":9,"origin_id":8,"origin_slot":0,"target_id":-20,"target_slot":0,"type":"FRIENDLY_PIPE"}],"extra":{"workflowRendererVersion":"LG","ue_links":[]}},{"id":"7f36590c-c931-44f4-a37b-ec33ef4051a0","version":1,"state":{"lastGroupId":0,"lastNodeId":4,"lastLinkId":2,"lastRerouteId":0},"revision":0,"config":{},"name":"Subgraph 2","inputNode":{"id":-10,"bounding":[9345,-2622,120,60]},"outputNode":{"id":-20,"bounding":[9738.390234375,-2622,120,40]},"inputs":[{"id":"e28ac386-50e4-4167-a960-841c1c6aafcd","name":"pipe","type":"FRIENDLY_PIPE","linkIds":[2],"pos":[9445,-2602]}],"outputs":[],"widgets":[],"nodes":[{"id":4,"type":"FriendlyPipeOut","pos":[9525,-2600],"size":[153.390234375,46],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"pipe","name":"pipe","type":"FRIENDLY_PIPE","link":2}],"outputs":[{"label":"edit_2","localized_name":"slot_1","name":"slot_1","type":"*","links":null},{"label":"edit_2","name":"slot_2","type":"*","links":null}],"properties":{"cnr_id":"comfyui-friendly-pipe","ver":"72f0ea3d7f686488045950d1c0bfc4c72b2d677a","Node name for S&R":"FriendlyPipeOut","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{}}},"widgets_values":[],"slotCount":2,"slotNames":{"1":"edit_2","2":"edit_2"},"slotTypes":{}}],"groups":[],"links":[{"id":2,"origin_id":-10,"origin_slot":0,"target_id":4,"target_slot":0,"type":"FRIENDLY_PIPE"}],"extra":{"workflowRendererVersion":"LG","ue_links":[]}}]},"config":{},"extra":{"workflowRendererVersion":"LG","ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/js/friendly_pipe.js b/js/friendly_pipe.js index 1c14eda..e4a8bf1 100644 --- a/js/friendly_pipe.js +++ b/js/friendly_pipe.js @@ -421,10 +421,11 @@ function setupFriendlyPipeIn(nodeType, nodeData, app) { const node = this; - // Initialize slot count, names, and types + // Initialize slot count, names, types, and sources this.slotCount = 1; this.slotNames = { 1: "slot_1" }; this.slotTypes = {}; + this.slotSources = {}; // Remove all inputs except the first one while (this.inputs && this.inputs.length > 1) { @@ -549,6 +550,7 @@ function setupFriendlyPipeIn(nodeType, nodeData, app) { nodeType.prototype.updateSlotTypes = function() { this.slotTypes = {}; + this.slotSources = {}; // Track source nodes for FRIENDLY_PIPE inputs if (!this.inputs) return; @@ -563,12 +565,26 @@ function setupFriendlyPipeIn(nodeType, nodeData, app) { if (sourceNode && sourceNode.outputs && sourceNode.outputs[link.origin_slot]) { const outputType = sourceNode.outputs[link.origin_slot].type; this.slotTypes[i + 1] = outputType; + + // If this slot receives a FRIENDLY_PIPE, track its source + if (outputType === "FRIENDLY_PIPE") { + // Find the original source of this pipe + const pipeSource = findOriginalSource(sourceNode, link.origin_slot); + if (pipeSource) { + this.slotSources[i + 1] = pipeSource; + } + } } } } } }; + // Get the source node for a specific slot (used by downstream FriendlyPipeOut) + nodeType.prototype.getSlotSource = function(slotIndex) { + return this.slotSources?.[slotIndex] || null; + }; + // Handle serialization const origOnSerialize = nodeType.prototype.onSerialize; nodeType.prototype.onSerialize = function(o) { @@ -681,6 +697,7 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { this.slotCount = 1; this.slotNames = {}; this.slotTypes = {}; + this.slotSources = {}; // Remove all outputs except the first one while (this.outputs && this.outputs.length > 1) { @@ -699,13 +716,14 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { this.setSize(this.computeSize()); }; - nodeType.prototype.updateFromSource = function(slotCount, slotNames, slotTypes) { + nodeType.prototype.updateFromSource = function(slotCount, slotNames, slotTypes, slotSources) { const node = this; // Update outputs to match source const targetCount = slotCount || 1; const names = slotNames || {}; const types = slotTypes || {}; + const sources = slotSources || {}; // Add or remove outputs as needed while (this.outputs && this.outputs.length < targetCount) { @@ -729,8 +747,39 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { this.slotCount = targetCount; this.slotNames = names; this.slotTypes = types; + this.slotSources = sources; this.updateSize(); this.setDirtyCanvas(true, true); + + // Notify any FriendlyPipeOut nodes connected to our output slots + this.notifySlotConnections(); + }; + + // Notify FriendlyPipeOut nodes connected to individual output slots + nodeType.prototype.notifySlotConnections = function() { + if (!this.outputs) return; + + const graph = this.graph || app.graph; + + for (let i = 0; i < this.outputs.length; i++) { + const output = this.outputs[i]; + if (output.links) { + for (const linkId of output.links) { + const link = graph.links instanceof Map ? graph.links.get(linkId) : graph.links?.[linkId]; + if (link) { + const targetNode = graph.getNodeById(link.target_id); + if (targetNode && targetNode.syncWithSource) { + targetNode.syncWithSource(); + } + } + } + } + } + }; + + // Get the source node for a specific slot (used by downstream FriendlyPipeOut) + nodeType.prototype.getSlotSource = function(slotIndex) { + return this.slotSources?.[slotIndex] || null; }; // Handle connection changes @@ -834,6 +883,56 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { return; } + // Special case: if connected to a FriendlyPipeOut's output slot, check if that slot + // contains a FRIENDLY_PIPE and trace back to its original source + if (immediateSource.type === "FriendlyPipeOut" && originSlot > 0) { + debugLog("Connected to FriendlyPipeOut output slot", originSlot); + // This FriendlyPipeOut is outputting individual slots + // Check if the slot type is FRIENDLY_PIPE + const slotType = immediateSource.slotTypes?.[originSlot]; + debugLog("Slot type:", slotType); + + if (slotType === "FRIENDLY_PIPE") { + // Need to find the original source of this pipe + // First, find the FriendlyPipeIn that feeds this FriendlyPipeOut + const pipeOutInput = immediateSource.inputs?.[0]; + if (pipeOutInput && pipeOutInput.link) { + const pipeOutLink = graph.links[pipeOutInput.link]; + if (pipeOutLink) { + const pipeInNode = graph.getNodeById(pipeOutLink.origin_id); + // Find the original FriendlyPipeIn/Edit + const pipeSource = findOriginalSource(pipeInNode, pipeOutLink.origin_slot); + if (pipeSource && pipeSource.getSlotSource) { + // Get the source for this specific slot + const slotSource = pipeSource.getSlotSource(originSlot); + debugLog("Found slot source:", slotSource); + if (slotSource) { + if (slotSource.updateSlotTypes) { + slotSource.updateSlotTypes(); + } + if (slotSource.getTotalSlotCount) { + this.updateFromSource( + slotSource.getTotalSlotCount(), + slotSource.getCombinedSlotNames(), + slotSource.getCombinedSlotTypes(), + slotSource.slotSources || {} + ); + } else { + this.updateFromSource( + slotSource.slotCount, + slotSource.slotNames || {}, + slotSource.slotTypes || {}, + slotSource.slotSources || {} + ); + } + return; + } + } + } + } + } + } + // Traverse through reroute/subgraph nodes to find the original FriendlyPipeIn or FriendlyPipeEdit const sourceNode = findOriginalSource(immediateSource, originSlot); debugLog("sourceNode from traversal:", sourceNode); @@ -854,7 +953,8 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { this.updateFromSource( effectiveSource.getTotalSlotCount(), effectiveSource.getCombinedSlotNames(), - effectiveSource.getCombinedSlotTypes() + effectiveSource.getCombinedSlotTypes(), + effectiveSource.slotSources || {} ); } else { // FriendlyPipeIn or other source @@ -864,7 +964,8 @@ function setupFriendlyPipeOut(nodeType, nodeData, app) { this.updateFromSource( effectiveSource.slotCount, effectiveSource.slotNames || {}, - effectiveSource.slotTypes || {} + effectiveSource.slotTypes || {}, + effectiveSource.slotSources || {} ); } } else {