| import { app } from "../../../scripts/app.js"; |
| import { ComfyWidgets } from "../../../scripts/widgets.js"; |
|
|
| const REROUTE_PRIMITIVE = "ReroutePrimitive|pysssss"; |
| const MULTI_PRIMITIVE = "MultiPrimitive|pysssss"; |
| const LAST_TYPE = Symbol("LastType"); |
|
|
| app.registerExtension({ |
| name: "pysssss.ReroutePrimitive", |
| init() { |
| |
| const graphConfigure = LGraph.prototype.configure; |
| LGraph.prototype.configure = function () { |
| const r = graphConfigure.apply(this, arguments); |
| for (const n of app.graph._nodes) { |
| if (n.type === REROUTE_PRIMITIVE) { |
| n.onGraphConfigured(); |
| } |
| } |
|
|
| return r; |
| }; |
|
|
| const graphToPrompt = app.graphToPrompt; |
| app.graphToPrompt = async function () { |
| const res = await graphToPrompt.apply(this, arguments); |
|
|
| const multiOutputs = []; |
| for (const nodeId in res.output) { |
| const output = res.output[nodeId]; |
| if (output.class_type === MULTI_PRIMITIVE) { |
| multiOutputs.push({ id: nodeId, inputs: output.inputs }); |
| } |
| } |
|
|
| function permute(outputs) { |
| function generatePermutations(inputs, currentIndex, currentPermutation, result) { |
| if (currentIndex === inputs.length) { |
| result.push({ ...currentPermutation }); |
| return; |
| } |
|
|
| const input = inputs[currentIndex]; |
|
|
| for (const k in input) { |
| currentPermutation[currentIndex] = input[k]; |
| generatePermutations(inputs, currentIndex + 1, currentPermutation, result); |
| } |
| } |
|
|
| const inputs = outputs.map((output) => output.inputs); |
| const result = []; |
| const current = new Array(inputs.length); |
|
|
| generatePermutations(inputs, 0, current, result); |
|
|
| return outputs.map((output, index) => ({ |
| ...output, |
| inputs: result.reduce((p, permutation) => { |
| const count = Object.keys(p).length; |
| p["value" + (count || "")] = permutation[index]; |
| return p; |
| }, {}), |
| })); |
| } |
|
|
| const permutations = permute(multiOutputs); |
| for (let i = 0; i < permutations.length; i++) { |
| res.output[multiOutputs[i].id].inputs = permutations[i].inputs; |
| } |
|
|
| return res; |
| }; |
| }, |
| async beforeRegisterNodeDef(nodeType, nodeData, app) { |
| function addOutputHandler() { |
| |
| nodeType.prototype.getFirstReroutedOutput = function (slot) { |
| if (nodeData.name === MULTI_PRIMITIVE) { |
| slot = 0; |
| } |
| const links = this.outputs[slot].links; |
| if (!links) return null; |
|
|
| const search = []; |
| for (const l of links) { |
| const link = app.graph.links[l]; |
| if (!link) continue; |
|
|
| const node = app.graph.getNodeById(link.target_id); |
| if (node.type !== REROUTE_PRIMITIVE && node.type !== MULTI_PRIMITIVE) { |
| return { node, link }; |
| } |
| search.push({ node, link }); |
| } |
|
|
| for (const { link, node } of search) { |
| const r = node.getFirstReroutedOutput(link.target_slot); |
| if (r) { |
| return r; |
| } |
| } |
| }; |
| } |
|
|
| if (nodeData.name === REROUTE_PRIMITIVE) { |
| const configure = nodeType.prototype.configure || LGraphNode.prototype.configure; |
| const onConnectionsChange = nodeType.prototype.onConnectionsChange; |
| const onAdded = nodeType.prototype.onAdded; |
|
|
| nodeType.title_mode = LiteGraph.NO_TITLE; |
|
|
| function hasAnyInput(node) { |
| for (const input of node.inputs) { |
| if (input.link) { |
| return true; |
| } |
| } |
| return false; |
| } |
|
|
| |
| nodeType.prototype.onAdded = function () { |
| onAdded?.apply(this, arguments); |
| this.inputs[0].label = ""; |
| this.outputs[0].label = "value"; |
| this.setSize(this.computeSize()); |
| }; |
|
|
| |
| nodeType.prototype.onGraphConfigured = function () { |
| if (hasAnyInput(this)) return; |
|
|
| const outputNode = this.getFirstReroutedOutput(0); |
| if (outputNode) { |
| this.checkPrimitiveWidget(outputNode); |
| } |
| }; |
|
|
| |
| nodeType.prototype.checkPrimitiveWidget = function ({ node, link }) { |
| let widgetType = link.type; |
| let targetLabel = widgetType; |
| const input = node.inputs[link.target_slot]; |
| if (input.widget?.config?.[0] instanceof Array) { |
| targetLabel = input.widget.name; |
| widgetType = "COMBO"; |
| } |
|
|
| if (widgetType in ComfyWidgets) { |
| if (!this.widgets?.length) { |
| let v; |
| if (this.widgets_values?.length) { |
| v = this.widgets_values[0]; |
| } |
| let config = [link.type, {}]; |
| if (input.widget) { |
| config = input.widget.config; |
| } |
| const { widget } = ComfyWidgets[widgetType](this, "value", config, app); |
| if (v !== undefined && (!this[LAST_TYPE] || this[LAST_TYPE] === widgetType)) { |
| widget.value = v; |
| } |
| this[LAST_TYPE] = widgetType; |
| } |
| } else if (this.widgets) { |
| this.widgets.length = 0; |
| } |
|
|
| return targetLabel; |
| }; |
|
|
| |
| nodeType.prototype.getReroutedInputs = function (slot) { |
| let nodes = [{ node: this }]; |
| let node = this; |
| while (node?.type === REROUTE_PRIMITIVE) { |
| const input = node.inputs[slot]; |
| if (input.link) { |
| const link = app.graph.links[input.link]; |
| node = app.graph.getNodeById(link.origin_id); |
| slot = link.origin_slot; |
| nodes.push({ |
| node, |
| link, |
| }); |
| } else { |
| node = null; |
| } |
| } |
|
|
| return nodes; |
| }; |
|
|
| addOutputHandler(); |
|
|
| |
| nodeType.prototype.changeRerouteType = function (slot, type, label) { |
| const color = LGraphCanvas.link_type_colors[type]; |
| const output = this.outputs[slot]; |
| this.inputs[slot].label = " "; |
| output.label = label || (type === "*" ? "value" : type); |
| output.type = type; |
|
|
| |
| for (const linkId of output.links || []) { |
| const link = app.graph.links[linkId]; |
| if (!link) continue; |
| link.color = color; |
| const node = app.graph.getNodeById(link.target_id); |
| if (node.changeRerouteType) { |
| |
| node.changeRerouteType(link.target_slot, type, label); |
| } else { |
| |
| const theirType = node.inputs[link.target_slot].type; |
| if (theirType !== type && theirType !== "*") { |
| node.disconnectInput(link.target_slot); |
| } |
| } |
| } |
|
|
| if (this.inputs[slot].link) { |
| const link = app.graph.links[this.inputs[slot].link]; |
| if (link) link.color = color; |
| } |
| }; |
|
|
| |
| let configuring = false; |
| nodeType.prototype.configure = function () { |
| configuring = true; |
| const r = configure?.apply(this, arguments); |
| configuring = false; |
|
|
| return r; |
| }; |
|
|
| Object.defineProperty(nodeType, "title_mode", { |
| get() { |
| return app.canvas.current_node?.widgets?.length ? LiteGraph.NORMAL_TITLE : LiteGraph.NO_TITLE; |
| }, |
| }); |
|
|
| nodeType.prototype.onConnectionsChange = function (type, _, connected, link_info) { |
| |
| if (configuring) return; |
|
|
| const isInput = type === LiteGraph.INPUT; |
| const slot = isInput ? link_info.target_slot : link_info.origin_slot; |
|
|
| let targetLabel = null; |
| let targetNode = null; |
| let targetType = "*"; |
| let targetSlot = slot; |
|
|
| const inputPath = this.getReroutedInputs(slot); |
| const rootInput = inputPath[inputPath.length - 1]; |
| const outputNode = this.getFirstReroutedOutput(slot); |
| if (rootInput.node.type === REROUTE_PRIMITIVE) { |
| |
| if (outputNode) { |
| targetType = outputNode.link.type; |
| } else if (rootInput.node.widgets) { |
| rootInput.node.widgets.length = 0; |
| } |
| targetNode = rootInput; |
| targetSlot = rootInput.link?.target_slot ?? slot; |
| } else { |
| |
| targetNode = inputPath[inputPath.length - 2]; |
| targetType = rootInput.node.outputs[rootInput.link.origin_slot].type; |
| targetSlot = rootInput.link.target_slot; |
| } |
|
|
| if (this.widgets && inputPath.length > 1) { |
| |
| this.widgets.length = 0; |
| } |
|
|
| if (outputNode && rootInput.node.checkPrimitiveWidget) { |
| |
| targetLabel = rootInput.node.checkPrimitiveWidget(outputNode); |
| } |
|
|
| |
| targetNode.node.changeRerouteType(targetSlot, targetType, targetLabel); |
|
|
| return onConnectionsChange?.apply(this, arguments); |
| }; |
|
|
| |
| const computeSize = nodeType.prototype.computeSize || LGraphNode.prototype.computeSize; |
| nodeType.prototype.computeSize = function () { |
| const r = computeSize.apply(this, arguments); |
| if (this.flags?.collapsed) { |
| return [1, 25]; |
| } else if (this.widgets?.length) { |
| return r; |
| } else { |
| let w = 75; |
| if (this.outputs?.[0]?.label) { |
| const t = LiteGraph.NODE_TEXT_SIZE * this.outputs[0].label.length * 0.6 + 30; |
| if (t > w) { |
| w = t; |
| } |
| } |
| return [w, r[1]]; |
| } |
| }; |
|
|
| |
| const collapse = nodeType.prototype.collapse || LGraphNode.prototype.collapse; |
| nodeType.prototype.collapse = function () { |
| collapse.apply(this, arguments); |
| this.setSize(this.computeSize()); |
| requestAnimationFrame(() => { |
| this.setDirtyCanvas(true, true); |
| }); |
| }; |
|
|
| |
| nodeType.prototype.onBounding = function (area) { |
| if (this.flags?.collapsed) { |
| area[1] -= 15; |
| } |
| }; |
| } else if (nodeData.name === MULTI_PRIMITIVE) { |
| addOutputHandler(); |
| nodeType.prototype.onConnectionsChange = function (type, _, connected, link_info) { |
| for (let i = 0; i < this.inputs.length - 1; i++) { |
| if (!this.inputs[i].link) { |
| this.removeInput(i--); |
| } |
| } |
| if (this.inputs[this.inputs.length - 1].link) { |
| this.addInput("v" + +new Date(), this.inputs[0].type).label = "value"; |
| } |
| }; |
| } |
| }, |
| }); |
|
|