V1.92 Efficiency Nodes MEGA Update (SDXL + MORE)
This PR is a huge update, I added/fixed so many things that I will explain all the updates later on the release notes.
4046
efficiency_nodes.py
95
js/appearance.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
|
||||||
|
const COLOR_THEMES = {
|
||||||
|
red: { nodeColor: "#332222", nodeBgColor: "#553333" },
|
||||||
|
green: { nodeColor: "#223322", nodeBgColor: "#335533" },
|
||||||
|
blue: { nodeColor: "#222233", nodeBgColor: "#333355" },
|
||||||
|
pale_blue: { nodeColor: "#2a363b", nodeBgColor: "#3f5159" },
|
||||||
|
cyan: { nodeColor: "#223333", nodeBgColor: "#335555" },
|
||||||
|
purple: { nodeColor: "#332233", nodeBgColor: "#553355" },
|
||||||
|
yellow: { nodeColor: "#443322", nodeBgColor: "#665533" },
|
||||||
|
none: { nodeColor: null, nodeBgColor: null } // no color
|
||||||
|
};
|
||||||
|
|
||||||
|
const NODE_COLORS = {
|
||||||
|
"KSampler (Efficient)": "random",
|
||||||
|
"KSampler Adv. (Efficient)": "random",
|
||||||
|
"KSampler SDXL (Eff.)": "random",
|
||||||
|
"Efficient Loader": "random",
|
||||||
|
"Eff. Loader SDXL": "random",
|
||||||
|
"LoRA Stacker": "blue",
|
||||||
|
"Control Net Stacker": "green",
|
||||||
|
"Apply ControlNet Stack": "none",
|
||||||
|
"XY Plot": "purple",
|
||||||
|
"Unpack SDXL Tuple": "none",
|
||||||
|
"Pack SDXL Tuple": "none",
|
||||||
|
"XY Input: Seeds++ Batch": "cyan",
|
||||||
|
"XY Input: Add/Return Noise": "cyan",
|
||||||
|
"XY Input: Steps": "cyan",
|
||||||
|
"XY Input: CFG Scale": "cyan",
|
||||||
|
"XY Input: Sampler/Scheduler": "cyan",
|
||||||
|
"XY Input: Denoise": "cyan",
|
||||||
|
"XY Input: VAE": "cyan",
|
||||||
|
"XY Input: Prompt S/R": "cyan",
|
||||||
|
"XY Input: Aesthetic Score": "cyan",
|
||||||
|
"XY Input: Refiner On/Off": "cyan",
|
||||||
|
"XY Input: Checkpoint": "cyan",
|
||||||
|
"XY Input: Clip Skip": "cyan",
|
||||||
|
"XY Input: LoRA": "cyan",
|
||||||
|
"XY Input: LoRA Plot": "cyan",
|
||||||
|
"XY Input: LoRA Stacks": "cyan",
|
||||||
|
"XY Input: Control Net": "cyan",
|
||||||
|
"XY Input: Control Net Plot": "cyan",
|
||||||
|
"XY Input: Manual XY Entry": "cyan",
|
||||||
|
"Manual XY Entry Info": "cyan",
|
||||||
|
"Join XY Inputs of Same Type": "cyan",
|
||||||
|
"Image Overlay": "random",
|
||||||
|
"HighRes-Fix Script": "yellow",
|
||||||
|
"Tiled Sampling Script": "none",
|
||||||
|
"Evaluate Integers": "pale_blue",
|
||||||
|
"Evaluate Floats": "pale_blue",
|
||||||
|
"Evaluate Strings": "pale_blue",
|
||||||
|
"Simple Eval Examples": "pale_blue",
|
||||||
|
};
|
||||||
|
|
||||||
|
function shuffleArray(array) {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]]; // Swap elements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorKeys = Object.keys(COLOR_THEMES).filter(key => key !== "none");
|
||||||
|
shuffleArray(colorKeys); // Shuffle the color themes initially
|
||||||
|
|
||||||
|
function setNodeColors(node, theme) {
|
||||||
|
node.shape = "box";
|
||||||
|
if(theme.nodeColor && theme.nodeBgColor) {
|
||||||
|
node.color = theme.nodeColor;
|
||||||
|
node.bgcolor = theme.nodeBgColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = {
|
||||||
|
name: "efficiency.appearance",
|
||||||
|
|
||||||
|
nodeCreated(node) {
|
||||||
|
const title = node.getTitle();
|
||||||
|
if (NODE_COLORS.hasOwnProperty(title)) {
|
||||||
|
let colorKey = NODE_COLORS[title];
|
||||||
|
|
||||||
|
if (colorKey === "random") {
|
||||||
|
if (colorKeys.length === 0) {
|
||||||
|
colorKeys = Object.values(COLOR_THEMES).filter(theme => theme.nodeColor && theme.nodeBgColor);
|
||||||
|
shuffleArray(colorKeys); // Reshuffle when out of colors
|
||||||
|
}
|
||||||
|
colorKey = colorKeys.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = COLOR_THEMES[colorKey];
|
||||||
|
setNodeColors(node, theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app.registerExtension(ext);
|
||||||
106
js/previewfix.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
|
||||||
|
const ext = {
|
||||||
|
name: "efficiency.previewfix",
|
||||||
|
ws: null,
|
||||||
|
maxCount: 0,
|
||||||
|
currentCount: 0,
|
||||||
|
sendBlob: false,
|
||||||
|
startProcessing: false,
|
||||||
|
lastBlobURL: null,
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
log(...args) {
|
||||||
|
if (this.debug) console.log(...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
error(...args) {
|
||||||
|
if (this.debug) console.error(...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendBlobDataAsDataURL(blobURL) {
|
||||||
|
const blob = await fetch(blobURL).then(res => res.blob());
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
reader.onloadend = () => this.ws.send(reader.result);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCommandMessage(data) {
|
||||||
|
Object.assign(this, {
|
||||||
|
maxCount: data.maxCount,
|
||||||
|
sendBlob: data.sendBlob,
|
||||||
|
startProcessing: data.startProcessing,
|
||||||
|
currentCount: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.startProcessing && this.lastBlobURL) {
|
||||||
|
this.log("[BlobURLLogger] Revoking last Blob URL:", this.lastBlobURL);
|
||||||
|
URL.revokeObjectURL(this.lastBlobURL);
|
||||||
|
this.lastBlobURL = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.log("[BlobURLLogger] Initializing...");
|
||||||
|
|
||||||
|
this.ws = new WebSocket('ws://127.0.0.1:8288');
|
||||||
|
|
||||||
|
this.ws.addEventListener('open', () => this.log('[BlobURLLogger] WebSocket connection opened.'));
|
||||||
|
this.ws.addEventListener('error', err => this.error('[BlobURLLogger] WebSocket Error:', err));
|
||||||
|
this.ws.addEventListener('message', (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.maxCount !== undefined && data.sendBlob !== undefined && data.startProcessing !== undefined) {
|
||||||
|
this.handleCommandMessage(data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.error('[BlobURLLogger] Error parsing JSON:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalCreateObjectURL = URL.createObjectURL;
|
||||||
|
URL.createObjectURL = (object) => {
|
||||||
|
const blobURL = originalCreateObjectURL.call(this, object);
|
||||||
|
if (blobURL.startsWith('blob:') && this.startProcessing) {
|
||||||
|
this.log("[BlobURLLogger] Blob URL created:", blobURL);
|
||||||
|
this.lastBlobURL = blobURL;
|
||||||
|
if (this.sendBlob && this.currentCount < this.maxCount) {
|
||||||
|
this.sendBlobDataAsDataURL(blobURL);
|
||||||
|
}
|
||||||
|
this.currentCount++;
|
||||||
|
}
|
||||||
|
return blobURL;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.log("[BlobURLLogger] Hook attached.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function toggleWidgetVisibility(node, widgetName, isVisible) {
|
||||||
|
const widget = node.widgets.find(w => w.name === widgetName);
|
||||||
|
if (widget) {
|
||||||
|
widget.visible = isVisible;
|
||||||
|
node.setDirtyCanvas(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoraNameChange(node, loraNameWidget) {
|
||||||
|
const isNone = loraNameWidget.value === "None";
|
||||||
|
toggleWidgetVisibility(node, "lora_model_strength", !isNone);
|
||||||
|
toggleWidgetVisibility(node, "lora_clip_strength", !isNone);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
...ext,
|
||||||
|
nodeCreated(node) {
|
||||||
|
if (node.getTitle() === "Efficient Loader") {
|
||||||
|
const loraNameWidget = node.widgets.find(w => w.name === "lora_name");
|
||||||
|
if (loraNameWidget) {
|
||||||
|
handleLoraNameChange(node, loraNameWidget);
|
||||||
|
loraNameWidget.onChange = function() {
|
||||||
|
handleLoraNameChange(node, this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
141
js/seedcontrol.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
|
||||||
|
const LAST_SEED_BUTTON_LABEL = '🎲 Randomize / ♻️ Last Queued Seed';
|
||||||
|
|
||||||
|
const NODE_WIDGET_MAP = {
|
||||||
|
"KSampler (Efficient)": "seed",
|
||||||
|
"KSampler Adv. (Efficient)": "noise_seed",
|
||||||
|
"KSampler SDXL (Eff.)": "noise_seed"
|
||||||
|
};
|
||||||
|
|
||||||
|
const SPECIFIC_WIDTH = 325; // Set to desired width
|
||||||
|
|
||||||
|
function setNodeWidthForMappedTitles(node) {
|
||||||
|
if (NODE_WIDGET_MAP[node.getTitle()]) {
|
||||||
|
node.setSize([SPECIFIC_WIDTH, node.size[1]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeedControl {
|
||||||
|
constructor(node, seedName) {
|
||||||
|
this.lastSeed = -1;
|
||||||
|
this.serializedCtx = {};
|
||||||
|
this.node = node;
|
||||||
|
this.holdFlag = false; // Flag to track if sampler_state was set to "Hold"
|
||||||
|
this.usedLastSeedOnHoldRelease = false; // To track if we used the lastSeed after releasing hold
|
||||||
|
|
||||||
|
let controlAfterGenerateIndex;
|
||||||
|
this.samplerStateWidget = this.node.widgets.find(w => w.name === 'sampler_state');
|
||||||
|
|
||||||
|
for (const [i, w] of this.node.widgets.entries()) {
|
||||||
|
if (w.name === seedName) {
|
||||||
|
this.seedWidget = w;
|
||||||
|
} else if (w.name === 'control_after_generate') {
|
||||||
|
controlAfterGenerateIndex = i;
|
||||||
|
this.node.widgets.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.seedWidget) {
|
||||||
|
throw new Error('Something\'s wrong; expected seed widget');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastSeedButton = this.node.addWidget("button", LAST_SEED_BUTTON_LABEL, null, () => {
|
||||||
|
if (this.seedWidget.value != -1) {
|
||||||
|
this.seedWidget.value = -1;
|
||||||
|
} else if (this.lastSeed !== -1) {
|
||||||
|
this.seedWidget.value = this.lastSeed;
|
||||||
|
}
|
||||||
|
}, { width: 50, serialize: false });
|
||||||
|
|
||||||
|
setNodeWidthForMappedTitles(node);
|
||||||
|
if (controlAfterGenerateIndex !== undefined) {
|
||||||
|
const addedWidget = this.node.widgets.pop();
|
||||||
|
this.node.widgets.splice(controlAfterGenerateIndex, 0, addedWidget);
|
||||||
|
setNodeWidthForMappedTitles(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const max = Math.min(1125899906842624, this.seedWidget.options.max);
|
||||||
|
const min = Math.max(-1125899906842624, this.seedWidget.options.min);
|
||||||
|
const range = (max - min) / (this.seedWidget.options.step / 10);
|
||||||
|
|
||||||
|
this.seedWidget.serializeValue = async (node, index) => {
|
||||||
|
const currentSeed = this.seedWidget.value;
|
||||||
|
this.serializedCtx = {
|
||||||
|
wasRandom: currentSeed == -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for the state transition and act accordingly.
|
||||||
|
if (this.samplerStateWidget) {
|
||||||
|
if (this.samplerStateWidget.value !== "Hold" && this.holdFlag && !this.usedLastSeedOnHoldRelease) {
|
||||||
|
this.serializedCtx.seedUsed = this.lastSeed;
|
||||||
|
this.usedLastSeedOnHoldRelease = true;
|
||||||
|
this.holdFlag = false; // Reset flag for the next cycle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.usedLastSeedOnHoldRelease) {
|
||||||
|
if (this.serializedCtx.wasRandom) {
|
||||||
|
this.serializedCtx.seedUsed = Math.floor(Math.random() * range) * (this.seedWidget.options.step / 10) + min;
|
||||||
|
} else {
|
||||||
|
this.serializedCtx.seedUsed = this.seedWidget.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node && node.widgets_values) {
|
||||||
|
node.widgets_values[index] = this.serializedCtx.seedUsed;
|
||||||
|
}else{
|
||||||
|
// Update the last seed value and the button's label to show the current seed value
|
||||||
|
this.lastSeed = this.serializedCtx.seedUsed;
|
||||||
|
this.lastSeedButton.name = `🎲 Randomize / ♻️ ${this.lastSeed}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.seedWidget.value = this.serializedCtx.seedUsed;
|
||||||
|
|
||||||
|
if (this.serializedCtx.wasRandom) {
|
||||||
|
this.lastSeed = this.serializedCtx.seedUsed;
|
||||||
|
this.lastSeedButton.name = `🎲 Randomize / ♻️ ${this.lastSeed}`;
|
||||||
|
if (this.samplerStateWidget.value === "Hold") {
|
||||||
|
this.holdFlag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.usedLastSeedOnHoldRelease && this.samplerStateWidget.value !== "Hold") {
|
||||||
|
// Reset the flag to ensure default behavior is restored
|
||||||
|
this.usedLastSeedOnHoldRelease = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.serializedCtx.seedUsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.seedWidget.afterQueued = () => {
|
||||||
|
if (this.serializedCtx.wasRandom) {
|
||||||
|
this.seedWidget.value = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if seed has changed to a non -1 value, and if so, update lastSeed
|
||||||
|
if (this.seedWidget.value !== -1) {
|
||||||
|
this.lastSeed = this.seedWidget.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the button's label to show the current last seed value
|
||||||
|
this.lastSeedButton.name = `🎲 Randomize / ♻️ ${this.lastSeed}`;
|
||||||
|
|
||||||
|
this.serializedCtx = {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "efficiency.seedcontrol",
|
||||||
|
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
|
||||||
|
if (NODE_WIDGET_MAP[nodeData.name]) {
|
||||||
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||||
|
nodeType.prototype.onNodeCreated = function () {
|
||||||
|
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||||
|
this.seedControl = new SeedControl(this, NODE_WIDGET_MAP[nodeData.name]);
|
||||||
|
this.seedControl.seedWidget.value = -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
452
js/widgethider.js
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
import { app } from "/scripts/app.js";
|
||||||
|
|
||||||
|
let origProps = {};
|
||||||
|
|
||||||
|
const findWidgetByName = (node, name) => {
|
||||||
|
return node.widgets ? node.widgets.find((w) => w.name === name) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doesInputWithNameExist = (node, name) => {
|
||||||
|
return node.inputs ? node.inputs.some((input) => input.name === name) : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
function computeNodeSizeBasedOnWidgetCount(node) {
|
||||||
|
const DEFAULT_BASE_HEIGHT = 40;
|
||||||
|
const NODE_TITLE_BASE_HEIGHTS = {
|
||||||
|
"XY Input: LoRA Stacks": 110,
|
||||||
|
"XY Input: LoRA Plot": 60,
|
||||||
|
"XY Input: Control Net": 80,
|
||||||
|
"XY Input: Control Net Plot": 80,
|
||||||
|
};
|
||||||
|
const BASE_HEIGHT = NODE_TITLE_BASE_HEIGHTS[node.getTitle()] || DEFAULT_BASE_HEIGHT;
|
||||||
|
|
||||||
|
const WIDGET_HEIGHT = 24;
|
||||||
|
let visibleWidgetCount = 0;
|
||||||
|
|
||||||
|
node.widgets.forEach(widget => {
|
||||||
|
if (widget.type !== "tschide") {
|
||||||
|
visibleWidgetCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [node.size[0], BASE_HEIGHT + (visibleWidgetCount * WIDGET_HEIGHT)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Widget + change size
|
||||||
|
function toggleWidget(node, widget, show = false, suffix = "") {
|
||||||
|
if (!widget || doesInputWithNameExist(node, widget.name)) return;
|
||||||
|
if (!origProps[widget.name]) {
|
||||||
|
origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize };
|
||||||
|
}
|
||||||
|
const origSize = node.size;
|
||||||
|
|
||||||
|
widget.type = show ? origProps[widget.name].origType : "tschide" + suffix;
|
||||||
|
widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4];
|
||||||
|
|
||||||
|
const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
|
||||||
|
node.setSize([node.size[0], height]);
|
||||||
|
|
||||||
|
// Compute the new size based on widget count and set it
|
||||||
|
node.setSize(computeNodeSizeBasedOnWidgetCount(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// New function to handle widget visibility based on input_mode
|
||||||
|
function handleInputModeWidgetsVisibility(node, inputModeValue) {
|
||||||
|
// Utility function to generate widget names up to a certain count
|
||||||
|
function generateWidgetNames(baseName, count) {
|
||||||
|
return Array.from({ length: count }, (_, i) => `${baseName}_${i + 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common widget groups
|
||||||
|
const batchWidgets = ["batch_path", "subdirectories", "batch_sort", "batch_max"];
|
||||||
|
const xbatchWidgets = ["X_batch_path", "X_subdirectories", "X_batch_sort", "X_batch_max"];
|
||||||
|
const ckptWidgets = [...generateWidgetNames("ckpt_name", 50)];
|
||||||
|
const clipSkipWidgets = [...generateWidgetNames("clip_skip", 50)];
|
||||||
|
const vaeNameWidgets = [...generateWidgetNames("vae_name", 50)];
|
||||||
|
const loraNameWidgets = [...generateWidgetNames("lora_name", 50)];
|
||||||
|
const loraWtWidgets = [...generateWidgetNames("lora_wt", 50)];
|
||||||
|
const modelStrWidgets = [...generateWidgetNames("model_str", 50)];
|
||||||
|
const clipStrWidgets = [...generateWidgetNames("clip_str", 50)];
|
||||||
|
const xWidgets = ["X_batch_count", "X_first_value", "X_last_value"]
|
||||||
|
const yWidgets = ["Y_batch_count", "Y_first_value", "Y_last_value"]
|
||||||
|
|
||||||
|
const nodeVisibilityMap = {
|
||||||
|
"LoRA Stacker": {
|
||||||
|
"simple": [...modelStrWidgets, ...clipStrWidgets],
|
||||||
|
"advanced": [...loraWtWidgets]
|
||||||
|
},
|
||||||
|
"XY Input: Steps": {
|
||||||
|
"steps": ["first_start_step", "last_start_step", "first_end_step", "last_end_step", "first_refine_step", "last_refine_step"],
|
||||||
|
"start_at_step": ["first_step", "last_step", "first_end_step", "last_end_step", "first_refine_step", "last_refine_step"],
|
||||||
|
"end_at_step": ["first_step", "last_step", "first_start_step", "last_start_step", "first_refine_step", "last_refine_step"],
|
||||||
|
"refine_at_step": ["first_step", "last_step", "first_start_step", "last_start_step", "first_end_step", "last_end_step"]
|
||||||
|
},
|
||||||
|
"XY Input: VAE": {
|
||||||
|
"VAE Names": [...batchWidgets],
|
||||||
|
"VAE Batch": [...vaeNameWidgets, "vae_count"]
|
||||||
|
},
|
||||||
|
"XY Input: Checkpoint": {
|
||||||
|
"Ckpt Names": [...clipSkipWidgets, ...vaeNameWidgets, ...batchWidgets],
|
||||||
|
"Ckpt Names+ClipSkip": [...vaeNameWidgets, ...batchWidgets],
|
||||||
|
"Ckpt Names+ClipSkip+VAE": [...batchWidgets],
|
||||||
|
"Checkpoint Batch": [...ckptWidgets, ...clipSkipWidgets, ...vaeNameWidgets, "ckpt_count"]
|
||||||
|
},
|
||||||
|
"XY Input: LoRA": {
|
||||||
|
"LoRA Names": [...modelStrWidgets, ...clipStrWidgets, ...batchWidgets],
|
||||||
|
"LoRA Names+Weights": [...batchWidgets, "model_strength", "clip_strength"],
|
||||||
|
"LoRA Batch": [...loraNameWidgets, ...modelStrWidgets, ...clipStrWidgets, "lora_count"]
|
||||||
|
},
|
||||||
|
"XY Input: LoRA Plot": {
|
||||||
|
"X: LoRA Batch, Y: LoRA Weight": ["lora_name", "model_strength", "clip_strength", "X_first_value", "X_last_value"],
|
||||||
|
"X: LoRA Batch, Y: Model Strength": ["lora_name", "model_strength", "model_strength", "X_first_value", "X_last_value"],
|
||||||
|
"X: LoRA Batch, Y: Clip Strength": ["lora_name", "clip_strength", "X_first_value", "X_last_value"],
|
||||||
|
"X: Model Strength, Y: Clip Strength": [...xbatchWidgets, "model_strength", "clip_strength"],
|
||||||
|
},
|
||||||
|
"XY Input: Control Net": {
|
||||||
|
"strength": ["first_start_percent", "last_start_percent", "first_end_percent", "last_end_percent", "strength"],
|
||||||
|
"start_percent": ["first_strength", "last_strength", "first_end_percent", "last_end_percent", "start_percent"],
|
||||||
|
"end_percent": ["first_strength", "last_strength", "first_start_percent", "last_start_percent", "end_percent"]
|
||||||
|
},
|
||||||
|
"XY Input: Control Net Plot": {
|
||||||
|
"X: Strength, Y: Start%": ["strength", "start_percent"],
|
||||||
|
"X: Strength, Y: End%": ["strength","end_percent"],
|
||||||
|
"X: Start%, Y: Strength": ["start_percent", "strength"],
|
||||||
|
"X: Start%, Y: End%": ["start_percent", "end_percent"],
|
||||||
|
"X: End%, Y: Strength": ["end_percent", "strength"],
|
||||||
|
"X: End%, Y: Start%": ["end_percent", "start_percent"],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputModeVisibilityMap = nodeVisibilityMap[node.getTitle()];
|
||||||
|
|
||||||
|
if (!inputModeVisibilityMap || !inputModeVisibilityMap[inputModeValue]) return;
|
||||||
|
|
||||||
|
// Reset all widgets to visible
|
||||||
|
for (const key in inputModeVisibilityMap) {
|
||||||
|
for (const widgetName of inputModeVisibilityMap[key]) {
|
||||||
|
const widget = findWidgetByName(node, widgetName);
|
||||||
|
toggleWidget(node, widget, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the specific widgets for the current input_mode value
|
||||||
|
for (const widgetName of inputModeVisibilityMap[inputModeValue]) {
|
||||||
|
const widget = findWidgetByName(node, widgetName);
|
||||||
|
toggleWidget(node, widget, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle multi-widget visibilities
|
||||||
|
function handleVisibility(node, countValue, mode) {
|
||||||
|
const inputModeValue = findWidgetByName(node, "input_mode").value;
|
||||||
|
const baseNamesMap = {
|
||||||
|
"LoRA": ["lora_name", "model_str", "clip_str"],
|
||||||
|
"Checkpoint": ["ckpt_name", "clip_skip", "vae_name"],
|
||||||
|
"LoRA Stacker": ["lora_name", "model_str", "clip_str", "lora_wt"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseNames = baseNamesMap[mode];
|
||||||
|
|
||||||
|
const isBatchMode = inputModeValue.includes("Batch");
|
||||||
|
if (isBatchMode) {
|
||||||
|
countValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= 50; i++) {
|
||||||
|
const nameWidget = findWidgetByName(node, `${baseNames[0]}_${i}`);
|
||||||
|
const firstWidget = findWidgetByName(node, `${baseNames[1]}_${i}`);
|
||||||
|
const secondWidget = findWidgetByName(node, `${baseNames[2]}_${i}`);
|
||||||
|
const thirdWidget = mode === "LoRA Stacker" ? findWidgetByName(node, `${baseNames[3]}_${i}`) : null;
|
||||||
|
|
||||||
|
if (i <= countValue) {
|
||||||
|
toggleWidget(node, nameWidget, true);
|
||||||
|
|
||||||
|
if (mode === "LoRA Stacker") {
|
||||||
|
if (inputModeValue === "simple") {
|
||||||
|
toggleWidget(node, firstWidget, false); // model_str
|
||||||
|
toggleWidget(node, secondWidget, false); // clip_str
|
||||||
|
toggleWidget(node, thirdWidget, true); // lora_wt
|
||||||
|
} else if (inputModeValue === "advanced") {
|
||||||
|
toggleWidget(node, firstWidget, true); // model_str
|
||||||
|
toggleWidget(node, secondWidget, true); // clip_str
|
||||||
|
toggleWidget(node, thirdWidget, false); // lora_wt
|
||||||
|
}
|
||||||
|
} else if (inputModeValue.includes("Names+Weights") || inputModeValue.includes("+ClipSkip")) {
|
||||||
|
toggleWidget(node, firstWidget, true);
|
||||||
|
}
|
||||||
|
if (!inputModeValue.includes("Names") && mode !== "LoRA Stacker") {
|
||||||
|
toggleWidget(node, secondWidget, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toggleWidget(node, nameWidget, false);
|
||||||
|
toggleWidget(node, firstWidget, false);
|
||||||
|
toggleWidget(node, secondWidget, false);
|
||||||
|
if (thirdWidget) {
|
||||||
|
toggleWidget(node, thirdWidget, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sampler & Scheduler XY input visibility logic
|
||||||
|
function handleSamplerSchedulerVisibility(node, countValue, targetParameter) {
|
||||||
|
for (let i = 1; i <= 50; i++) {
|
||||||
|
const samplerWidget = findWidgetByName(node, `sampler_${i}`);
|
||||||
|
const schedulerWidget = findWidgetByName(node, `scheduler_${i}`);
|
||||||
|
|
||||||
|
if (i <= countValue) {
|
||||||
|
if (targetParameter === "sampler") {
|
||||||
|
toggleWidget(node, samplerWidget, true);
|
||||||
|
toggleWidget(node, schedulerWidget, false);
|
||||||
|
} else if (targetParameter === "scheduler") {
|
||||||
|
toggleWidget(node, samplerWidget, false);
|
||||||
|
toggleWidget(node, schedulerWidget, true);
|
||||||
|
} else { // targetParameter is "sampler & scheduler"
|
||||||
|
toggleWidget(node, samplerWidget, true);
|
||||||
|
toggleWidget(node, schedulerWidget, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toggleWidget(node, samplerWidget, false);
|
||||||
|
toggleWidget(node, schedulerWidget, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle simple widget visibility based on a count
|
||||||
|
function handleWidgetVisibility(node, thresholdValue, widgetNamePrefix, maxCount) {
|
||||||
|
for (let i = 1; i <= maxCount; i++) {
|
||||||
|
const widget = findWidgetByName(node, `${widgetNamePrefix}${i}`);
|
||||||
|
if (widget) {
|
||||||
|
toggleWidget(node, widget, i <= thresholdValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the 'Ckpt Name+ClipSkip+VAE' option if 'target_ckpt' is "Refiner"
|
||||||
|
let last_ckpt_input_mode;
|
||||||
|
let last_target_ckpt;
|
||||||
|
function xyCkptRefinerOptionsRemove(widget, node) {
|
||||||
|
|
||||||
|
let target_ckpt = findWidgetByName(node, "target_ckpt").value
|
||||||
|
let input_mode = widget.value
|
||||||
|
|
||||||
|
if ((input_mode === "Ckpt Names+ClipSkip+VAE") && (target_ckpt === "Refiner")) {
|
||||||
|
if (last_ckpt_input_mode === "Ckpt Names+ClipSkip") {
|
||||||
|
if (last_target_ckpt === "Refiner"){
|
||||||
|
widget.value = "Checkpoint Batch";
|
||||||
|
} else {widget.value = "Ckpt Names+ClipSkip";}
|
||||||
|
} else if (last_ckpt_input_mode === "Checkpoint Batch") {
|
||||||
|
if (last_target_ckpt === "Refiner"){
|
||||||
|
widget.value = "Ckpt Names+ClipSkip";
|
||||||
|
} else {widget.value = "Checkpoint Batch";}
|
||||||
|
} else if (last_ckpt_input_mode !== 'undefined') {
|
||||||
|
widget.value = last_ckpt_input_mode;
|
||||||
|
} else {
|
||||||
|
widget.value = "Ckpt Names";
|
||||||
|
}
|
||||||
|
} else if (input_mode !== "Ckpt Names+ClipSkip+VAE"){
|
||||||
|
last_ckpt_input_mode = input_mode;
|
||||||
|
}
|
||||||
|
last_target_ckpt = target_ckpt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map of node titles to their respective widget handlers
|
||||||
|
const nodeWidgetHandlers = {
|
||||||
|
"Efficient Loader": {
|
||||||
|
'lora_name': handleEfficientLoaderLoraName
|
||||||
|
},
|
||||||
|
"Eff. Loader SDXL": {
|
||||||
|
'refiner_ckpt_name': handleEffLoaderSDXLRefinerCkptName
|
||||||
|
},
|
||||||
|
"LoRA Stacker": {
|
||||||
|
'input_mode': handleLoRAStackerInputMode,
|
||||||
|
'lora_count': handleLoRAStackerLoraCount
|
||||||
|
},
|
||||||
|
"XY Input: Steps": {
|
||||||
|
'target_parameter': handleXYInputStepsTargetParameter
|
||||||
|
},
|
||||||
|
"XY Input: Sampler/Scheduler": {
|
||||||
|
'target_parameter': handleXYInputSamplerSchedulerTargetParameter,
|
||||||
|
'input_count': handleXYInputSamplerSchedulerInputCount
|
||||||
|
},
|
||||||
|
"XY Input: VAE": {
|
||||||
|
'input_mode': handleXYInputVAEInputMode,
|
||||||
|
'vae_count': handleXYInputVAEVaeCount
|
||||||
|
},
|
||||||
|
"XY Input: Prompt S/R": {
|
||||||
|
'replace_count': handleXYInputPromptSRReplaceCount
|
||||||
|
},
|
||||||
|
"XY Input: Checkpoint": {
|
||||||
|
'input_mode': handleXYInputCheckpointInputMode,
|
||||||
|
'ckpt_count': handleXYInputCheckpointCkptCount,
|
||||||
|
'target_ckpt': handleXYInputCheckpointTargetCkpt
|
||||||
|
},
|
||||||
|
"XY Input: LoRA": {
|
||||||
|
'input_mode': handleXYInputLoRAInputMode,
|
||||||
|
'lora_count': handleXYInputLoRALoraCount
|
||||||
|
},
|
||||||
|
"XY Input: LoRA Plot": {
|
||||||
|
'input_mode': handleXYInputLoRAPlotInputMode
|
||||||
|
},
|
||||||
|
"XY Input: LoRA Stacks": {
|
||||||
|
'node_state': handleXYInputLoRAStacksNodeState
|
||||||
|
},
|
||||||
|
"XY Input: Control Net": {
|
||||||
|
'target_parameter': handleXYInputControlNetTargetParameter
|
||||||
|
},
|
||||||
|
"XY Input: Control Net Plot": {
|
||||||
|
'plot_type': handleXYInputControlNetPlotPlotType
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// In the main function where widgetLogic is called
|
||||||
|
function widgetLogic(node, widget) {
|
||||||
|
// Retrieve the handler for the current node title and widget name
|
||||||
|
const handler = nodeWidgetHandlers[node.getTitle()]?.[widget.name];
|
||||||
|
if (handler) {
|
||||||
|
handler(node, widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Efficient Loader Handlers
|
||||||
|
function handleEfficientLoaderLoraName(node, widget) {
|
||||||
|
const action = widget.value === 'None' ? toggleWidget : (node, widget) => toggleWidget(node, widget, true);
|
||||||
|
['lora_model_strength', 'lora_clip_strength'].forEach(wName => {
|
||||||
|
action(node, findWidgetByName(node, wName));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eff. Loader SDXL Handlers
|
||||||
|
function handleEffLoaderSDXLRefinerCkptName(node, widget) {
|
||||||
|
const action = widget.value === 'None' ? toggleWidget : (node, widget) => toggleWidget(node, widget, true);
|
||||||
|
['refiner_clip_skip', 'positive_ascore', 'negative_ascore'].forEach(wName => {
|
||||||
|
action(node, findWidgetByName(node, wName));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoRA Stacker Handlers
|
||||||
|
function handleLoRAStackerInputMode(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
handleVisibility(node, findWidgetByName(node, "lora_count").value, "LoRA Stacker");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoRAStackerLoraCount(node, widget) {
|
||||||
|
handleVisibility(node, widget.value, "LoRA Stacker");
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Steps Handlers
|
||||||
|
function handleXYInputStepsTargetParameter(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Sampler/Scheduler Handlers
|
||||||
|
function handleXYInputSamplerSchedulerTargetParameter(node, widget) {
|
||||||
|
handleSamplerSchedulerVisibility(node, findWidgetByName(node, 'input_count').value, widget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXYInputSamplerSchedulerInputCount(node, widget) {
|
||||||
|
handleSamplerSchedulerVisibility(node, widget.value, findWidgetByName(node, 'target_parameter').value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: VAE Handlers
|
||||||
|
function handleXYInputVAEInputMode(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
if (widget.value === "VAE Names") {
|
||||||
|
handleWidgetVisibility(node, findWidgetByName(node, "vae_count").value, "vae_name_", 50);
|
||||||
|
} else {
|
||||||
|
handleWidgetVisibility(node, 0, "vae_name_", 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXYInputVAEVaeCount(node, widget) {
|
||||||
|
if (findWidgetByName(node, "input_mode").value === "VAE Names") {
|
||||||
|
handleWidgetVisibility(node, widget.value, "vae_name_", 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Prompt S/R Handlers
|
||||||
|
function handleXYInputPromptSRReplaceCount(node, widget) {
|
||||||
|
handleWidgetVisibility(node, widget.value, "replace_", 49);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Checkpoint Handlers
|
||||||
|
function handleXYInputCheckpointInputMode(node, widget) {
|
||||||
|
xyCkptRefinerOptionsRemove(widget, node);
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
handleVisibility(node, findWidgetByName(node, "ckpt_count").value, "Checkpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXYInputCheckpointCkptCount(node, widget) {
|
||||||
|
handleVisibility(node, widget.value, "Checkpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXYInputCheckpointTargetCkpt(node, widget) {
|
||||||
|
xyCkptRefinerOptionsRemove(findWidgetByName(node, "input_mode"), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: LoRA Handlers
|
||||||
|
function handleXYInputLoRAInputMode(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
handleVisibility(node, findWidgetByName(node, "lora_count").value, "LoRA");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleXYInputLoRALoraCount(node, widget) {
|
||||||
|
handleVisibility(node, widget.value, "LoRA");
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: LoRA Plot Handlers
|
||||||
|
function handleXYInputLoRAPlotInputMode(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: LoRA Stacks Handlers
|
||||||
|
function handleXYInputLoRAStacksNodeState(node, widget) {
|
||||||
|
toggleWidget(node, findWidgetByName(node, "node_state"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Control Net Handlers
|
||||||
|
function handleXYInputControlNetTargetParameter(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY Input: Control Net Plot Handlers
|
||||||
|
function handleXYInputControlNetPlotPlotType(node, widget) {
|
||||||
|
handleInputModeWidgetsVisibility(node, widget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "efficiency.widgethider",
|
||||||
|
nodeCreated(node) {
|
||||||
|
for (const w of node.widgets || []) {
|
||||||
|
let widgetValue = w.value;
|
||||||
|
|
||||||
|
// Store the original descriptor if it exists
|
||||||
|
let originalDescriptor = Object.getOwnPropertyDescriptor(w, 'value');
|
||||||
|
|
||||||
|
widgetLogic(node, w);
|
||||||
|
|
||||||
|
Object.defineProperty(w, 'value', {
|
||||||
|
get() {
|
||||||
|
// If there's an original getter, use it. Otherwise, return widgetValue.
|
||||||
|
let valueToReturn = originalDescriptor && originalDescriptor.get
|
||||||
|
? originalDescriptor.get.call(w)
|
||||||
|
: widgetValue;
|
||||||
|
|
||||||
|
return valueToReturn;
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
|
||||||
|
// If there's an original setter, use it. Otherwise, set widgetValue.
|
||||||
|
if (originalDescriptor && originalDescriptor.set) {
|
||||||
|
originalDescriptor.set.call(w, newVal);
|
||||||
|
} else {
|
||||||
|
widgetValue = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
widgetLogic(node, w);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
{
|
{
|
||||||
"Efficient Loader": {
|
"Efficient Loader": {
|
||||||
"model_cache": {
|
"model_cache": {
|
||||||
"ckpt": 1,
|
|
||||||
"vae": 1,
|
"vae": 1,
|
||||||
"lora": 1
|
"ckpt": 1,
|
||||||
|
"lora": 1,
|
||||||
|
"refn": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"XY Plot": {
|
"XY Plot": {
|
||||||
"model_cache": {
|
"model_cache": {
|
||||||
"ckpt": 5,
|
|
||||||
"vae": 5,
|
"vae": 5,
|
||||||
"lora": 5
|
"ckpt": 5,
|
||||||
|
"lora": 5,
|
||||||
|
"refn": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
178
tsc_utils.py
@@ -31,12 +31,13 @@ from comfy.cli_args import args
|
|||||||
# Cache for Efficiency Node models
|
# Cache for Efficiency Node models
|
||||||
loaded_objects = {
|
loaded_objects = {
|
||||||
"ckpt": [], # (ckpt_name, ckpt_model, clip, bvae, [id])
|
"ckpt": [], # (ckpt_name, ckpt_model, clip, bvae, [id])
|
||||||
|
"refn": [], # (ckpt_name, ckpt_model, clip, bvae, [id])
|
||||||
"vae": [], # (vae_name, vae, [id])
|
"vae": [], # (vae_name, vae, [id])
|
||||||
"lora": [] # ([(lora_name, strength_model, strength_clip)], ckpt_name, lora_model, clip_lora, [id])
|
"lora": [] # ([(lora_name, strength_model, strength_clip)], ckpt_name, lora_model, clip_lora, [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache for Ksampler (Efficient) Outputs
|
# Cache for Ksampler (Efficient) Outputs
|
||||||
last_helds: dict[str, list] = {
|
last_helds = {
|
||||||
"preview_images": [], # (preview_images, id) # Preview Images, stored as a pil image list
|
"preview_images": [], # (preview_images, id) # Preview Images, stored as a pil image list
|
||||||
"latent": [], # (latent, id) # Latent outputs, stored as a latent tensor list
|
"latent": [], # (latent, id) # Latent outputs, stored as a latent tensor list
|
||||||
"output_images": [], # (output_images, id) # Output Images, stored as an image tensor list
|
"output_images": [], # (output_images, id) # Output Images, stored as an image tensor list
|
||||||
@@ -53,6 +54,29 @@ def tensor2pil(image: torch.Tensor) -> Image.Image:
|
|||||||
def pil2tensor(image: Image.Image) -> torch.Tensor:
|
def pil2tensor(image: Image.Image) -> torch.Tensor:
|
||||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||||
|
|
||||||
|
# Color coded messages functions
|
||||||
|
MESSAGE_COLOR = "\033[36m" # Cyan
|
||||||
|
XYPLOT_COLOR = "\033[35m" # Purple
|
||||||
|
SUCCESS_COLOR = "\033[92m" # Green
|
||||||
|
WARNING_COLOR = "\033[93m" # Yellow
|
||||||
|
ERROR_COLOR = "\033[91m" # Red
|
||||||
|
INFO_COLOR = "\033[90m" # Gray
|
||||||
|
def format_message(text, color_code):
|
||||||
|
RESET_COLOR = "\033[0m"
|
||||||
|
return f"{color_code}{text}{RESET_COLOR}"
|
||||||
|
def message(text):
|
||||||
|
return format_message(text, MESSAGE_COLOR)
|
||||||
|
def warning(text):
|
||||||
|
return format_message(text, WARNING_COLOR)
|
||||||
|
def error(text):
|
||||||
|
return format_message(text, ERROR_COLOR)
|
||||||
|
def success(text):
|
||||||
|
return format_message(text, SUCCESS_COLOR)
|
||||||
|
def xyplot_message(text):
|
||||||
|
return format_message(text, XYPLOT_COLOR)
|
||||||
|
def info(text):
|
||||||
|
return format_message(text, INFO_COLOR)
|
||||||
|
|
||||||
def extract_node_info(prompt, id, indirect_key=None):
|
def extract_node_info(prompt, id, indirect_key=None):
|
||||||
# Convert ID to string
|
# Convert ID to string
|
||||||
id = str(id)
|
id = str(id)
|
||||||
@@ -95,7 +119,7 @@ def print_loaded_objects_entries(id=None, prompt=None, show_id=False):
|
|||||||
else:
|
else:
|
||||||
print(f"\033[36mModels Cache: \nnode_id:{int(id)}\033[0m")
|
print(f"\033[36mModels Cache: \nnode_id:{int(id)}\033[0m")
|
||||||
entries_found = False
|
entries_found = False
|
||||||
for key in ["ckpt", "vae", "lora"]:
|
for key in ["ckpt", "refn", "vae", "lora"]:
|
||||||
entries_with_id = loaded_objects[key] if id is None else [entry for entry in loaded_objects[key] if id in entry[-1]]
|
entries_with_id = loaded_objects[key] if id is None else [entry for entry in loaded_objects[key] if id in entry[-1]]
|
||||||
if not entries_with_id: # If no entries with the chosen ID, print None and skip this key
|
if not entries_with_id: # If no entries with the chosen ID, print None and skip this key
|
||||||
continue
|
continue
|
||||||
@@ -103,13 +127,15 @@ def print_loaded_objects_entries(id=None, prompt=None, show_id=False):
|
|||||||
print(f"{key.capitalize()}:")
|
print(f"{key.capitalize()}:")
|
||||||
for i, entry in enumerate(entries_with_id, 1): # Start numbering from 1
|
for i, entry in enumerate(entries_with_id, 1): # Start numbering from 1
|
||||||
if key == "lora":
|
if key == "lora":
|
||||||
lora_models_info = ', '.join(f"{os.path.splitext(os.path.basename(name))[0]}({round(strength_model, 2)},{round(strength_clip, 2)})" for name, strength_model, strength_clip in entry[0])
|
|
||||||
base_ckpt_name = os.path.splitext(os.path.basename(entry[1]))[0] # Split logic for base_ckpt
|
base_ckpt_name = os.path.splitext(os.path.basename(entry[1]))[0] # Split logic for base_ckpt
|
||||||
if id is None:
|
if id is None:
|
||||||
associated_ids = ', '.join(map(str, entry[-1])) # Gather all associated ids
|
associated_ids = ', '.join(map(str, entry[-1])) # Gather all associated ids
|
||||||
print(f" [{i}] base_ckpt: {base_ckpt_name}, lora(mod,clip): {lora_models_info} (ids: {associated_ids})")
|
print(f" [{i}] base_ckpt: {base_ckpt_name} (ids: {associated_ids})")
|
||||||
else:
|
else:
|
||||||
print(f" [{i}] base_ckpt: {base_ckpt_name}, lora(mod,clip): {lora_models_info}")
|
print(f" [{i}] base_ckpt: {base_ckpt_name}")
|
||||||
|
for name, strength_model, strength_clip in entry[0]:
|
||||||
|
lora_model_info = f"{os.path.splitext(os.path.basename(name))[0]}({round(strength_model, 2)},{round(strength_clip, 2)})"
|
||||||
|
print(f" lora(mod,clip): {lora_model_info}")
|
||||||
else:
|
else:
|
||||||
name_without_ext = os.path.splitext(os.path.basename(entry[0]))[0]
|
name_without_ext = os.path.splitext(os.path.basename(entry[0]))[0]
|
||||||
if id is None:
|
if id is None:
|
||||||
@@ -146,59 +172,54 @@ def globals_cleanup(prompt):
|
|||||||
loaded_objects[key].remove(tup)
|
loaded_objects[key].remove(tup)
|
||||||
###print(f'Deleted tuple at index {i} in {key} in loaded_objects because its id array became empty.')
|
###print(f'Deleted tuple at index {i} in {key} in loaded_objects because its id array became empty.')
|
||||||
|
|
||||||
def load_checkpoint(ckpt_name, id, output_vae=True, cache=None, cache_overwrite=False):
|
def load_checkpoint(ckpt_name, id, output_vae=True, cache=None, cache_overwrite=False, ckpt_type="ckpt"):
|
||||||
"""
|
|
||||||
Searches for tuple index that contains ckpt_name in "ckpt" array of loaded_objects.
|
|
||||||
If found, extracts the model, clip, and vae from the loaded_objects.
|
|
||||||
If not found, loads the checkpoint, extracts the model, clip, and vae.
|
|
||||||
The id parameter represents the node ID and is used for caching models for the XY Plot node.
|
|
||||||
If the cache limit is reached for a specific id, clears the cache and returns the loaded model, clip, and vae without adding a new entry.
|
|
||||||
If there is cache space, adds the id to the ids list if it's not already there.
|
|
||||||
If there is cache space and the checkpoint was not found in loaded_objects, adds a new entry to loaded_objects.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- ckpt_name: name of the checkpoint to load.
|
|
||||||
- id: an identifier for caching models for specific nodes.
|
|
||||||
- output_vae: boolean, if True loads the VAE too.
|
|
||||||
- cache (optional): an integer that specifies how many checkpoint entries with a given id can exist in loaded_objects. Defaults to None.
|
|
||||||
"""
|
|
||||||
global loaded_objects
|
global loaded_objects
|
||||||
|
|
||||||
for entry in loaded_objects["ckpt"]:
|
# Create copies of the arguments right at the start
|
||||||
|
ckpt_name = ckpt_name.copy() if isinstance(ckpt_name, (list, dict, set)) else ckpt_name
|
||||||
|
|
||||||
|
# Check if the type is valid
|
||||||
|
if ckpt_type not in ["ckpt", "refn"]:
|
||||||
|
raise ValueError(f"Invalid checkpoint type: {ckpt_type}")
|
||||||
|
|
||||||
|
for entry in loaded_objects[ckpt_type]:
|
||||||
if entry[0] == ckpt_name:
|
if entry[0] == ckpt_name:
|
||||||
_, model, clip, vae, ids = entry
|
_, model, clip, vae, ids = entry
|
||||||
cache_full = cache and len([entry for entry in loaded_objects["ckpt"] if id in entry[-1]]) >= cache
|
cache_full = cache and len([entry for entry in loaded_objects[ckpt_type] if id in entry[-1]]) >= cache
|
||||||
|
|
||||||
if cache_full:
|
if cache_full:
|
||||||
clear_cache(id, cache, "ckpt")
|
clear_cache(id, cache, ckpt_type)
|
||||||
elif id not in ids:
|
elif id not in ids:
|
||||||
ids.append(id)
|
ids.append(id)
|
||||||
|
|
||||||
return model, clip, vae
|
return model, clip, vae
|
||||||
|
|
||||||
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
if os.path.isabs(ckpt_name):
|
||||||
|
ckpt_path = ckpt_name
|
||||||
|
else:
|
||||||
|
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||||
with suppress_output():
|
with suppress_output():
|
||||||
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae, output_clip=True,
|
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||||
embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
|
||||||
model = out[0]
|
model = out[0]
|
||||||
clip = out[1]
|
clip = out[1]
|
||||||
vae = out[2] # bvae
|
vae = out[2] if output_vae else None # Load VAE from the checkpoint path only if output_vae is True
|
||||||
|
|
||||||
if cache:
|
if cache:
|
||||||
if len([entry for entry in loaded_objects["ckpt"] if id in entry[-1]]) < cache:
|
cache_list = [entry for entry in loaded_objects[ckpt_type] if id in entry[-1]]
|
||||||
loaded_objects["ckpt"].append((ckpt_name, model, clip, vae, [id]))
|
if len(cache_list) < cache:
|
||||||
|
loaded_objects[ckpt_type].append((ckpt_name, model, clip, vae, [id]))
|
||||||
else:
|
else:
|
||||||
clear_cache(id, cache, "ckpt")
|
clear_cache(id, cache, ckpt_type)
|
||||||
if cache_overwrite:
|
if cache_overwrite:
|
||||||
# Find the first entry with the id, remove the id from the entry's id list
|
for e in loaded_objects[ckpt_type]:
|
||||||
for e in loaded_objects["ckpt"]:
|
|
||||||
if id in e[-1]:
|
if id in e[-1]:
|
||||||
e[-1].remove(id)
|
e[-1].remove(id)
|
||||||
# If the id list becomes empty, remove the entry from the "ckpt" list
|
# If the id list becomes empty, remove the entry from the ckpt_type list
|
||||||
if not e[-1]:
|
if not e[-1]:
|
||||||
loaded_objects["ckpt"].remove(e)
|
loaded_objects[ckpt_type].remove(e)
|
||||||
break
|
break
|
||||||
loaded_objects["ckpt"].append((ckpt_name, model, clip, vae, [id]))
|
loaded_objects[ckpt_type].append((ckpt_name, model, clip, vae, [id]))
|
||||||
|
|
||||||
return model, clip, vae
|
return model, clip, vae
|
||||||
|
|
||||||
@@ -209,21 +230,11 @@ def get_bvae_by_ckpt_name(ckpt_name):
|
|||||||
return None # return None if no match is found
|
return None # return None if no match is found
|
||||||
|
|
||||||
def load_vae(vae_name, id, cache=None, cache_overwrite=False):
|
def load_vae(vae_name, id, cache=None, cache_overwrite=False):
|
||||||
"""
|
|
||||||
Extracts the vae with a given name from the "vae" array in loaded_objects.
|
|
||||||
If the vae is not found, creates a new VAE object with the given name and adds it to the "vae" array.
|
|
||||||
Also stores the id parameter, which is used for caching models specifically for nodes with the given ID.
|
|
||||||
If the cache limit is reached for a specific id, returns the loaded vae without adding id or making a new entry in loaded_objects.
|
|
||||||
If there is cache space, and the id is not in the ids list, adds the id to the ids list.
|
|
||||||
If there is cache space, and the vae was not found in loaded_objects, adds a new entry to the loaded_objects.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- vae_name: name of the VAE to load.
|
|
||||||
- id (optional): an identifier for caching models for specific nodes. Defaults to None.
|
|
||||||
- cache (optional): an integer that specifies how many vae entries with a given id can exist in loaded_objects. Defaults to None.
|
|
||||||
"""
|
|
||||||
global loaded_objects
|
global loaded_objects
|
||||||
|
|
||||||
|
# Create copies of the argument right at the start
|
||||||
|
vae_name = vae_name.copy() if isinstance(vae_name, (list, dict, set)) else vae_name
|
||||||
|
|
||||||
for i, entry in enumerate(loaded_objects["vae"]):
|
for i, entry in enumerate(loaded_objects["vae"]):
|
||||||
if entry[0] == vae_name:
|
if entry[0] == vae_name:
|
||||||
vae, ids = entry[1], entry[2]
|
vae, ids = entry[1], entry[2]
|
||||||
@@ -235,7 +246,10 @@ def load_vae(vae_name, id, cache=None, cache_overwrite=False):
|
|||||||
clear_cache(id, cache, "vae")
|
clear_cache(id, cache, "vae")
|
||||||
return vae
|
return vae
|
||||||
|
|
||||||
vae_path = folder_paths.get_full_path("vae", vae_name)
|
if os.path.isabs(vae_name):
|
||||||
|
vae_path = vae_name
|
||||||
|
else:
|
||||||
|
vae_path = folder_paths.get_full_path("vae", vae_name)
|
||||||
vae = comfy.sd.VAE(ckpt_path=vae_path)
|
vae = comfy.sd.VAE(ckpt_path=vae_path)
|
||||||
|
|
||||||
if cache:
|
if cache:
|
||||||
@@ -257,23 +271,14 @@ def load_vae(vae_name, id, cache=None, cache_overwrite=False):
|
|||||||
return vae
|
return vae
|
||||||
|
|
||||||
def load_lora(lora_params, ckpt_name, id, cache=None, ckpt_cache=None, cache_overwrite=False):
|
def load_lora(lora_params, ckpt_name, id, cache=None, ckpt_cache=None, cache_overwrite=False):
|
||||||
"""
|
|
||||||
Extracts the Lora model with a given name from the "lora" array in loaded_objects.
|
|
||||||
If the Lora model is not found or strength values changed or model changed, creates a new Lora object with the given name and adds it to the "lora" array.
|
|
||||||
Also stores the id parameter, which is used for caching models specifically for nodes with the given ID.
|
|
||||||
If the cache limit is reached for a specific id, clears the cache and returns the loaded Lora model and clip without adding a new entry.
|
|
||||||
If there is cache space, adds the id to the ids list if it's not already there.
|
|
||||||
If there is cache space and the Lora model was not found in loaded_objects, adds a new entry to loaded_objects.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- lora_params: A list of tuples, where each tuple contains lora_name, strength_model, strength_clip.
|
|
||||||
- ckpt_name: name of the checkpoint from which the Lora model is created.
|
|
||||||
- id: an identifier for caching models for specific nodes.
|
|
||||||
- cache (optional): an integer that specifies how many Lora entries with a given id can exist in loaded_objects. Defaults to None.
|
|
||||||
"""
|
|
||||||
global loaded_objects
|
global loaded_objects
|
||||||
|
|
||||||
|
# Create copies of the arguments right at the start
|
||||||
|
lora_params = lora_params.copy() if isinstance(lora_params, (list, dict, set)) else lora_params
|
||||||
|
ckpt_name = ckpt_name.copy() if isinstance(ckpt_name, (list, dict, set)) else ckpt_name
|
||||||
|
|
||||||
for entry in loaded_objects["lora"]:
|
for entry in loaded_objects["lora"]:
|
||||||
|
|
||||||
# Convert to sets and compare
|
# Convert to sets and compare
|
||||||
if set(entry[0]) == set(lora_params) and entry[1] == ckpt_name:
|
if set(entry[0]) == set(lora_params) and entry[1] == ckpt_name:
|
||||||
|
|
||||||
@@ -304,7 +309,11 @@ def load_lora(lora_params, ckpt_name, id, cache=None, ckpt_cache=None, cache_ove
|
|||||||
return ckpt, clip
|
return ckpt, clip
|
||||||
|
|
||||||
lora_name, strength_model, strength_clip = lora_params[0]
|
lora_name, strength_model, strength_clip = lora_params[0]
|
||||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
if os.path.isabs(lora_name):
|
||||||
|
lora_path = lora_name
|
||||||
|
else:
|
||||||
|
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||||
|
|
||||||
lora_model, lora_clip = comfy.sd.load_lora_for_models(ckpt, clip, comfy.utils.load_torch_file(lora_path), strength_model, strength_clip)
|
lora_model, lora_clip = comfy.sd.load_lora_for_models(ckpt, clip, comfy.utils.load_torch_file(lora_path), strength_model, strength_clip)
|
||||||
|
|
||||||
# Call the function again with the new lora_model and lora_clip and the remaining tuples
|
# Call the function again with the new lora_model and lora_clip and the remaining tuples
|
||||||
@@ -336,7 +345,7 @@ def load_lora(lora_params, ckpt_name, id, cache=None, ckpt_cache=None, cache_ove
|
|||||||
|
|
||||||
def clear_cache(id, cache, dict_name):
|
def clear_cache(id, cache, dict_name):
|
||||||
"""
|
"""
|
||||||
Clear the cache for a specific id in a specific dictionary (either "ckpt" or "vae").
|
Clear the cache for a specific id in a specific dictionary.
|
||||||
If the cache limit is reached for a specific id, deletes the id from the oldest entry.
|
If the cache limit is reached for a specific id, deletes the id from the oldest entry.
|
||||||
If the id array of the entry becomes empty, deletes the entry.
|
If the id array of the entry becomes empty, deletes the entry.
|
||||||
"""
|
"""
|
||||||
@@ -353,16 +362,18 @@ def clear_cache(id, cache, dict_name):
|
|||||||
# Update the id_associated_entries
|
# Update the id_associated_entries
|
||||||
id_associated_entries = [entry for entry in loaded_objects[dict_name] if id in entry[-1]]
|
id_associated_entries = [entry for entry in loaded_objects[dict_name] if id in entry[-1]]
|
||||||
|
|
||||||
def clear_cache_by_exception(node_id, vae_dict=None, ckpt_dict=None, lora_dict=None):
|
|
||||||
|
def clear_cache_by_exception(node_id, vae_dict=None, ckpt_dict=None, lora_dict=None, refn_dict=None):
|
||||||
global loaded_objects
|
global loaded_objects
|
||||||
|
|
||||||
dict_mapping = {
|
dict_mapping = {
|
||||||
"vae_dict": "vae",
|
"vae_dict": "vae",
|
||||||
"ckpt_dict": "ckpt",
|
"ckpt_dict": "ckpt",
|
||||||
"lora_dict": "lora"
|
"lora_dict": "lora",
|
||||||
|
"refn_dict": "refn"
|
||||||
}
|
}
|
||||||
|
|
||||||
for arg_name, arg_val in {"vae_dict": vae_dict, "ckpt_dict": ckpt_dict, "lora_dict": lora_dict}.items():
|
for arg_name, arg_val in {"vae_dict": vae_dict, "ckpt_dict": ckpt_dict, "lora_dict": lora_dict, "refn_dict": refn_dict}.items():
|
||||||
if arg_val is None:
|
if arg_val is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -401,7 +412,8 @@ def get_cache_numbers(node_name):
|
|||||||
vae_cache = int(model_cache_settings.get('vae', 1))
|
vae_cache = int(model_cache_settings.get('vae', 1))
|
||||||
ckpt_cache = int(model_cache_settings.get('ckpt', 1))
|
ckpt_cache = int(model_cache_settings.get('ckpt', 1))
|
||||||
lora_cache = int(model_cache_settings.get('lora', 1))
|
lora_cache = int(model_cache_settings.get('lora', 1))
|
||||||
return vae_cache, ckpt_cache, lora_cache
|
refn_cache = int(model_cache_settings.get('ckpt', 1))
|
||||||
|
return vae_cache, ckpt_cache, lora_cache, refn_cache,
|
||||||
|
|
||||||
def print_last_helds(id=None):
|
def print_last_helds(id=None):
|
||||||
print("\n" + "-" * 40) # Print an empty line followed by a separator line
|
print("\n" + "-" * 40) # Print an empty line followed by a separator line
|
||||||
@@ -509,19 +521,33 @@ def packages(python_exe=None, versions=False):
|
|||||||
install_packages(my_dir)
|
install_packages(my_dir)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------------------------------------------------
|
||||||
# Auto install efficiency nodes web extension '\js\efficiency_nodes.js' to 'ComfyUI\web\extensions'
|
# Auto install efficiency nodes web extensions '\js\' to 'ComfyUI\web\extensions'
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
# Source and destination paths
|
# Source and destination directories
|
||||||
source_path = os.path.join(my_dir, 'js', 'efficiency_nodes.js')
|
source_dir = os.path.join(my_dir, 'js')
|
||||||
destination_dir = os.path.join(comfy_dir, 'web', 'extensions', 'efficiency-nodes-comfyui')
|
destination_dir = os.path.join(comfy_dir, 'web', 'extensions', 'efficiency-nodes-comfyui')
|
||||||
destination_path = os.path.join(destination_dir, 'efficiency_nodes.js')
|
|
||||||
|
|
||||||
# Create the destination directory if it doesn't exist
|
# Create the destination directory if it doesn't exist
|
||||||
os.makedirs(destination_dir, exist_ok=True)
|
os.makedirs(destination_dir, exist_ok=True)
|
||||||
|
|
||||||
# Copy the file
|
# Get a list of all .js files in the source directory
|
||||||
shutil.copy2(source_path, destination_path)
|
source_files = [f for f in os.listdir(source_dir) if f.endswith('.js')]
|
||||||
|
|
||||||
|
# Clear files in the destination directory that aren't in the source directory
|
||||||
|
for file_name in os.listdir(destination_dir):
|
||||||
|
if file_name not in source_files and file_name.endswith('.js'):
|
||||||
|
file_path = os.path.join(destination_dir, file_name)
|
||||||
|
os.unlink(file_path)
|
||||||
|
|
||||||
|
# Iterate over all files in the source directory for copying
|
||||||
|
for file_name in source_files:
|
||||||
|
# Full paths for source and destination
|
||||||
|
source_path = os.path.join(source_dir, file_name)
|
||||||
|
destination_path = os.path.join(destination_dir, file_name)
|
||||||
|
|
||||||
|
# Directly copy the file (this will overwrite if the file already exists)
|
||||||
|
shutil.copy2(source_path, destination_path)
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------------------------------------------------
|
||||||
# Establish a websocket connection to communicate with "efficiency-nodes.js" under:
|
# Establish a websocket connection to communicate with "efficiency-nodes.js" under:
|
||||||
|
|||||||
BIN
workflows/ControlNet (Overview).png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
workflows/ControlNet.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 701 KiB After Width: | Height: | Size: 630 KiB |
BIN
workflows/SDXL Base+Refine (Overview).png
Normal file
|
After Width: | Height: | Size: 890 KiB |
BIN
workflows/SDXL Base+Refine.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
workflows/Thumbs.db
Normal file
BIN
workflows/XY Plot/LoRA Plot X-ModelStr Y-ClipStr (Overview).png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
workflows/XY Plot/LoRA Plot X-ModelStr Y-ClipStr.png
Normal file
|
After Width: | Height: | Size: 6.9 MiB |
BIN
workflows/XY Plot/Thumbs.db
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
workflows/XY Plot/X-Seeds Y-Checkpoints +HiRes Fix.png
Normal file
|
After Width: | Height: | Size: 6.1 MiB |