Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
348
custom_nodes/comfyui-custom-scripts/web/js/reroutePrimitive.js
Normal file
348
custom_nodes/comfyui-custom-scripts/web/js/reroutePrimitive.js
Normal file
@@ -0,0 +1,348 @@
|
||||
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() {
|
||||
// On graph configure, fire onGraphConfigured to create widgets
|
||||
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;
|
||||
};
|
||||
|
||||
// Hide this node as it is no longer supported
|
||||
const getNodeTypesCategories = LiteGraph.getNodeTypesCategories;
|
||||
LiteGraph.getNodeTypesCategories = function() {
|
||||
return getNodeTypesCategories.apply(this, arguments).filter(c => !c.startsWith("__hidden__"));
|
||||
}
|
||||
|
||||
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() {
|
||||
// Finds the first non reroute output node down the chain
|
||||
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;
|
||||
}
|
||||
|
||||
// Remove input text
|
||||
nodeType.prototype.onAdded = function () {
|
||||
onAdded?.apply(this, arguments);
|
||||
this.inputs[0].label = "";
|
||||
this.outputs[0].label = "value";
|
||||
this.setSize(this.computeSize());
|
||||
};
|
||||
|
||||
// Restore any widgets
|
||||
nodeType.prototype.onGraphConfigured = function () {
|
||||
if (hasAnyInput(this)) return;
|
||||
|
||||
const outputNode = this.getFirstReroutedOutput(0);
|
||||
if (outputNode) {
|
||||
this.checkPrimitiveWidget(outputNode);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if we need to create (or remove) a widget on the node
|
||||
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) {
|
||||
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;
|
||||
};
|
||||
|
||||
// Finds all input nodes from the current reroute
|
||||
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();
|
||||
|
||||
// Update the type of all reroutes in a chain
|
||||
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;
|
||||
|
||||
// Process all linked outputs
|
||||
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) {
|
||||
// Recursively update reroutes
|
||||
node.changeRerouteType(link.target_slot, type, label);
|
||||
} else {
|
||||
// Validate links to 'real' nodes
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// Override configure so we can flag that we are configuring to avoid link validation breaking
|
||||
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 treat everything as OK as links may not be set by litegraph yet
|
||||
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) {
|
||||
// Our input node is a reroute, so see if we have an output
|
||||
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 {
|
||||
// We have a real input, so we want to use that type
|
||||
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) {
|
||||
// We have an input node so remove our widget
|
||||
this.widgets.length = 0;
|
||||
}
|
||||
|
||||
if (outputNode && rootInput.node.checkPrimitiveWidget) {
|
||||
// We have an output, check if we need to create a widget
|
||||
targetLabel = rootInput.node.checkPrimitiveWidget(outputNode);
|
||||
}
|
||||
|
||||
// Trigger an update of the type to all child nodes
|
||||
targetNode.node.changeRerouteType(targetSlot, targetType, targetLabel);
|
||||
|
||||
return onConnectionsChange?.apply(this, arguments);
|
||||
};
|
||||
|
||||
// When collapsed fix the size to just the dot
|
||||
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]];
|
||||
}
|
||||
};
|
||||
|
||||
// On collapse shrink the node to just a dot
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
// Shift the bounding area up slightly as LiteGraph miscalculates it for collapsed nodes
|
||||
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";
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user