278 lines
8.9 KiB
JavaScript
278 lines
8.9 KiB
JavaScript
import { app } from "/scripts/app.js"
|
|
|
|
class OpenPoseEditor {
|
|
constructor(app, node, container) {
|
|
this.node = node
|
|
this.images = node.widgets.filter(w => ['pose','depth','normal','canny'].indexOf(w.name)> -1)
|
|
|
|
this.iframe = document.createElement('iframe')
|
|
this.iframe.src = '/extensions/ComfyUI_3dPoseEditor/editor.html'
|
|
container.appendChild(this.iframe)
|
|
}
|
|
|
|
async uploadPoseFile(imageData) {
|
|
for (let type in imageData) {
|
|
const blobData = await fetch(imageData[type]).then(r => r.blob())
|
|
const filename = `${this.node.name}_${type}.png`
|
|
|
|
const image = this.images[this.images.findIndex(i => i.name === type)]
|
|
|
|
let formData = new FormData()
|
|
formData.append('image', blobData, filename)
|
|
formData.append('overwrite', 'true')
|
|
formData.append('type', 'temp')
|
|
formData.append('subfolder', '3dposeeditor')
|
|
|
|
const resp = await fetch('/upload/image', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
if (resp.status === 200) {
|
|
const data = await resp.json()
|
|
|
|
console.log("[3D Pose Editor] Upload image success.", data.name)
|
|
|
|
image.options.value = data.name
|
|
image.value = data.name
|
|
}
|
|
}
|
|
}
|
|
|
|
remove() {
|
|
this.container.remove()
|
|
}
|
|
}
|
|
|
|
function createOpenPoseEditor(node, inputName, inputData, app) {
|
|
node.name = inputName
|
|
|
|
const waitForElement = async function (parent, selector, exist) {
|
|
return new Promise(function (resolve) {
|
|
if (!!parent.querySelector(selector) === exist) {
|
|
resolve(undefined)
|
|
|
|
return
|
|
}
|
|
|
|
new MutationObserver((mutationList, observer) => {
|
|
if (!!parent.querySelector(selector) === exist) {
|
|
observer.disconnect()
|
|
resolve(undefined)
|
|
}
|
|
}).observe(parent, {
|
|
childList: true,
|
|
subtree: true,
|
|
})
|
|
})
|
|
}
|
|
|
|
const timeout = ms => {
|
|
return new Promise(function (resolve, reject) {
|
|
setTimeout(() => reject('Timeout'), ms)
|
|
})
|
|
}
|
|
|
|
const postMessage = function (message) {
|
|
node.openposeeditor?.iframe?.contentWindow?.postMessage(message, "*")
|
|
}
|
|
|
|
const widget = {
|
|
type: "openposeeditor",
|
|
name: `op3d${inputName}`,
|
|
callback: () => {},
|
|
draw: function (ctx, _, widgetWidth, y, widgetHeight) {
|
|
const margin = 10
|
|
const top_offset = 5
|
|
const visible = app.canvas.ds.scale > 0.6 && this.type === "openposeeditor"
|
|
const w = widgetWidth - margin * 4
|
|
const clientRectBound = ctx.canvas.getBoundingClientRect()
|
|
const transform = new DOMMatrix()
|
|
.scaleSelf(
|
|
clientRectBound.width / ctx.canvas.width,
|
|
clientRectBound.height / ctx.canvas.height
|
|
)
|
|
.multiplySelf(ctx.getTransform())
|
|
.translateSelf(margin, margin + y)
|
|
|
|
Object.assign(this.openposeeditor.style, {
|
|
left: `${transform.a * margin + transform.e}px`,
|
|
top: `${transform.d + transform.f + top_offset}px`,
|
|
width: `${(w * transform.a)}px`,
|
|
height: `${(w * transform.d - widgetHeight - (margin * 15) * transform.d)}px`,
|
|
position: "absolute",
|
|
overflow: "hidden",
|
|
zIndex: app.graph._nodes.indexOf(node),
|
|
})
|
|
|
|
Object.assign(this.openposeeditor.children[0].style, {
|
|
transformOrigin: "50% 50%",
|
|
width: '100%',
|
|
height: '100%',
|
|
border: '0 none',
|
|
})
|
|
|
|
this.openposeeditor.hidden = !visible
|
|
},
|
|
handleMessage (event) {
|
|
const { data } = event
|
|
if (data && data.cmd && data.cmd === 'openpose-3d' && data.method) {
|
|
const method = data.method
|
|
|
|
if ('MakeImages' === method && true === node.isMakingImages) {
|
|
node.openposeeditor.uploadPoseFile(data.payload)
|
|
node.isMakingImages = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const container = document.createElement('div')
|
|
container.id = `comfyui-${inputName.toLowerCase()}`
|
|
container.classList.add('comfyui-3dopenpose-editor')
|
|
|
|
node.isMakingImages = false
|
|
const debounce = function (func, timeout = 300) {
|
|
let timer
|
|
return (...args) => {
|
|
clearTimeout(timer)
|
|
timer = setTimeout(() => { func.apply(this, args) }, timeout)
|
|
}
|
|
}
|
|
|
|
const handlerMouseUp = debounce(() => {
|
|
if (true === node.isMakingImages) return
|
|
|
|
node.isMakingImages = true
|
|
postMessage({
|
|
cmd: 'openpose-3d',
|
|
method: 'MakeImages',
|
|
type: 'call',
|
|
payload: null,
|
|
})
|
|
})
|
|
|
|
node.openposeeditor = new OpenPoseEditor(app, node, container)
|
|
widget.openposeeditor = container
|
|
widget.parent = node
|
|
|
|
document.body.appendChild(widget.openposeeditor)
|
|
|
|
node.addCustomWidget(widget)
|
|
|
|
node.onRemoved = () => {
|
|
window.removeEventListener('message', widget.handleMessage, false)
|
|
node.openposeeditor.iframe.contentWindow.removeEventListener('mouseup', handlerMouseUp, false)
|
|
// When removing this node we need to remove the input from the DOM
|
|
for (let y in node.widgets) {
|
|
if (node.widgets[y].openposeeditor) {
|
|
node.widgets[y].openposeeditor.remove()
|
|
}
|
|
}
|
|
}
|
|
|
|
node.onResize = function () {
|
|
let [w, h] = this.size
|
|
if (w <= 600) w = 600
|
|
if (h <= 400) h = 400
|
|
|
|
if (w > 400) {
|
|
h = w + 20
|
|
}
|
|
|
|
this.size = [w, h]
|
|
}
|
|
|
|
widget.onRemove = () => {
|
|
window.removeEventListener('message', widget.handleMessage, false)
|
|
widget.openposeeditor?.remove()
|
|
|
|
node.openposeeditor.iframe.contentWindow.removeEventListener('mouseup', handlerMouseUp, false)
|
|
}
|
|
|
|
node.onDrawBackground = function (ctx) {
|
|
if (!this.flags.collapsed) {
|
|
node.openposeeditor.iframe.hidden = false
|
|
} else {
|
|
node.openposeeditor.iframe.hidden = true
|
|
}
|
|
}
|
|
|
|
app.canvas.onDrawBackground = function () {
|
|
// Draw node isnt fired once the node is off the screen
|
|
// if it goes off screen quickly, the input may not be removed
|
|
// this shifts it off screen so it can be moved back if the node is visible.
|
|
for (let n in app.graph._nodes) {
|
|
n = app.graph._nodes[n]
|
|
for (let w in n.widgets) {
|
|
let wid = n.widgets[w]
|
|
if (Object.hasOwn(wid, "openposeeditor")) {
|
|
wid.openposeeditor.style.left = -8000 + "px"
|
|
wid.openposeeditor.style.position = "absolute"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setTimeout(() => {
|
|
Promise.race([
|
|
waitForElement(node.openposeeditor.iframe.contentDocument.body, "canvas", true),
|
|
timeout(5000)
|
|
]).then(() => {
|
|
postMessage({
|
|
cmd: 'openpose-3d',
|
|
method: 'MakeImages',
|
|
type: 'call',
|
|
payload: null,
|
|
})
|
|
|
|
node.openposeeditor.iframe.contentWindow.addEventListener('mouseup', handlerMouseUp, false)
|
|
}).catch(() => {
|
|
console.log("[3D Pose Editor] Editor initialize failed.")
|
|
})
|
|
}, 150)
|
|
|
|
return {
|
|
widget: widget,
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "Hina.PoseEditor3D",
|
|
|
|
async init (app) {
|
|
const style = document.createElement("style")
|
|
style.innerText = `.comfyui-3dopenpose-editor iframe { border: 0 none; }`
|
|
document.head.appendChild(style)
|
|
},
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name === "Hina.PoseEditor3D") {
|
|
console.log("[3D Pose Editor] Registering node...", nodeData)
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated
|
|
|
|
nodeType.prototype.onNodeCreated = async function() {
|
|
const r = onNodeCreated
|
|
? onNodeCreated.apply(this, arguments)
|
|
: undefined
|
|
|
|
let openPoseNode = app.graph._nodes.filter(
|
|
(wi) => wi.type == "Hina.PoseEditor3D"
|
|
)
|
|
let nodeName = `OpenPoseEditor_${openPoseNode.length}`
|
|
|
|
console.log(`[3D Pose Editor] Create PoseNode: ${nodeName}`)
|
|
|
|
const result = await createOpenPoseEditor.apply(this, [this, nodeName, {}, app])
|
|
|
|
window.addEventListener('message', result.widget.handleMessage, false)
|
|
|
|
this.setSize([600, 400])
|
|
|
|
return r
|
|
}
|
|
}
|
|
},
|
|
})
|