Back to Python nodes with preview + text input
- SimpleSet/Get use text input (type any name) - Preview shows on node after execution - SimpleGet has trigger input for execution order - SimpleList shows all variables - SimpleClear clears all 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+109
-10
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
SimpleVariables - Set/Get Variable nodes for ComfyUI
|
||||
JavaScript handles the actual logic (virtual nodes), Python stubs make them appear in menus.
|
||||
"""
|
||||
import time
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
@@ -14,14 +14,17 @@ class AnyType(str):
|
||||
|
||||
ANY = AnyType("*")
|
||||
|
||||
# Global storage
|
||||
_variables = {}
|
||||
|
||||
|
||||
class SimpleSet:
|
||||
"""Set a variable by name"""
|
||||
"""Set a variable by name - stores value and passes through"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"name": ("STRING", {"default": ""}),
|
||||
"name": ("STRING", {"default": "var"}),
|
||||
},
|
||||
"optional": {
|
||||
"value": (ANY,),
|
||||
@@ -29,11 +32,30 @@ class SimpleSet:
|
||||
}
|
||||
RETURN_TYPES = (ANY,)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "passthrough"
|
||||
FUNCTION = "set_var"
|
||||
CATEGORY = "SimpleVariables"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def passthrough(self, name, value=None):
|
||||
return (value,)
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, name, value=None):
|
||||
return time.time()
|
||||
|
||||
def set_var(self, name, value=None):
|
||||
_variables[name] = value
|
||||
|
||||
# Generate preview
|
||||
if value is None:
|
||||
preview = f"{name} = None"
|
||||
elif hasattr(value, 'shape'):
|
||||
preview = f"{name} = {type(value).__name__} {list(value.shape)}"
|
||||
elif isinstance(value, (int, float, bool)):
|
||||
preview = f"{name} = {value}"
|
||||
elif isinstance(value, str):
|
||||
preview = f'{name} = "{value[:30]}..."' if len(value) > 30 else f'{name} = "{value}"'
|
||||
else:
|
||||
preview = f"{name} = {type(value).__name__}"
|
||||
|
||||
return {"ui": {"text": [preview]}, "result": (value,)}
|
||||
|
||||
|
||||
class SimpleGet:
|
||||
@@ -42,26 +64,103 @@ class SimpleGet:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"name": ("STRING", {"default": ""}),
|
||||
"name": ("STRING", {"default": "var"}),
|
||||
},
|
||||
"optional": {
|
||||
"trigger": (ANY,), # Connect to SimpleSet output for execution order
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = (ANY,)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "get"
|
||||
FUNCTION = "get_var"
|
||||
CATEGORY = "SimpleVariables"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def get(self, name):
|
||||
return (None,)
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return time.time()
|
||||
|
||||
def get_var(self, name, trigger=None):
|
||||
value = _variables.get(name)
|
||||
|
||||
# Generate preview
|
||||
if name not in _variables:
|
||||
preview = f"{name} = NOT SET"
|
||||
elif value is None:
|
||||
preview = f"{name} = None"
|
||||
elif hasattr(value, 'shape'):
|
||||
preview = f"{name} = {type(value).__name__} {list(value.shape)}"
|
||||
elif isinstance(value, (int, float, bool)):
|
||||
preview = f"{name} = {value}"
|
||||
elif isinstance(value, str):
|
||||
preview = f'{name} = "{value[:30]}..."' if len(value) > 30 else f'{name} = "{value}"'
|
||||
else:
|
||||
preview = f"{name} = {type(value).__name__}"
|
||||
|
||||
return {"ui": {"text": [preview]}, "result": (value,)}
|
||||
|
||||
|
||||
class SimpleList:
|
||||
"""List all stored variables"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("list",)
|
||||
FUNCTION = "list_vars"
|
||||
CATEGORY = "SimpleVariables"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls):
|
||||
return time.time()
|
||||
|
||||
def list_vars(self):
|
||||
if not _variables:
|
||||
text = "(no variables)"
|
||||
else:
|
||||
lines = [f"{k} = {type(v).__name__}" for k, v in _variables.items()]
|
||||
text = "\n".join(lines)
|
||||
return {"ui": {"text": [text]}, "result": (text,)}
|
||||
|
||||
|
||||
class SimpleClear:
|
||||
"""Clear all stored variables"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"clear": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("status",)
|
||||
FUNCTION = "clear_vars"
|
||||
CATEGORY = "SimpleVariables"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def clear_vars(self, clear):
|
||||
if clear:
|
||||
count = len(_variables)
|
||||
_variables.clear()
|
||||
status = f"Cleared {count} variable(s)"
|
||||
else:
|
||||
status = "Set clear=True to clear"
|
||||
return {"ui": {"text": [status]}, "result": (status,)}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SimpleSet": SimpleSet,
|
||||
"SimpleGet": SimpleGet,
|
||||
"SimpleList": SimpleList,
|
||||
"SimpleClear": SimpleClear,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SimpleSet": "SimpleSet",
|
||||
"SimpleGet": "SimpleGet",
|
||||
"SimpleList": "SimpleList",
|
||||
"SimpleClear": "SimpleClear",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
+37
-94
@@ -1,120 +1,63 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||
|
||||
// Override Python nodes with virtual node behavior
|
||||
// Display text on nodes when they execute
|
||||
app.registerExtension({
|
||||
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);
|
||||
beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
|
||||
if (["SimpleSet", "SimpleGet", "SimpleList", "SimpleClear"].includes(nodeData.name)) {
|
||||
|
||||
const node = this;
|
||||
this.isVirtualNode = true;
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function(message) {
|
||||
if (onExecuted) onExecuted.apply(this, arguments);
|
||||
|
||||
// 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";
|
||||
};
|
||||
}
|
||||
if (message?.text) {
|
||||
// Find or create display widget
|
||||
let widget = this.widgets?.find(w => w.name === "_display");
|
||||
|
||||
// Update slot types when connection changes
|
||||
this.onConnectionsChange = function(slotType, slot, isConnect, linkInfo) {
|
||||
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;
|
||||
if (node.outputs?.[0]) {
|
||||
node.outputs[0].type = type;
|
||||
node.outputs[0].name = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.inputs[0].type = "*";
|
||||
node.inputs[0].name = "value";
|
||||
if (node.outputs?.[0]) {
|
||||
node.outputs[0].type = "*";
|
||||
node.outputs[0].name = "value";
|
||||
}
|
||||
}
|
||||
if (!widget) {
|
||||
widget = ComfyWidgets.STRING(this, "_display", [
|
||||
"STRING", { multiline: true }
|
||||
], appInstance).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
widget.inputEl.style.opacity = "0.7";
|
||||
widget.inputEl.style.fontFamily = "monospace";
|
||||
widget.inputEl.style.fontSize = "12px";
|
||||
}
|
||||
};
|
||||
|
||||
const text = Array.isArray(message.text) ? message.text[0] : message.text;
|
||||
widget.value = text || "";
|
||||
|
||||
// Resize node
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) sz[0] = this.size[0];
|
||||
if (sz[1] < this.size[1]) sz[1] = this.size[1];
|
||||
this.onResize?.(sz);
|
||||
appInstance.graph?.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (nodeData.name === "SimpleGet") {
|
||||
// Update title based on name
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
if (onNodeCreated) onNodeCreated.apply(this, arguments);
|
||||
|
||||
const node = this;
|
||||
this.isVirtualNode = true;
|
||||
|
||||
// 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()];
|
||||
}
|
||||
};
|
||||
|
||||
const origCallback = nameWidget.callback;
|
||||
nameWidget.callback = function(value) {
|
||||
if (origCallback) origCallback.apply(this, arguments);
|
||||
node.title = value ? `Get: ${value}` : "SimpleGet";
|
||||
node.syncWithSetter();
|
||||
if (nodeData.name === "SimpleSet") {
|
||||
node.title = value ? `Set: ${value}` : "SimpleSet";
|
||||
} else if (nodeData.name === "SimpleGet") {
|
||||
node.title = value ? `Get: ${value}` : "SimpleGet";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user