497 lines
14 KiB
JavaScript
497 lines
14 KiB
JavaScript
import { app } from '../../scripts/app.js'
|
|
import * as shared from './comfy_shared.js'
|
|
import { infoLogger } from './comfy_shared.js'
|
|
import { MtbWidgets } from './mtb_widgets.js'
|
|
import { ComfyWidgets } from '../../scripts/widgets.js'
|
|
import * as mtb_widgets from './mtb_widgets.js'
|
|
|
|
/**
|
|
* @typedef {'number'|'string'|'vector2'|'vector3'|'vector4'|'color'} ConstantType
|
|
* @typedef {import ("../../../web/types/litegraph.d.ts").LGraphNode} Node
|
|
* @typedef {{x:number,y:number,z?:number,w?:number}} VectorValue
|
|
* @typedef {}
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @param {number} size - The number of axis of the vector (2,3 or 4)
|
|
* @param {number} val - The default scalar value to fill the vector with
|
|
* @returns {VectorValue} vector
|
|
* */
|
|
const initVector = (size, val = 0.0) => {
|
|
const res = {}
|
|
for (let i = 0; i < size; i++) {
|
|
const axis = mtb_widgets.VECTOR_AXIS[i]
|
|
res[axis] = val
|
|
}
|
|
return res
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @extends {Node}
|
|
* @classdesc Wrapper for the python node
|
|
*/
|
|
export class ConstantJs {
|
|
constructor(python_node) {
|
|
// this.uuid = shared.makeUUID()
|
|
const wrapper = this
|
|
|
|
python_node.shape = LiteGraph.BOX_SHAPE
|
|
python_node.serialize_widgets = true
|
|
|
|
const onNodeCreated = python_node.prototype.onNodeCreated
|
|
python_node.prototype.onNodeCreated = function () {
|
|
const r = onNodeCreated ? onNodeCreated.apply(this) : undefined
|
|
|
|
this.addProperty('type', 'number')
|
|
this.addProperty('value', 0)
|
|
|
|
this.removeInput(0)
|
|
this.removeOutput(0)
|
|
|
|
this.addOutput('Output', '*')
|
|
|
|
// bind our wrapper
|
|
this.configure = wrapper.configure.bind(this)
|
|
// this.applyToGraph = wrapper.applyToGraph.bind(this)
|
|
this.updateWidgets = wrapper.updateWidgets.bind(this)
|
|
this.convertValue = wrapper.convertValue.bind(this)
|
|
// this.updateOutput = wrapper.updateOutput.bind(this)
|
|
this.updateOutputType = wrapper.updateOutputType.bind(this)
|
|
// this.updateTargetWidgets = wrapper.updateTargetWidgets.bind(this)
|
|
|
|
this.addWidget(
|
|
'combo',
|
|
'Type',
|
|
this.properties.type,
|
|
(value) => {
|
|
this.properties.type = value
|
|
this.updateWidgets()
|
|
this.updateOutputType()
|
|
},
|
|
{
|
|
values: [
|
|
// 'number',
|
|
'float',
|
|
'int',
|
|
'string',
|
|
'vector2',
|
|
'vector3',
|
|
'vector4',
|
|
'color',
|
|
],
|
|
},
|
|
)
|
|
this.updateWidgets()
|
|
this.updateOutputType()
|
|
|
|
for (let n = 0; n < this.inputs.length; n++) {
|
|
this.removeInput(n)
|
|
}
|
|
this.inputs = []
|
|
return r
|
|
}
|
|
return
|
|
}
|
|
|
|
// NOTE: this is called onPrompt
|
|
// applyToGraph() {
|
|
// infoLogger('Updating values for backend')
|
|
// this.updateTargetWidgets()
|
|
// }
|
|
|
|
// NOTE: deserialization happens here
|
|
configure(info) {
|
|
// super.configure(info)
|
|
infoLogger('Configure Constant', { info, node: this })
|
|
|
|
this.properties.type = info.properties.type
|
|
this.properties.value = info.properties.value
|
|
|
|
this.pos = info.pos
|
|
this.order = info.order
|
|
|
|
this.updateWidgets()
|
|
this.updateOutputType()
|
|
}
|
|
|
|
/**
|
|
* Convert the old value type to the new one, falling back to some default
|
|
* @param {ConstantType} propType - The target type
|
|
*/
|
|
convertValue(propType) {
|
|
switch (propType) {
|
|
case 'color': {
|
|
if (typeof this.properties.value !== 'string') {
|
|
this.properties.value = '#ffffff'
|
|
} else if (this.properties.value[0] !== '#') {
|
|
this.properties.value = '#ff0000'
|
|
}
|
|
break
|
|
}
|
|
case 'int': {
|
|
if (typeof this.properties.value === 'object') {
|
|
this.properties.value = Number.parseInt(this.properties.value.x)
|
|
} else {
|
|
this.properties.value = Number.parseInt(this.properties.value) || 0
|
|
}
|
|
break
|
|
}
|
|
case 'float': {
|
|
if (typeof this.properties.value === 'object') {
|
|
this.properties.value = Number.parseFloat(this.properties.value.x)
|
|
} else {
|
|
this.properties.value =
|
|
Number.parseFloat(this.properties.value) || 0.0
|
|
}
|
|
break
|
|
}
|
|
case 'string': {
|
|
if (typeof this.properties.value !== 'string') {
|
|
this.properties.value = JSON.stringify(this.properties.value)
|
|
}
|
|
break
|
|
}
|
|
case 'vector2':
|
|
case 'vector3':
|
|
case 'vector4': {
|
|
const numInputs = Number.parseInt(propType.charAt(6))
|
|
if (!this.properties.value) {
|
|
this.properties.value = initVector(numInputs) // Array.from({ length: numInputs }, () => 0.0)
|
|
} else if (typeof this.properties.value === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(this.properties.value)
|
|
const newVec = {}
|
|
for (
|
|
let i = 0;
|
|
i < Object.keys(mtb_widgets.VECTOR_AXIS).length;
|
|
i++
|
|
) {
|
|
const axis = mtb_widgets.VECTOR_AXIS[i]
|
|
if (Object.keys(parsed).includes(axis)) {
|
|
newVec[axis] = parsed[axis]
|
|
}
|
|
}
|
|
this.properties.value = newVec
|
|
} catch (e) {
|
|
shared.errorLogger(e)
|
|
infoLogger(
|
|
`Couldn't parse string to vec (${this.properties.value})`,
|
|
)
|
|
this.properties.value = initVector(numInputs)
|
|
}
|
|
} else if (typeof this.properties.value === 'number') {
|
|
const newVec = initVector(numInputs)
|
|
newVec.x = Number.parseFloat(this.properties.value)
|
|
this.properties.value = newVec
|
|
}
|
|
|
|
if (
|
|
typeof this.properties.value === 'object' &&
|
|
Object.keys(this.properties.value).length !== numInputs
|
|
) {
|
|
const current = Object.keys(this.properties.value)
|
|
if (current.length < numInputs) {
|
|
infoLogger('current value smaller than target, adjusting')
|
|
for (let index = current.length; index < numInputs; index++) {
|
|
this.properties.value[mtb_widgets.VECTOR_AXIS[index]] = 0.0
|
|
}
|
|
} else {
|
|
infoLogger('current value greater than target, adjusting')
|
|
const newVal = {}
|
|
for (let index = 0; index < numInputs; index++) {
|
|
newVal[mtb_widgets.VECTOR_AXIS[index]] =
|
|
this.properties.value[mtb_widgets.VECTOR_AXIS[index]]
|
|
}
|
|
this.properties.value = newVal
|
|
}
|
|
}
|
|
break
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all widgets but the comboBox for selecting the type
|
|
* then recreate the appropriate widget from scratch
|
|
*/
|
|
updateWidgets() {
|
|
// NOTE: Remove existing widgets
|
|
for (let i = 1; i < this.widgets.length; i++) {
|
|
const element = this.widgets[i]
|
|
if (element.onRemove) {
|
|
element.onRemove()
|
|
}
|
|
// element?.onRemove()
|
|
}
|
|
|
|
this.widgets.splice(1)
|
|
this.widgets[0].value = this.properties.type
|
|
|
|
this.convertValue(this.properties.type)
|
|
|
|
switch (this.properties.type) {
|
|
case 'color': {
|
|
const col_widget = this.addCustomWidget(
|
|
MtbWidgets.COLOR('Value', this.properties.value),
|
|
)
|
|
col_widget.callback = (col) => {
|
|
this.properties.value = col
|
|
// this.updateOutput()
|
|
}
|
|
break
|
|
}
|
|
case 'int': {
|
|
const f_widget = this.addCustomWidget(
|
|
ComfyWidgets.INT(
|
|
this,
|
|
'Value',
|
|
[
|
|
'',
|
|
{
|
|
default: this.properties.value,
|
|
callback: (val) => console.log('VALUE', val),
|
|
},
|
|
],
|
|
app,
|
|
),
|
|
)
|
|
|
|
f_widget.widget.callback = (val) => {
|
|
this.properties.value = val
|
|
}
|
|
|
|
break
|
|
}
|
|
case 'float': {
|
|
this.addWidget('number', 'Value', this.properties.value, (val) => {
|
|
this.properties.value = val
|
|
})
|
|
break
|
|
}
|
|
case 'string': {
|
|
mtb_widgets.addMultilineWidget(
|
|
this,
|
|
'Value',
|
|
{
|
|
defaultVal: this.properties.value,
|
|
},
|
|
(v) => {
|
|
this.properties.value = v
|
|
// this.updateOutput()
|
|
},
|
|
)
|
|
break
|
|
}
|
|
case 'vector2':
|
|
case 'vector3':
|
|
case 'vector4': {
|
|
const numInputs = Number.parseInt(this.properties.type.charAt(6))
|
|
const node = this
|
|
const v_widget = mtb_widgets.addVectorWidget(
|
|
this,
|
|
'Value',
|
|
this.properties.value, // value
|
|
numInputs, // vector_size
|
|
function (v) {
|
|
node.properties.value = v
|
|
// this.updateOutput()
|
|
},
|
|
)
|
|
break
|
|
}
|
|
|
|
// NOTE: this is not reached anymore, kept for reference
|
|
case 'number': {
|
|
if (typeof this.properties.value !== 'number') {
|
|
this.properties.value = 0.0
|
|
}
|
|
const n_widget = this.addWidget(
|
|
'number',
|
|
'Value',
|
|
this.properties.force_int
|
|
? Number.parseInt(this.properties.value)
|
|
: this.properties.value,
|
|
(value) => {
|
|
this.properties.value = this.properties.force_int
|
|
? Number.parseInt(value)
|
|
: value
|
|
// this.updateOutput()
|
|
},
|
|
)
|
|
//override the callback
|
|
const origCallback = n_widget.callback
|
|
const node = this
|
|
n_widget.callback = function (val) {
|
|
const r = origCallback ? origCallback.apply(this, [val]) : undefined
|
|
if (node.properties.force_int) {
|
|
// TODO: rework this, a it makes it harder to manipulate
|
|
this.value = Number.parseInt(this.value)
|
|
node.properties.value = Number.parseInt(this.value)
|
|
}
|
|
infoLogger('NEW NUMBER', this.value)
|
|
return r
|
|
}
|
|
|
|
this.addWidget(
|
|
'toggle',
|
|
'Convert to Integer',
|
|
this.properties.force_int,
|
|
(value) => {
|
|
this.properties.force_int = value
|
|
this.updateOutputType()
|
|
},
|
|
)
|
|
break
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
onConnectionsChange(type, slotIndex, isConnected, link, ioSlot) {
|
|
// super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot)
|
|
if (isConnected) {
|
|
this.updateTargetWidgets([link.id])
|
|
}
|
|
}
|
|
|
|
updateOutputType() {
|
|
infoLogger('Updating output type')
|
|
const rm_if_mismatch = (type) => {
|
|
if (this.outputs[0].type !== type) {
|
|
for (let i = 0; i < this.outputs.length; i++) {
|
|
this.removeOutput(i)
|
|
}
|
|
this.addOutput('output', type)
|
|
// this.setOutputDataType(0, type)
|
|
}
|
|
}
|
|
switch (this.properties.type) {
|
|
case 'color':
|
|
rm_if_mismatch('COLOR')
|
|
break
|
|
case 'float':
|
|
rm_if_mismatch('FLOAT')
|
|
break
|
|
case 'int':
|
|
rm_if_mismatch('INT')
|
|
break
|
|
case 'number':
|
|
if (this.properties.force_int) {
|
|
rm_if_mismatch('INT')
|
|
} else {
|
|
rm_if_mismatch('FLOAT')
|
|
}
|
|
break
|
|
case 'string':
|
|
rm_if_mismatch('STRING')
|
|
break
|
|
// case 'vector2':
|
|
// case 'vector3':
|
|
// case 'vector4':
|
|
// rm_if_mismatch('FLOAT')
|
|
// break
|
|
case 'vector2':
|
|
rm_if_mismatch('VECTOR2')
|
|
break
|
|
case 'vector3':
|
|
rm_if_mismatch('VECTOR3')
|
|
break
|
|
case 'vector4':
|
|
rm_if_mismatch('VECTOR4')
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
// this.updateOutput()
|
|
}
|
|
|
|
/**
|
|
* NOTE: This feels hacky but seems to work fine
|
|
* since Constant is a virtual node.
|
|
*/
|
|
updateTargetWidgets(u_links) {
|
|
infoLogger('Updating target widgets')
|
|
if (!app.graph.links) return
|
|
const links = u_links || this.outputs[0].links
|
|
if (!links) return
|
|
for (let i = 0; i < links.length; i++) {
|
|
const link = app.graph.links[links[i]]
|
|
const tgt_node = app.graph.getNodeById(link.target_id)
|
|
if (!tgt_node || !tgt_node.inputs) return
|
|
const tgt_input = tgt_node.inputs[link.target_slot]
|
|
if (!tgt_input) return
|
|
const tgt_widget = tgt_node.widgets.filter(
|
|
(w) => w.name === tgt_input.name,
|
|
)
|
|
// infoLogger('Constant Target Node', tgt_node)
|
|
// infoLogger('Constant Target Input', tgt_input)
|
|
if (!tgt_widget || tgt_widget.length === 0) return
|
|
|
|
tgt_widget[0].value = this.properties.value
|
|
}
|
|
}
|
|
|
|
updateOutput() {
|
|
infoLogger('Updating output value')
|
|
const value = this.properties.value
|
|
|
|
switch (this.properties.type) {
|
|
case 'color':
|
|
this.setOutputData(0, value)
|
|
break
|
|
case 'number':
|
|
if (this.properties.force_int) {
|
|
this.setOutputData(0, Number.parseInt(value))
|
|
} else {
|
|
this.setOutputData(0, Number.parseFloat(value))
|
|
}
|
|
break
|
|
case 'string':
|
|
this.setOutputData(0, value.toString())
|
|
break
|
|
case 'vector2':
|
|
case 'vector3':
|
|
case 'vector4':
|
|
this.setOutputData(0, value)
|
|
break
|
|
|
|
// case 'vector2':
|
|
// this.setOutputData(0, value.slice(0, 2))
|
|
// break
|
|
// case 'vector3':
|
|
// this.setOutputData(0, value.slice(0, 3))
|
|
// break
|
|
// case 'vector4':
|
|
// this.setOutputData(0, value.slice(0, 4))
|
|
// break
|
|
default:
|
|
break
|
|
}
|
|
|
|
infoLogger('New Value', this.value)
|
|
|
|
this.updateTargetWidgets()
|
|
}
|
|
}
|
|
app.registerExtension({
|
|
name: 'mtb.constant',
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
|
|
if (nodeData.name === 'Constant (mtb)') {
|
|
new ConstantJs(nodeType)
|
|
}
|
|
},
|
|
// NOTE: old js only registration
|
|
//
|
|
// registerCustomNodes() {
|
|
// LiteGraph.registerNodeType('Constant (mtb)', Constant)
|
|
//
|
|
// Constant.category = 'mtb/utils'
|
|
// Constant.title = 'Constant (mtb)'
|
|
// },
|
|
})
|