Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #204 from LucianoCirino/v2.0
Browse files Browse the repository at this point in the history
Efficiency Nodes V2.0
  • Loading branch information
LucianoCirino authored Oct 21, 2023
2 parents 749c42b + ab73e18 commit 333f984
Show file tree
Hide file tree
Showing 52 changed files with 5,083 additions and 888 deletions.
2 changes: 0 additions & 2 deletions .gitignore

This file was deleted.

264 changes: 168 additions & 96 deletions README.md

Large diffs are not rendered by default.

1,129 changes: 589 additions & 540 deletions efficiency_nodes.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion js/appearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ const NODE_COLORS = {
"Manual XY Entry Info": "cyan",
"Join XY Inputs of Same Type": "cyan",
"Image Overlay": "random",
"Noise Control Script": "none",
"HighRes-Fix Script": "yellow",
"Tiled Sampling Script": "none",
"Tiled Upscaler Script": "red",
"AnimateDiff Script": "random",
"Evaluate Integers": "pale_blue",
"Evaluate Floats": "pale_blue",
"Evaluate Strings": "pale_blue",
Expand Down
144 changes: 144 additions & 0 deletions js/gif_preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { app } from '../../scripts/app.js'
import { api } from '../../scripts/api.js'

function offsetDOMWidget(
widget,
ctx,
node,
widgetWidth,
widgetY,
height
) {
const margin = 10
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(0, widgetY + margin)

const scale = new DOMMatrix().scaleSelf(transform.a, transform.d)
Object.assign(widget.inputEl.style, {
transformOrigin: '0 0',
transform: scale,
left: `${transform.e}px`,
top: `${transform.d + transform.f}px`,
width: `${widgetWidth}px`,
height: `${(height || widget.parent?.inputHeight || 32) - margin}px`,
position: 'absolute',
background: !node.color ? '' : node.color,
color: !node.color ? '' : 'white',
zIndex: 5, //app.graph._nodes.indexOf(node),
})
}

export const hasWidgets = (node) => {
if (!node.widgets || !node.widgets?.[Symbol.iterator]) {
return false
}
return true
}

export const cleanupNode = (node) => {
if (!hasWidgets(node)) {
return
}

for (const w of node.widgets) {
if (w.canvas) {
w.canvas.remove()
}
if (w.inputEl) {
w.inputEl.remove()
}
// calls the widget remove callback
w.onRemoved?.()
}
}

const CreatePreviewElement = (name, val, format) => {
const [type] = format.split('/')
const w = {
name,
type,
value: val,
draw: function (ctx, node, widgetWidth, widgetY, height) {
const [cw, ch] = this.computeSize(widgetWidth)
offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch)
},
computeSize: function (_) {
const ratio = this.inputRatio || 1
const width = Math.max(220, this.parent.size[0])
return [width, (width / ratio + 10)]
},
onRemoved: function () {
if (this.inputEl) {
this.inputEl.remove()
}
},
}

w.inputEl = document.createElement(type === 'video' ? 'video' : 'img')
w.inputEl.src = w.value
if (type === 'video') {
w.inputEl.setAttribute('type', 'video/webm');
w.inputEl.autoplay = true
w.inputEl.loop = true
w.inputEl.controls = false;
}
w.inputEl.onload = function () {
w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight
}
document.body.appendChild(w.inputEl)
return w
}

const gif_preview = {
name: 'efficiency.gif_preview',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
switch (nodeData.name) {
case 'KSampler (Efficient)':{
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const prefix = 'ad_gif_preview_'
const r = onExecuted ? onExecuted.apply(this, message) : undefined

if (this.widgets) {
const pos = this.widgets.findIndex((w) => w.name === `${prefix}_0`)
if (pos !== -1) {
for (let i = pos; i < this.widgets.length; i++) {
this.widgets[i].onRemoved?.()
}
this.widgets.length = pos
}
if (message?.gifs) {
message.gifs.forEach((params, i) => {
const previewUrl = api.apiURL(
'/view?' + new URLSearchParams(params).toString()
)
const w = this.addCustomWidget(
CreatePreviewElement(`${prefix}_${i}`, previewUrl, params.format || 'image/gif')
)
w.parent = this
})
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
cleanupNode(this)
return onRemoved?.()
}
}
if (message?.gifs && message.gifs.length > 0) {
this.setSize([this.size[0], this.computeSize([this.size[0], this.size[1]])[1]]);
}
return r
}
break
}
}
}
}

app.registerExtension(gif_preview)
180 changes: 180 additions & 0 deletions js/node_options/addLinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { app } from "../../../scripts/app.js";
import { addMenuHandler } from "./common/utils.js";
import { addNode } from "./common/utils.js";

function createKSamplerEntry(node, samplerType, subNodeType = null, isSDXL = false) {
const samplerLabelMap = {
"Eff": "KSampler (Efficient)",
"Adv": "KSampler Adv. (Efficient)",
"SDXL": "KSampler SDXL (Eff.)"
};

const subNodeLabelMap = {
"XYPlot": "XY Plot",
"NoiseControl": "Noise Control Script",
"HiResFix": "HighRes-Fix Script",
"TiledUpscale": "Tiled Upscaler Script",
"AnimateDiff": "AnimateDiff Script"
};

const nicknameMap = {
"KSampler (Efficient)": "KSampler",
"KSampler Adv. (Efficient)": "KSampler(Adv)",
"KSampler SDXL (Eff.)": "KSampler",
"XY Plot": "XY Plot",
"Noise Control Script": "NoiseControl",
"HighRes-Fix Script": "HiResFix",
"Tiled Upscaler Script": "TiledUpscale",
"AnimateDiff Script": "AnimateDiff"
};

const kSamplerLabel = samplerLabelMap[samplerType];
const subNodeLabel = subNodeLabelMap[subNodeType];

const kSamplerNickname = nicknameMap[kSamplerLabel];
const subNodeNickname = nicknameMap[subNodeLabel];

const contentLabel = subNodeNickname ? `${kSamplerNickname} + ${subNodeNickname}` : kSamplerNickname;

return {
content: contentLabel,
callback: function() {
const kSamplerNode = addNode(kSamplerLabel, node, { shiftX: node.size[0] + 50 });

// Standard connections for all samplers
node.connect(0, kSamplerNode, 0); // MODEL
node.connect(1, kSamplerNode, 1); // CONDITIONING+
node.connect(2, kSamplerNode, 2); // CONDITIONING-

// Additional connections for non-SDXL
if (!isSDXL) {
node.connect(3, kSamplerNode, 3); // LATENT
node.connect(4, kSamplerNode, 4); // VAE
}

if (subNodeLabel) {
const subNode = addNode(subNodeLabel, node, { shiftX: 50, shiftY: node.size[1] + 50 });
const dependencyIndex = isSDXL ? 3 : 5;
node.connect(dependencyIndex, subNode, 0);
subNode.connect(0, kSamplerNode, dependencyIndex);
}
},
};
}

function createStackerNode(node, type) {
const stackerLabelMap = {
"LoRA": "LoRA Stacker",
"ControlNet": "Control Net Stacker"
};

const contentLabel = stackerLabelMap[type];

return {
content: contentLabel,
callback: function() {
const stackerNode = addNode(contentLabel, node);

// Calculate the left shift based on the width of the new node
const shiftX = -(stackerNode.size[0] + 25);

stackerNode.pos[0] += shiftX; // Adjust the x position of the new node

// Introduce a Y offset of 200 for ControlNet Stacker node
if (type === "ControlNet") {
stackerNode.pos[1] += 300;
}

// Connect outputs to the Efficient Loader based on type
if (type === "LoRA") {
stackerNode.connect(0, node, 0);
} else if (type === "ControlNet") {
stackerNode.connect(0, node, 1);
}
},
};
}

function createXYPlotNode(node, type) {
const contentLabel = "XY Plot";

return {
content: contentLabel,
callback: function() {
const xyPlotNode = addNode(contentLabel, node);

// Center the X coordinate of the XY Plot node
const centerXShift = (node.size[0] - xyPlotNode.size[0]) / 2;
xyPlotNode.pos[0] += centerXShift;

// Adjust the Y position to place it below the loader node
xyPlotNode.pos[1] += node.size[1] + 60;

// Depending on the node type, connect the appropriate output to the XY Plot node
if (type === "Efficient") {
node.connect(6, xyPlotNode, 0);
} else if (type === "SDXL") {
node.connect(3, xyPlotNode, 0);
}
},
};
}

function getMenuValues(type, node) {
const subNodeTypes = [null, "XYPlot", "NoiseControl", "HiResFix", "TiledUpscale", "AnimateDiff"];
const excludedSubNodeTypes = ["NoiseControl", "HiResFix", "TiledUpscale", "AnimateDiff"]; // Nodes to exclude from the menu

const menuValues = [];

// Add the new node types to the menu first for the correct order
menuValues.push(createStackerNode(node, "LoRA"));
menuValues.push(createStackerNode(node, "ControlNet"));

for (const subNodeType of subNodeTypes) {
// Skip adding submenu items that are in the excludedSubNodeTypes array
if (!excludedSubNodeTypes.includes(subNodeType)) {
const menuEntry = createKSamplerEntry(node, type === "Efficient" ? "Eff" : "SDXL", subNodeType, type === "SDXL");
menuValues.push(menuEntry);
}
}

// Insert the standalone XY Plot option after the KSampler without any subNodeTypes and before any other KSamplers with subNodeTypes
menuValues.splice(3, 0, createXYPlotNode(node, type));

return menuValues;
}

function showAddLinkMenuCommon(value, options, e, menu, node, type) {
const values = getMenuValues(type, node);
new LiteGraph.ContextMenu(values, {
event: e,
callback: null,
parentMenu: menu,
node: node
});
return false;
}

// Extension Definition
app.registerExtension({
name: "efficiency.addLinks",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
const linkTypes = {
"Efficient Loader": "Efficient",
"Eff. Loader SDXL": "SDXL"
};

const linkType = linkTypes[nodeData.name];

if (linkType) {
addMenuHandler(nodeType, function(insertOption) {
insertOption({
content: "⛓ Add link...",
has_submenu: true,
callback: (value, options, e, menu, node) => showAddLinkMenuCommon(value, options, e, menu, node, linkType)
});
});
}
},
});

Loading

0 comments on commit 333f984

Please sign in to comment.