This repository has been archived by the owner on Jan 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #204 from LucianoCirino/v2.0
Efficiency Nodes V2.0
- Loading branch information
Showing
52 changed files
with
5,083 additions
and
888 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}); | ||
}); | ||
} | ||
}, | ||
}); | ||
|
Oops, something went wrong.