|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { app } from '../../scripts/app.js' |
|
import { api } from '../../scripts/api.js' |
|
|
|
import * as mtb_ui from './mtb_ui.js' |
|
import parseCss from './extern/parse-css.js' |
|
import * as shared from './comfy_shared.js' |
|
import { infoLogger } from './comfy_shared.js' |
|
import { NumberInputWidget } from './numberInput.js' |
|
|
|
|
|
const newTypes = [ , 'COLOR', 'BBOX'] |
|
|
|
const deprecated_nodes = { |
|
|
|
|
|
} |
|
|
|
const withFont = (ctx, font, cb) => { |
|
const oldFont = ctx.font |
|
ctx.font = font |
|
cb() |
|
ctx.font = oldFont |
|
} |
|
|
|
const calculateTextDimensions = (ctx, value, width, fontSize = 16) => { |
|
const words = value.split(' ') |
|
const lines = [] |
|
let currentLine = '' |
|
for (const word of words) { |
|
const testLine = currentLine.length === 0 ? word : `${currentLine} ${word}` |
|
const testWidth = ctx.measureText(testLine).width |
|
if (testWidth > width) { |
|
lines.push(currentLine) |
|
currentLine = word |
|
} else { |
|
currentLine = testLine |
|
} |
|
} |
|
if (lines.length === 0) lines.push(value) |
|
const textHeight = (lines.length + 1) * fontSize |
|
const maxLineWidth = lines.reduce( |
|
(maxWidth, line) => Math.max(maxWidth, ctx.measureText(line).width), |
|
0, |
|
) |
|
return { textHeight, maxLineWidth } |
|
} |
|
|
|
export function addMultilineWidget(node, name, opts, callback) { |
|
const inputEl = document.createElement('textarea') |
|
inputEl.className = 'comfy-multiline-input' |
|
inputEl.value = opts.defaultVal |
|
inputEl.placeholder = opts.placeholder || name |
|
|
|
const widget = node.addDOMWidget(name, 'textmultiline', inputEl, { |
|
getValue() { |
|
return inputEl.value |
|
}, |
|
setValue(v) { |
|
inputEl.value = v |
|
}, |
|
}) |
|
widget.inputEl = inputEl |
|
|
|
inputEl.addEventListener('input', () => { |
|
callback?.(widget.value) |
|
widget.callback?.(widget.value) |
|
}) |
|
widget.onRemove = () => { |
|
inputEl.remove() |
|
} |
|
|
|
return { minWidth: 400, minHeight: 200, widget } |
|
} |
|
|
|
export const VECTOR_AXIS = { |
|
0: 'x', |
|
1: 'y', |
|
2: 'z', |
|
3: 'w', |
|
} |
|
|
|
export function addVectorWidgetW( |
|
node, |
|
name, |
|
value, |
|
vector_size, |
|
_callback, |
|
app, |
|
) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const inputs = [] |
|
|
|
for (let i = 0; i < vector_size; i++) { |
|
|
|
|
|
|
|
const input = node.addWidget( |
|
'number', |
|
`${name}_${VECTOR_AXIS[i]}`, |
|
value[VECTOR_AXIS[i]], |
|
(val) => {}, |
|
) |
|
|
|
inputs.push(input) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { minWidth: 400, minHeight: 200, widget } |
|
} |
|
export function addVectorWidget(node, name, value, vector_size, callback, app) { |
|
const inputEl = document.createElement('div') |
|
const vecEl = document.createElement('div') |
|
|
|
inputEl.className = 'comfy-vector-container' |
|
vecEl.className = 'comfy-vector-input' |
|
vecEl.id = 'vecEl' |
|
|
|
vecEl.style.display = 'flex' |
|
vecEl.style.flexDirection = 'column' |
|
inputEl.appendChild(vecEl) |
|
const inputs = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const widget = node.addDOMWidget(name, 'vector', inputEl, { |
|
getValue() { |
|
return JSON.stringify(widget._value) |
|
}, |
|
setValue(v) { |
|
widget._value = v |
|
}, |
|
}) |
|
const vec = new NumberInputWidget('vecEl', vector_size, true) |
|
vec.setValue(...Object.values(value)) |
|
vec.onChange = (value) => { |
|
for (let i = 0; i < value.length; i++) { |
|
const val = value[i] |
|
widget._value[VECTOR_AXIS[i]] = Number.parseFloat(val) |
|
} |
|
|
|
widget.callback?.(widget._value) |
|
|
|
} |
|
|
|
console.log('prev callback', widget.callback) |
|
widget.callback = callback |
|
widget._value = value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
widget.inputEl = inputEl |
|
widget.vecEl = vecEl |
|
widget.vec = vec |
|
|
|
return { minWidth: 400, minHeight: 200 * vector_size, widget } |
|
} |
|
export const MtbWidgets = { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VECTOR: (key, val, size) => { |
|
shared.infoLogger('Adding VECTOR widget', { key, val, size }) |
|
|
|
const widget = { |
|
name: key, |
|
type: `vector${size}`, |
|
y: 0, |
|
options: { default: Array.from({ length: size }, () => 0.0) }, |
|
_value: val || Array.from({ length: size }, () => 0.0), |
|
draw: (ctx, node, width, widgetY, height) => { |
|
ctx.textAlign = 'left' |
|
ctx.strokeStyle = outline_color |
|
ctx.fillStyle = background_color |
|
ctx.beginPath() |
|
if (show_text) |
|
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) |
|
else ctx.rect(margin, y, widget_width - margin * 2, H) |
|
ctx.fill() |
|
if (show_text) { |
|
if (!w.disabled) ctx.stroke() |
|
ctx.fillStyle = text_color |
|
if (!w.disabled) { |
|
ctx.beginPath() |
|
ctx.moveTo(margin + 16, y + 5) |
|
ctx.lineTo(margin + 6, y + H * 0.5) |
|
ctx.lineTo(margin + 16, y + H - 5) |
|
ctx.fill() |
|
ctx.beginPath() |
|
ctx.moveTo(widget_width - margin - 16, y + 5) |
|
ctx.lineTo(widget_width - margin - 6, y + H * 0.5) |
|
ctx.lineTo(widget_width - margin - 16, y + H - 5) |
|
ctx.fill() |
|
} |
|
ctx.fillStyle = secondary_text_color |
|
ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7) |
|
ctx.fillStyle = text_color |
|
ctx.textAlign = 'right' |
|
if (w.type === 'number') { |
|
ctx.fillText( |
|
Number(w.value).toFixed( |
|
w.options.precision !== undefined ? w.options.precision : 3, |
|
), |
|
widget_width - margin * 2 - 20, |
|
y + H * 0.7, |
|
) |
|
} else { |
|
let v = w.value |
|
if (w.options.values) { |
|
let values = w.options.values |
|
if (values.constructor === Function) values = values() |
|
if (values && values.constructor !== Array) v = values[w.value] |
|
} |
|
ctx.fillText(v, widget_width - margin * 2 - 20, y + H * 0.7) |
|
} |
|
} |
|
}, |
|
get value() { |
|
return this._value |
|
}, |
|
set value(val) { |
|
this._value = val |
|
this.callback?.(this._value) |
|
}, |
|
} |
|
|
|
return widget |
|
}, |
|
BBOX: (key, val) => { |
|
|
|
const widget = { |
|
name: key, |
|
type: 'BBOX', |
|
|
|
y: 0, |
|
value: val?.default || [0, 0, 0, 0], |
|
options: {}, |
|
|
|
draw: function (ctx, _node, widget_width, widgetY, _height) { |
|
const hide = this.type !== 'BBOX' && app.canvas.ds.scale > 0.5 |
|
|
|
const show_text = true |
|
const outline_color = LiteGraph.WIDGET_OUTLINE_COLOR |
|
const background_color = LiteGraph.WIDGET_BGCOLOR |
|
const text_color = LiteGraph.WIDGET_TEXT_COLOR |
|
const secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR |
|
const H = LiteGraph.NODE_WIDGET_HEIGHT |
|
|
|
const margin = 15 |
|
const numWidgets = 4 |
|
|
|
if (hide) return |
|
|
|
for (let i = 0; i < numWidgets; i++) { |
|
const currentY = widgetY + i * (H + margin) |
|
|
|
ctx.textAlign = 'left' |
|
ctx.strokeStyle = outline_color |
|
ctx.fillStyle = background_color |
|
ctx.beginPath() |
|
if (show_text) |
|
ctx.roundRect(margin, currentY, widget_width - margin * 2, H, [ |
|
H * 0.5, |
|
]) |
|
else ctx.rect(margin, currentY, widget_width - margin * 2, H) |
|
ctx.fill() |
|
if (show_text) { |
|
if (!this.disabled) ctx.stroke() |
|
ctx.fillStyle = text_color |
|
if (!this.disabled) { |
|
ctx.beginPath() |
|
ctx.moveTo(margin + 16, currentY + 5) |
|
ctx.lineTo(margin + 6, currentY + H * 0.5) |
|
ctx.lineTo(margin + 16, currentY + H - 5) |
|
ctx.fill() |
|
ctx.beginPath() |
|
ctx.moveTo(widget_width - margin - 16, currentY + 5) |
|
ctx.lineTo(widget_width - margin - 6, currentY + H * 0.5) |
|
ctx.lineTo(widget_width - margin - 16, currentY + H - 5) |
|
ctx.fill() |
|
} |
|
ctx.fillStyle = secondary_text_color |
|
ctx.fillText( |
|
this.label || this.name, |
|
margin * 2 + 5, |
|
currentY + H * 0.7, |
|
) |
|
ctx.fillStyle = text_color |
|
ctx.textAlign = 'right' |
|
|
|
ctx.fillText( |
|
Number(this.value).toFixed( |
|
this.options?.precision !== undefined |
|
? this.options.precision |
|
: 3, |
|
), |
|
widget_width - margin * 2 - 20, |
|
currentY + H * 0.7, |
|
) |
|
} |
|
} |
|
}, |
|
mouse: function (event, pos, node) { |
|
let old_value = this.value |
|
let x = pos[0] - node.pos[0] |
|
let y = pos[1] - node.pos[1] |
|
let width = node.size[0] |
|
let H = LiteGraph.NODE_WIDGET_HEIGHT |
|
let margin = 5 |
|
let numWidgets = 4 |
|
|
|
for (let i = 0; i < numWidgets; i++) { |
|
let currentY = y + i * (H + margin) |
|
|
|
if ( |
|
event.type == LiteGraph.pointerevents_method + 'move' && |
|
this.type == 'BBOX' |
|
) { |
|
if (event.deltaX) |
|
this.value += event.deltaX * 0.1 * (this.options?.step || 1) |
|
if (this.options.min != null && this.value < this.options.min) { |
|
this.value = this.options.min |
|
} |
|
if (this.options.max != null && this.value > this.options.max) { |
|
this.value = this.options.max |
|
} |
|
} else if (event.type == LiteGraph.pointerevents_method + 'down') { |
|
let values = this.options?.values |
|
if (values && values.constructor === Function) { |
|
values = this.options.values(w, node) |
|
} |
|
let values_list = null |
|
|
|
let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 |
|
if (this.type == 'BBOX') { |
|
this.value += delta * 0.1 * (this.options.step || 1) |
|
if (this.options.min != null && this.value < this.options.min) { |
|
this.value = this.options.min |
|
} |
|
if (this.options.max != null && this.value > this.options.max) { |
|
this.value = this.options.max |
|
} |
|
} else if (delta) { |
|
|
|
let index = -1 |
|
this.last_mouseclick = 0 |
|
if (values.constructor === Object) |
|
index = values_list.indexOf(String(this.value)) + delta |
|
else index = values_list.indexOf(this.value) + delta |
|
if (index >= values_list.length) { |
|
index = values_list.length - 1 |
|
} |
|
if (index < 0) { |
|
index = 0 |
|
} |
|
if (values.constructor === Array) this.value = values[index] |
|
else this.value = index |
|
} |
|
} |
|
else if ( |
|
event.type == LiteGraph.pointerevents_method + 'up' && |
|
this.type == 'BBOX' |
|
) { |
|
let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 |
|
if (event.click_time < 200 && delta == 0) { |
|
this.prompt( |
|
'Value', |
|
this.value, |
|
function (v) { |
|
|
|
if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { |
|
try { |
|
|
|
v = eval(v) |
|
} catch (e) {} |
|
} |
|
this.value = Number(v) |
|
shared.inner_value_change(this, this.value, event) |
|
}.bind(w), |
|
event, |
|
) |
|
} |
|
} |
|
|
|
if (old_value != this.value) |
|
setTimeout( |
|
function () { |
|
shared.inner_value_change(this, this.value, event) |
|
}.bind(this), |
|
20, |
|
) |
|
|
|
app.canvas.setDirty(true) |
|
} |
|
}, |
|
computeSize: function (width) { |
|
return [width, LiteGraph.NODE_WIDGET_HEIGHT * 4] |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
widget.desc = 'Represents a Bounding Box with x, y, width, and height.' |
|
return widget |
|
}, |
|
|
|
COLOR: (key, val, compute = false) => { |
|
|
|
const widget = {} |
|
widget.y = 0 |
|
widget.name = key |
|
widget.type = 'COLOR' |
|
widget.options = { default: '#ff0000' } |
|
widget.value = val || '#ff0000' |
|
widget.draw = function (ctx, node, widgetWidth, widgetY, height) { |
|
const hide = this.type !== 'COLOR' && app.canvas.ds.scale > 0.5 |
|
if (hide) { |
|
return |
|
} |
|
const border = 3 |
|
ctx.fillStyle = '#000' |
|
ctx.fillRect(0, widgetY, widgetWidth, height) |
|
ctx.fillStyle = this.value |
|
ctx.fillRect( |
|
border, |
|
widgetY + border, |
|
widgetWidth - border * 2, |
|
height - border * 2, |
|
) |
|
const color = parseCss(this.value.default || this.value) |
|
if (!color) { |
|
return |
|
} |
|
ctx.fillStyle = shared.isColorBright(color.values, 125) ? '#000' : '#fff' |
|
|
|
ctx.font = '14px Arial' |
|
ctx.textAlign = 'center' |
|
ctx.fillText(this.name, widgetWidth * 0.5, widgetY + 14) |
|
} |
|
widget.mouse = function (e, pos, node) { |
|
if (e.type === 'pointerdown') { |
|
const widgets = node.widgets.filter((w) => w.type === 'COLOR') |
|
|
|
for (const w of widgets) { |
|
|
|
const rect = [w.last_y, w.last_y + 32] |
|
if (pos[1] > rect[0] && pos[1] < rect[1]) { |
|
const picker = document.createElement('input') |
|
picker.type = 'color' |
|
picker.value = this.value |
|
|
|
picker.style.position = 'absolute' |
|
picker.style.left = '999999px' |
|
picker.style.top = '999999px' |
|
|
|
document.body.appendChild(picker) |
|
|
|
picker.addEventListener('change', () => { |
|
this.value = picker.value |
|
this.callback?.(this.value) |
|
node.graph._version++ |
|
node.setDirtyCanvas(true, true) |
|
picker.remove() |
|
}) |
|
|
|
picker.click() |
|
} |
|
} |
|
} |
|
} |
|
widget.computeSize = function (width) { |
|
return [width, 32] |
|
} |
|
|
|
return widget |
|
}, |
|
|
|
DEBUG_IMG: (name, val) => { |
|
const w = { |
|
name, |
|
type: 'image', |
|
value: val, |
|
draw: function (ctx, node, widgetWidth, widgetY, height) { |
|
const [cw, ch] = this.computeSize(widgetWidth) |
|
shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch) |
|
}, |
|
computeSize: function (width) { |
|
const ratio = this.inputRatio || 1 |
|
if (width) { |
|
return [width, width / ratio + 4] |
|
} |
|
return [128, 128] |
|
}, |
|
onRemoved: function () { |
|
if (this.inputEl) { |
|
this.inputEl.remove() |
|
} |
|
}, |
|
} |
|
|
|
w.inputEl = document.createElement('img') |
|
w.inputEl.src = w.value |
|
w.inputEl.onload = function () { |
|
w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight |
|
} |
|
document.body.appendChild(w.inputEl) |
|
return w |
|
}, |
|
DEBUG_STRING: (name, val) => { |
|
const fontSize = 16 |
|
const w = { |
|
name, |
|
type: 'debug_text', |
|
|
|
draw: function (ctx, node, widgetWidth, widgetY, height) { |
|
|
|
shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, height) |
|
}, |
|
computeSize(width) { |
|
if (!this.value) { |
|
return [32, 32] |
|
} |
|
if (!width) { |
|
console.debug(`No width ${this.parent.size}`) |
|
} |
|
let dimensions |
|
withFont(app.ctx, `${fontSize}px monospace`, () => { |
|
dimensions = calculateTextDimensions(app.ctx, this.value, width) |
|
}) |
|
const widgetWidth = Math.max( |
|
width || this.width || 32, |
|
dimensions.maxLineWidth, |
|
) |
|
const widgetHeight = dimensions.textHeight * 1.5 |
|
return [widgetWidth, widgetHeight] |
|
}, |
|
onRemoved: function () { |
|
if (this.inputEl) { |
|
this.inputEl.remove() |
|
} |
|
}, |
|
get value() { |
|
return this.inputEl.innerHTML |
|
}, |
|
set value(val) { |
|
this.inputEl.innerHTML = val |
|
this.parent?.setSize?.(this.parent?.computeSize()) |
|
}, |
|
} |
|
|
|
w.inputEl = document.createElement('p') |
|
w.inputEl.style = ` |
|
text-align: center; |
|
font-size: ${fontSize}px; |
|
color: var(--input-text); |
|
line-height: 1em; |
|
font-family: monospace; |
|
` |
|
w.value = val |
|
document.body.appendChild(w.inputEl) |
|
|
|
return w |
|
}, |
|
} |
|
|
|
|
|
|
|
|
|
const mtb_widgets = { |
|
name: 'mtb.widgets', |
|
|
|
init: async () => { |
|
infoLogger('Registering mtb.widgets') |
|
try { |
|
const res = await api.fetchApi('/mtb/debug') |
|
const msg = await res.json() |
|
if (!window.MTB) { |
|
window.MTB = {} |
|
} |
|
window.MTB.DEBUG = msg.enabled |
|
} catch (e) { |
|
console.error('Error:', e) |
|
} |
|
}, |
|
|
|
setup: () => { |
|
app.ui.settings.addSetting({ |
|
id: 'mtb.Main.debug-enabled', |
|
category: ['mtb', 'Main', 'debug-enabled'], |
|
name: 'Enable Debug (py and js)', |
|
type: 'boolean', |
|
defaultValue: false, |
|
|
|
tooltip: |
|
'This will enable debug messages in the console and in the python console respectively, no need to restart the server, but do reload the webui', |
|
attrs: { |
|
style: { |
|
|
|
}, |
|
}, |
|
async onChange(value) { |
|
if (!window.MTB) { |
|
window.MTB = {} |
|
} |
|
window.MTB.DEBUG = value |
|
if (value) { |
|
infoLogger('Enabled DEBUG mode') |
|
} |
|
|
|
await api |
|
.fetchApi('/mtb/debug', { |
|
method: 'POST', |
|
body: JSON.stringify({ |
|
enabled: value, |
|
}), |
|
}) |
|
.then((_response) => {}) |
|
.catch((error) => { |
|
console.error('Error:', error) |
|
}) |
|
}, |
|
}) |
|
}, |
|
|
|
getCustomWidgets: () => { |
|
return { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COLOR: (node, inputName, inputData, _app) => { |
|
console.debug('Registering color') |
|
return { |
|
widget: node.addCustomWidget( |
|
MtbWidgets.COLOR(inputName, inputData[1]?.default || '#ff0000'), |
|
), |
|
minWidth: 150, |
|
minHeight: 30, |
|
} |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
}, |
|
|
|
|
|
|
|
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) { |
|
|
|
|
|
let has_custom = false |
|
if (nodeData.input?.required) { |
|
for (const i of Object.keys(nodeData.input.required)) { |
|
const input_type = nodeData.input.required[i][0] |
|
|
|
if (newTypes.includes(input_type)) { |
|
has_custom = true |
|
break |
|
} |
|
} |
|
} |
|
if (has_custom) { |
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function (...args) { |
|
const r = onNodeCreated ? onNodeCreated.apply(this, args) : undefined |
|
this.serialize_widgets = true |
|
this.setSize?.(this.computeSize()) |
|
|
|
this.onRemoved = function () { |
|
|
|
shared.cleanupNode(this) |
|
} |
|
return r |
|
} |
|
|
|
|
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions |
|
nodeType.prototype.getExtraMenuOptions = function (_, options) { |
|
const r = origGetExtraMenuOptions |
|
? origGetExtraMenuOptions.apply(this, arguments) |
|
: undefined |
|
if (this.widgets) { |
|
const toInput = [] |
|
const toWidget = [] |
|
for (const w of this.widgets) { |
|
if (w.type === shared.CONVERTED_TYPE) { |
|
|
|
|
|
|
|
|
|
|
|
} else if (newTypes.includes(w.type)) { |
|
const config = nodeData?.input?.required[w.name] || |
|
nodeData?.input?.optional?.[w.name] || [w.type, w.options || {}] |
|
|
|
toInput.push({ |
|
content: `Convert ${w.name} to input`, |
|
callback: () => shared.convertToInput(this, w, config), |
|
}) |
|
} |
|
} |
|
if (toInput.length) { |
|
options.push(...toInput, null) |
|
} |
|
|
|
if (toWidget.length) { |
|
options.push(...toWidget, null) |
|
} |
|
} |
|
|
|
return r |
|
} |
|
} |
|
|
|
if (!nodeData.name.endsWith('(mtb)')) { |
|
return |
|
} |
|
|
|
|
|
shared.addDocumentation(nodeData, nodeType) |
|
|
|
const deprecation = deprecated_nodes[nodeData.name.replace(' (mtb)', '')] |
|
|
|
if (deprecation) { |
|
shared.addDeprecation(nodeType, deprecation) |
|
} |
|
|
|
switch (nodeData.name) { |
|
|
|
case 'Get Batch From History (mtb)': |
|
case 'Get Batch From History V2 (mtb)': { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function () { |
|
const r = onNodeCreated ? onNodeCreated.apply(this, []) : undefined |
|
const internal_count = this.widgets.find( |
|
(w) => w.name === 'internal_count', |
|
) |
|
shared.hideWidgetForGood(this, internal_count) |
|
internal_count.afterQueued = function () { |
|
this.value++ |
|
} |
|
|
|
return r |
|
} |
|
|
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
const r = onExecuted ? onExecuted.apply(this, message) : undefined |
|
return r |
|
} |
|
|
|
break |
|
} |
|
case 'Save Gif (mtb)': |
|
case 'Save Animated Image (mtb)': { |
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
const prefix = 'anything_' |
|
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 |
|
} |
|
|
|
let imgURLs = [] |
|
if (message) { |
|
if (message.gif) { |
|
imgURLs = imgURLs.concat( |
|
message.gif.map((params) => { |
|
return api.apiURL( |
|
`/view?${new URLSearchParams(params).toString()}`, |
|
) |
|
}), |
|
) |
|
} |
|
if (message.apng) { |
|
imgURLs = imgURLs.concat( |
|
message.apng.map((params) => { |
|
return api.apiURL( |
|
`/view?${new URLSearchParams(params).toString()}`, |
|
) |
|
}), |
|
) |
|
} |
|
let i = 0 |
|
for (const img of imgURLs) { |
|
const w = this.addCustomWidget( |
|
MtbWidgets.DEBUG_IMG(`${prefix}_${i}`, img), |
|
) |
|
w.parent = this |
|
i++ |
|
} |
|
} |
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
shared.cleanupNode(this) |
|
return onRemoved?.() |
|
} |
|
} |
|
this.setSize?.(this.computeSize()) |
|
return r |
|
} |
|
|
|
break |
|
} |
|
case 'Animation Builder (mtb)': { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function (...args) { |
|
const r = onNodeCreated ? onNodeCreated.apply(this, args) : undefined |
|
|
|
this.changeMode(LiteGraph.ALWAYS) |
|
const { raw_iteration, raw_loop, total_frames, loop_count } = |
|
shared.getNamedWidget( |
|
this, |
|
'raw_iteration', |
|
'raw_loop', |
|
'total_frames', |
|
'loop_count', |
|
) |
|
|
|
shared.hideWidgetForGood(this, raw_iteration) |
|
shared.hideWidgetForGood(this, raw_loop) |
|
|
|
raw_iteration._value = 0 |
|
|
|
|
|
|
|
|
|
|
|
const dom_value_preview = mtb_ui.makeElement('p', { |
|
fontWeigth: '700', |
|
textAlign: 'center', |
|
fontSize: '1.5em', |
|
margin: 0, |
|
}) |
|
const value_preview = this.addDOMWidget( |
|
'value_preview', |
|
'DISPLAY', |
|
dom_value_preview, |
|
{ |
|
hideOnZoom: false, |
|
setValue: (val) => { |
|
if (val) { |
|
value_preview.element.innerHTML = val |
|
} |
|
}, |
|
}, |
|
) |
|
value_preview.value = 'Idle' |
|
|
|
const dom_loop_preview = mtb_ui.makeElement('p', { |
|
textAlign: 'center', |
|
margin: 0, |
|
}) |
|
|
|
const loop_preview = this.addDOMWidget( |
|
'loop_preview', |
|
'DISPLAY', |
|
dom_loop_preview, |
|
{ |
|
hideOnZoom: false, |
|
setValue: (val) => { |
|
if (val) { |
|
dom_loop_preview.innerHTML = val |
|
} |
|
}, |
|
getValue: () => { |
|
dom_loop_preview.innerHTML |
|
}, |
|
}, |
|
) |
|
loop_preview.value = 'Iteration: Idle' |
|
|
|
const onReset = () => { |
|
raw_iteration.value = 0 |
|
raw_loop.value = 0 |
|
|
|
value_preview.value = 'Idle' |
|
loop_preview.value = 'Iteration: Idle' |
|
|
|
app.canvas.setDirty(true) |
|
} |
|
|
|
|
|
this.addWidget('button', 'Reset', 'reset', onReset) |
|
|
|
|
|
this.addWidget('button', 'Queue', 'queue', () => { |
|
onReset() |
|
app.queuePrompt(0, total_frames.value * loop_count.value) |
|
window.MTB?.notify?.( |
|
`Started a queue of ${total_frames.value} frames (for ${ |
|
loop_count.value |
|
} loop, so ${total_frames.value * loop_count.value})`, |
|
5000, |
|
) |
|
}) |
|
|
|
this.onRemoved = () => { |
|
shared.cleanupNode(this) |
|
app.canvas.setDirty(true) |
|
} |
|
|
|
raw_iteration.afterQueued = function () { |
|
this.value++ |
|
raw_loop.value = Math.floor(this.value / total_frames.value) |
|
|
|
value_preview.value = `frame: ${ |
|
raw_iteration.value % total_frames.value |
|
} / ${total_frames.value - 1}` |
|
|
|
if (raw_loop.value + 1 > loop_count.value) { |
|
loop_preview.value = 'Done 😎!' |
|
} else { |
|
loop_preview.value = `current loop: ${raw_loop.value + 1}/${ |
|
loop_count.value |
|
}` |
|
} |
|
} |
|
|
|
return r |
|
} |
|
|
|
break |
|
} |
|
case 'Interpolate Clip Sequential (mtb)': { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function (...args) { |
|
const r = onNodeCreated |
|
? onNodeCreated.apply(this, ...args) |
|
: undefined |
|
const addReplacement = () => { |
|
const input = this.addInput( |
|
`replacement_${this.widgets.length}`, |
|
'STRING', |
|
'', |
|
) |
|
console.log(input) |
|
this.addWidget('STRING', `replacement_${this.widgets.length}`, '') |
|
} |
|
|
|
this.addWidget('button', '+', 'add', (value, widget, node) => { |
|
console.log('Button clicked', value, widget, node) |
|
addReplacement() |
|
}) |
|
|
|
this.addWidget('button', '-', 'remove', (value, widget, node) => { |
|
console.log(`Button clicked: ${value}`, widget, node) |
|
}) |
|
|
|
return r |
|
} |
|
break |
|
} |
|
case 'Styles Loader (mtb)': { |
|
const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions |
|
nodeType.prototype.getExtraMenuOptions = function (_, options) { |
|
const r = origGetExtraMenuOptions |
|
? origGetExtraMenuOptions.apply(this, arguments) |
|
: undefined |
|
|
|
const getStyle = async (node) => { |
|
try { |
|
const getStyles = await api.fetchApi('/mtb/actions', { |
|
method: 'POST', |
|
body: JSON.stringify({ |
|
name: 'getStyles', |
|
args: node.widgets?.[0].value ? node.widgets[0].value : '', |
|
}), |
|
}) |
|
|
|
const output = await getStyles.json() |
|
return output?.result |
|
} catch (e) { |
|
console.error(e) |
|
} |
|
} |
|
const extracters = [ |
|
{ |
|
content: 'Extract Positive to Text node', |
|
callback: async () => { |
|
const style = await getStyle(this) |
|
if (style && style.length >= 1) { |
|
if (style[0]) { |
|
window.MTB?.notify?.( |
|
`Extracted positive from ${this.widgets[0].value}`, |
|
) |
|
|
|
const tn = LiteGraph.createNode('CLIPTextEncode') |
|
app.graph.add(tn) |
|
tn.title = `${this.widgets[0].value} (Positive)` |
|
tn.widgets[0].value = style[0] |
|
} else { |
|
window.MTB?.notify?.( |
|
`No positive to extract for ${this.widgets[0].value}`, |
|
) |
|
} |
|
} |
|
}, |
|
}, |
|
{ |
|
content: 'Extract Negative to Text node', |
|
callback: async () => { |
|
const style = await getStyle(this) |
|
if (style && style.length >= 2) { |
|
if (style[1]) { |
|
window.MTB?.notify?.( |
|
`Extracted negative from ${this.widgets[0].value}`, |
|
) |
|
const tn = LiteGraph.createNode('CLIPTextEncode') |
|
app.graph.add(tn) |
|
tn.title = `${this.widgets[0].value} (Negative)` |
|
tn.widgets[0].value = style[1] |
|
} else { |
|
window.MTB.notify( |
|
`No negative to extract for ${this.widgets[0].value}`, |
|
) |
|
} |
|
} |
|
}, |
|
}, |
|
] |
|
options.push(...extracters) |
|
} |
|
|
|
break |
|
} |
|
|
|
|
|
case 'Apply Text Template (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'var', '*') |
|
break |
|
} |
|
case 'Save Data Bundle (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'data', '*') |
|
break |
|
} |
|
case 'Add To Playlist (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'video', 'VIDEO') |
|
break |
|
} |
|
case 'Interpolate Condition (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'condition', 'CONDITIONING') |
|
break |
|
} |
|
case 'Psd Save (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'input_', 'PSDLAYER') |
|
break |
|
} |
|
|
|
|
|
|
|
|
|
case 'Stack Images (mtb)': |
|
case 'Concat Images (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'image', 'IMAGE') |
|
break |
|
} |
|
case 'Audio Sequence (mtb)': |
|
case 'Audio Stack (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'audio', 'AUDIO') |
|
break |
|
} |
|
case 'Batch Float Assemble (mtb)': |
|
case 'Batch Float Math (mtb)': |
|
case 'Plot Batch Float (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'floats', 'FLOATS') |
|
break |
|
} |
|
case 'Batch Merge (mtb)': { |
|
shared.setupDynamicConnections(nodeType, 'batches', 'IMAGE') |
|
|
|
break |
|
} |
|
|
|
case 'Math Expression (mtb)': { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function () { |
|
const r = onNodeCreated |
|
? onNodeCreated.apply(this, arguments) |
|
: undefined |
|
this.addInput('x', '*') |
|
return r |
|
} |
|
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange |
|
nodeType.prototype.onConnectionsChange = function ( |
|
_type, |
|
index, |
|
connected, |
|
link_info, |
|
) { |
|
const r = onConnectionsChange |
|
? onConnectionsChange.apply(this, arguments) |
|
: undefined |
|
shared.dynamic_connection(this, index, connected, 'var_', '*', { |
|
nameArray: ['x', 'y', 'z'], |
|
}) |
|
|
|
|
|
if (link_info) { |
|
const fromNode = this.graph._nodes.find( |
|
(otherNode) => otherNode.id !== link_info.origin_id, |
|
) |
|
const type = fromNode.outputs[link_info.origin_slot].type |
|
this.inputs[index].type = type |
|
|
|
} |
|
|
|
if (!connected) { |
|
this.inputs[index].type = '*' |
|
this.inputs[index].label = `number_${index + 1}` |
|
} |
|
} |
|
|
|
break |
|
} |
|
|
|
case 'Batch Shape (mtb)': |
|
case 'Mask To Image (mtb)': |
|
case 'Text To Image (mtb)': { |
|
shared.addMenuHandler(nodeType, function (_app, options) { |
|
|
|
const item = { |
|
content: 'swap colors', |
|
title: 'Swap BG/FG Color ⚡', |
|
callback: (_menuItem) => { |
|
const color_w = this.widgets.find((w) => w.name === 'color') |
|
const bg_w = this.widgets.find( |
|
(w) => w.name === 'background' || w.name === 'bg_color', |
|
) |
|
|
|
const color = color_w.value |
|
const bg = bg_w.value |
|
|
|
color_w.value = bg |
|
bg_w.value = color |
|
}, |
|
} |
|
|
|
options.push(item) |
|
return [item] |
|
}) |
|
break |
|
} |
|
case 'Save Tensors (mtb)': { |
|
const onDrawBackground = nodeType.prototype.onDrawBackground |
|
nodeType.prototype.onDrawBackground = function (ctx, canvas) { |
|
const r = onDrawBackground |
|
? onDrawBackground.apply(this, arguments) |
|
: undefined |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return r |
|
} |
|
break |
|
} |
|
default: { |
|
break |
|
} |
|
} |
|
}, |
|
} |
|
|
|
app.registerExtension(mtb_widgets) |
|
|