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.
This commit is contained in:
TSC
2023-08-30 20:06:15 -05:00
committed by GitHub
parent 26c81dba51
commit 92ff8fd8c3
21 changed files with 3573 additions and 1459 deletions

File diff suppressed because it is too large Load Diff

95
js/appearance.js Normal file
View 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
View 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
View 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
View 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);
}
});
}
}
});

View File

@@ -1,16 +1,18 @@
{
"Efficient Loader": {
"model_cache": {
"ckpt": 1,
"vae": 1,
"lora": 1
"ckpt": 1,
"lora": 1,
"refn": 1
}
},
"XY Plot": {
"model_cache": {
"ckpt": 5,
"vae": 5,
"lora": 5
"ckpt": 5,
"lora": 5,
"refn": 1
}
}
}

View File

@@ -31,12 +31,13 @@ from comfy.cli_args import args
# Cache for Efficiency Node models
loaded_objects = {
"ckpt": [], # (ckpt_name, ckpt_model, clip, bvae, [id])
"refn": [], # (ckpt_name, ckpt_model, clip, bvae, [id])
"vae": [], # (vae_name, vae, [id])
"lora": [] # ([(lora_name, strength_model, strength_clip)], ckpt_name, lora_model, clip_lora, [id])
}
# 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
"latent": [], # (latent, id) # Latent outputs, stored as a latent 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:
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):
# Convert ID to string
id = str(id)
@@ -95,7 +119,7 @@ def print_loaded_objects_entries(id=None, prompt=None, show_id=False):
else:
print(f"\033[36mModels Cache: \nnode_id:{int(id)}\033[0m")
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]]
if not entries_with_id: # If no entries with the chosen ID, print None and skip this key
continue
@@ -103,13 +127,15 @@ def print_loaded_objects_entries(id=None, prompt=None, show_id=False):
print(f"{key.capitalize()}:")
for i, entry in enumerate(entries_with_id, 1): # Start numbering from 1
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
if id is None:
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:
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:
name_without_ext = os.path.splitext(os.path.basename(entry[0]))[0]
if id is None:
@@ -146,59 +172,54 @@ def globals_cleanup(prompt):
loaded_objects[key].remove(tup)
###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):
"""
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.
"""
def load_checkpoint(ckpt_name, id, output_vae=True, cache=None, cache_overwrite=False, ckpt_type="ckpt"):
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:
_, 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:
clear_cache(id, cache, "ckpt")
clear_cache(id, cache, ckpt_type)
elif id not in ids:
ids.append(id)
return model, clip, vae
if os.path.isabs(ckpt_name):
ckpt_path = ckpt_name
else:
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
with suppress_output():
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae, output_clip=True,
embedding_directory=folder_paths.get_folder_paths("embeddings"))
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
model = out[0]
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 len([entry for entry in loaded_objects["ckpt"] if id in entry[-1]]) < cache:
loaded_objects["ckpt"].append((ckpt_name, model, clip, vae, [id]))
cache_list = [entry for entry in loaded_objects[ckpt_type] if id in entry[-1]]
if len(cache_list) < cache:
loaded_objects[ckpt_type].append((ckpt_name, model, clip, vae, [id]))
else:
clear_cache(id, cache, "ckpt")
clear_cache(id, cache, ckpt_type)
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"]:
for e in loaded_objects[ckpt_type]:
if id in e[-1]:
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]:
loaded_objects["ckpt"].remove(e)
loaded_objects[ckpt_type].remove(e)
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
@@ -209,21 +230,11 @@ def get_bvae_by_ckpt_name(ckpt_name):
return None # return None if no match is found
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
# 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"]):
if entry[0] == vae_name:
vae, ids = entry[1], entry[2]
@@ -235,6 +246,9 @@ def load_vae(vae_name, id, cache=None, cache_overwrite=False):
clear_cache(id, cache, "vae")
return vae
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)
@@ -257,23 +271,14 @@ def load_vae(vae_name, id, cache=None, cache_overwrite=False):
return vae
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
# 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"]:
# Convert to sets and compare
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
lora_name, strength_model, strength_clip = lora_params[0]
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)
# 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):
"""
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 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
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
dict_mapping = {
"vae_dict": "vae",
"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:
continue
@@ -401,7 +412,8 @@ def get_cache_numbers(node_name):
vae_cache = int(model_cache_settings.get('vae', 1))
ckpt_cache = int(model_cache_settings.get('ckpt', 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):
print("\n" + "-" * 40) # Print an empty line followed by a separator line
@@ -509,18 +521,32 @@ def packages(python_exe=None, versions=False):
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
# Source and destination paths
source_path = os.path.join(my_dir, 'js', 'efficiency_nodes.js')
# Source and destination directories
source_dir = os.path.join(my_dir, 'js')
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
os.makedirs(destination_dir, exist_ok=True)
# Copy the file
# Get a list of all .js files in the source directory
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)
#-----------------------------------------------------------------------------------------------------------------------

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
workflows/ControlNet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 KiB

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
workflows/Thumbs.db Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

BIN
workflows/XY Plot/Thumbs.db Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB