Hybrid approach: Python stubs + JS virtual override
- Python defines nodes with ANY type so they appear in menus/drag connections - JS overrides with isVirtualNode for actual graph-redirect behavior - SimpleSet accepts any input, passes through - SimpleGet dropdown shows available setter names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+62
-7
@@ -1,13 +1,68 @@
|
||||
"""
|
||||
SimpleVariables - Pure frontend Set/Get Variable nodes for ComfyUI
|
||||
|
||||
These nodes are implemented entirely in JavaScript (web/simplevariables.js).
|
||||
They work by redirecting graph connections - no Python execution needed.
|
||||
SimpleVariables - Set/Get Variable nodes for ComfyUI
|
||||
JavaScript handles the actual logic (virtual nodes), Python stubs make them appear in menus.
|
||||
"""
|
||||
|
||||
# No Python nodes - everything is in JavaScript
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
class AnyType(str):
|
||||
"""Matches any type for ComfyUI type checking"""
|
||||
def __eq__(self, _):
|
||||
return True
|
||||
def __ne__(self, _):
|
||||
return False
|
||||
|
||||
|
||||
ANY = AnyType("*")
|
||||
|
||||
|
||||
class SimpleSet:
|
||||
"""Set a variable by name"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"name": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"value": (ANY,),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = (ANY,)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "passthrough"
|
||||
CATEGORY = "SimpleVariables"
|
||||
|
||||
def passthrough(self, name, value=None):
|
||||
return (value,)
|
||||
|
||||
|
||||
class SimpleGet:
|
||||
"""Get a variable by name"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"name": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = (ANY,)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "get"
|
||||
CATEGORY = "SimpleVariables"
|
||||
|
||||
def get(self, name):
|
||||
return (None,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SimpleSet": SimpleSet,
|
||||
"SimpleGet": SimpleGet,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SimpleSet": "SimpleSet",
|
||||
"SimpleGet": "SimpleGet",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
+89
-168
@@ -1,200 +1,121 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
// Pure frontend Set/Get Variable nodes - based on KJNodes pattern
|
||||
const LGraphNode = LiteGraph.LGraphNode;
|
||||
|
||||
// Shared storage for variables (persists during session)
|
||||
const variableStorage = {};
|
||||
|
||||
// Override Python nodes with virtual node behavior
|
||||
app.registerExtension({
|
||||
name: "SimpleVariables.Set",
|
||||
registerCustomNodes() {
|
||||
class SimpleSetNode extends LGraphNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
name: "SimpleVariables",
|
||||
|
||||
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "SimpleSet") {
|
||||
// Make it a virtual node - passes data through without Python execution
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
if (onNodeCreated) onNodeCreated.apply(this, arguments);
|
||||
|
||||
const node = this;
|
||||
this.isVirtualNode = true;
|
||||
|
||||
this.properties = { variableName: "" };
|
||||
|
||||
this.addWidget("text", "name", "", (value) => {
|
||||
node.properties.variableName = value;
|
||||
node.title = value ? `Set: ${value}` : "SimpleSet";
|
||||
});
|
||||
|
||||
this.addInput("value", "*");
|
||||
this.addOutput("value", "*");
|
||||
// Update title when name changes
|
||||
const nameWidget = this.widgets?.find(w => w.name === "name");
|
||||
if (nameWidget) {
|
||||
const origCallback = nameWidget.callback;
|
||||
nameWidget.callback = function(value) {
|
||||
if (origCallback) origCallback.apply(this, arguments);
|
||||
node.title = value ? `Set: ${value}` : "SimpleSet";
|
||||
};
|
||||
}
|
||||
|
||||
// Update slot types when connection changes
|
||||
this.onConnectionsChange = function(slotType, slot, isConnect, linkInfo) {
|
||||
if (slotType === 1 && node.inputs[0]) {
|
||||
if (isConnect) {
|
||||
const link = node.graph?.links?.[node.inputs[0].link];
|
||||
if (slotType === 1 && node.inputs?.[0]) {
|
||||
if (isConnect && node.graph) {
|
||||
const link = node.graph.links?.[node.inputs[0].link];
|
||||
if (link) {
|
||||
const originNode = node.graph._nodes_by_id[link.origin_id];
|
||||
if (originNode?.outputs?.[link.origin_slot]) {
|
||||
const type = originNode.outputs[link.origin_slot].type;
|
||||
node.inputs[0].type = type;
|
||||
node.inputs[0].name = type;
|
||||
node.outputs[0].type = type;
|
||||
node.outputs[0].name = type;
|
||||
if (node.outputs?.[0]) {
|
||||
node.outputs[0].type = type;
|
||||
node.outputs[0].name = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.inputs[0].type = "*";
|
||||
node.inputs[0].name = "value";
|
||||
node.outputs[0].type = "*";
|
||||
node.outputs[0].name = "value";
|
||||
if (node.outputs?.[0]) {
|
||||
node.outputs[0].type = "*";
|
||||
node.outputs[0].name = "value";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.isVirtualNode = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("SimpleSet", Object.assign(SimpleSetNode, {
|
||||
title: "SimpleSet",
|
||||
}));
|
||||
SimpleSetNode.category = "SimpleVariables";
|
||||
}
|
||||
});
|
||||
if (nodeData.name === "SimpleGet") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
if (onNodeCreated) onNodeCreated.apply(this, arguments);
|
||||
|
||||
app.registerExtension({
|
||||
name: "SimpleVariables.Get",
|
||||
registerCustomNodes() {
|
||||
class SimpleGetNode extends LGraphNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
const node = this;
|
||||
|
||||
this.properties = { variableName: "" };
|
||||
|
||||
this.addWidget("combo", "name", "", (value) => {
|
||||
node.properties.variableName = value;
|
||||
node.title = value ? `Get: ${value}` : "SimpleGet";
|
||||
node.syncType();
|
||||
}, {
|
||||
values: () => {
|
||||
if (!node.graph?._nodes) return [""];
|
||||
const names = node.graph._nodes
|
||||
.filter(n => n.type === "SimpleSet")
|
||||
.map(n => n.properties?.variableName || n.widgets?.[0]?.value || "")
|
||||
.filter(n => n !== "");
|
||||
return ["", ...names.sort()];
|
||||
}
|
||||
});
|
||||
|
||||
this.addOutput("value", "*");
|
||||
this.isVirtualNode = true;
|
||||
}
|
||||
|
||||
syncType() {
|
||||
const setter = this.findSetter();
|
||||
if (setter?.inputs?.[0]) {
|
||||
this.outputs[0].type = setter.inputs[0].type || "*";
|
||||
this.outputs[0].name = setter.inputs[0].name || "value";
|
||||
} else {
|
||||
this.outputs[0].type = "*";
|
||||
this.outputs[0].name = "value";
|
||||
}
|
||||
}
|
||||
|
||||
findSetter() {
|
||||
if (!this.graph || !this.properties.variableName) return null;
|
||||
return this.graph._nodes.find(n =>
|
||||
n.type === "SimpleSet" &&
|
||||
(n.properties?.variableName === this.properties.variableName ||
|
||||
n.widgets?.[0]?.value === this.properties.variableName)
|
||||
);
|
||||
}
|
||||
|
||||
getInputLink(slot) {
|
||||
const setter = this.findSetter();
|
||||
if (setter?.inputs?.[0]?.link != null) {
|
||||
return this.graph.links[setter.inputs[0].link];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("SimpleGet", Object.assign(SimpleGetNode, {
|
||||
title: "SimpleGet",
|
||||
}));
|
||||
SimpleGetNode.category = "SimpleVariables";
|
||||
}
|
||||
});
|
||||
|
||||
app.registerExtension({
|
||||
name: "SimpleVariables.List",
|
||||
registerCustomNodes() {
|
||||
class SimpleListNode extends LGraphNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
const node = this;
|
||||
|
||||
this.addWidget("button", "Refresh", null, () => {
|
||||
node.updateList();
|
||||
});
|
||||
|
||||
this.addOutput("list", "STRING");
|
||||
this.isVirtualNode = true;
|
||||
this.size = [200, 100];
|
||||
}
|
||||
|
||||
updateList() {
|
||||
if (!this.graph?._nodes) return;
|
||||
const setters = this.graph._nodes.filter(n => n.type === "SimpleSet");
|
||||
const names = setters
|
||||
.map(n => n.properties?.variableName || n.widgets?.[0]?.value || "")
|
||||
.filter(n => n !== "");
|
||||
|
||||
// Update title to show variables
|
||||
if (names.length === 0) {
|
||||
this.title = "SimpleList (empty)";
|
||||
} else {
|
||||
this.title = "SimpleList:\n" + names.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
onAdded() {
|
||||
this.updateList();
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("SimpleList", Object.assign(SimpleListNode, {
|
||||
title: "SimpleList",
|
||||
}));
|
||||
SimpleListNode.category = "SimpleVariables";
|
||||
}
|
||||
});
|
||||
|
||||
app.registerExtension({
|
||||
name: "SimpleVariables.Clear",
|
||||
registerCustomNodes() {
|
||||
class SimpleClearNode extends LGraphNode {
|
||||
constructor(title) {
|
||||
super(title);
|
||||
const node = this;
|
||||
|
||||
this.addWidget("button", "Clear All Variables", null, () => {
|
||||
if (!node.graph?._nodes) return;
|
||||
const setters = node.graph._nodes.filter(n => n.type === "SimpleSet");
|
||||
let count = 0;
|
||||
setters.forEach(setter => {
|
||||
if (setter.inputs?.[0]?.link != null) {
|
||||
node.graph.removeLink(setter.inputs[0].link);
|
||||
count++;
|
||||
// Replace name widget with combo that shows available setters
|
||||
const nameWidget = this.widgets?.find(w => w.name === "name");
|
||||
if (nameWidget) {
|
||||
// Convert to combo widget
|
||||
nameWidget.type = "combo";
|
||||
nameWidget.options = {
|
||||
values: () => {
|
||||
if (!node.graph?._nodes) return [""];
|
||||
const names = node.graph._nodes
|
||||
.filter(n => n.type === "SimpleSet")
|
||||
.map(n => n.widgets?.find(w => w.name === "name")?.value || "")
|
||||
.filter(n => n !== "");
|
||||
return ["", ...names.sort()];
|
||||
}
|
||||
});
|
||||
node.title = `SimpleClear (cleared ${count})`;
|
||||
});
|
||||
};
|
||||
|
||||
this.isVirtualNode = true;
|
||||
}
|
||||
const origCallback = nameWidget.callback;
|
||||
nameWidget.callback = function(value) {
|
||||
if (origCallback) origCallback.apply(this, arguments);
|
||||
node.title = value ? `Get: ${value}` : "SimpleGet";
|
||||
node.syncWithSetter();
|
||||
};
|
||||
}
|
||||
|
||||
this.syncWithSetter = function() {
|
||||
const setter = this.findSetter();
|
||||
if (setter?.inputs?.[0] && this.outputs?.[0]) {
|
||||
this.outputs[0].type = setter.inputs[0].type || "*";
|
||||
this.outputs[0].name = setter.inputs[0].name || "value";
|
||||
} else if (this.outputs?.[0]) {
|
||||
this.outputs[0].type = "*";
|
||||
this.outputs[0].name = "value";
|
||||
}
|
||||
};
|
||||
|
||||
this.findSetter = function() {
|
||||
if (!this.graph) return null;
|
||||
const name = this.widgets?.find(w => w.name === "name")?.value;
|
||||
if (!name) return null;
|
||||
return this.graph._nodes.find(n =>
|
||||
n.type === "SimpleSet" &&
|
||||
n.widgets?.find(w => w.name === "name")?.value === name
|
||||
);
|
||||
};
|
||||
|
||||
// Override getInputLink to get data from setter
|
||||
this.getInputLink = function(slot) {
|
||||
const setter = this.findSetter();
|
||||
if (setter?.inputs?.[0]?.link != null) {
|
||||
return this.graph.links[setter.inputs[0].link];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType("SimpleClear", Object.assign(SimpleClearNode, {
|
||||
title: "SimpleClear",
|
||||
}));
|
||||
SimpleClearNode.category = "SimpleVariables";
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user