Spaces:
Running
on
Zero
Running
on
Zero
| 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)' | |
| // }, | |
| }) | |