| import { app } from '../../../scripts/app.js' |
| import { api } from '../../../scripts/api.js' |
| import { $el } from '../../../scripts/ui.js' |
|
|
|
|
| function get_position_style (ctx, widget_width, y, node_height) { |
| const MARGIN = 14 |
|
|
| |
| const elRect = ctx.canvas.getBoundingClientRect() |
| const transform = new DOMMatrix() |
| .scaleSelf( |
| elRect.width / ctx.canvas.width, |
| elRect.height / ctx.canvas.height |
| ) |
| .multiplySelf(ctx.getTransform()) |
| .translateSelf(MARGIN, MARGIN + y) |
|
|
| return { |
| transformOrigin: '0 0', |
| transform: transform, |
| left: `0`, |
| top: `0`, |
| cursor: 'pointer', |
| position: 'absolute', |
| maxWidth: `${widget_width - MARGIN * 2}px`, |
| |
| width: `${widget_width - MARGIN * 2}px`, |
| |
| |
| |
| display: 'flex', |
| flexDirection: 'column', |
| |
| justifyContent: 'space-around' |
| } |
| } |
|
|
|
|
| app.registerExtension({ |
| name: 'Mixlab.3D.SaveTripoSRMesh', |
| async beforeRegisterNodeDef (nodeType, nodeData, app) { |
| if (nodeType.comfyClass == 'SaveTripoSRMesh') { |
| const orig_nodeCreated = nodeType.prototype.onNodeCreated |
| nodeType.prototype.onNodeCreated = async function () { |
| orig_nodeCreated?.apply(this, arguments) |
|
|
| const widget = { |
| type: 'div', |
| name: 'preview', |
| draw (ctx, node, widget_width, y, widget_height) { |
| Object.assign( |
| this.div.style, |
| get_position_style(ctx, widget_width, 88, node.size[1]) |
| ) |
| } |
| |
| |
| |
| |
| } |
|
|
| widget.div = $el('div', {}) |
| widget.div.style.width = `120px` |
|
|
| document.body.appendChild(widget.div) |
|
|
| |
| |
| |
|
|
| this.addCustomWidget(widget) |
|
|
| const onResize = this.onResize |
| this.onResize = () => { |
| widget.div.style.width = `${this.size[0]}px` |
| widget.div.style.height = `${this.size[1] - 112}px` |
| let mvs = widget.div.querySelectorAll('model-viewer') |
| for (const m of mvs) { |
| m.style.height = `${Math.round( |
| (this.size[1] - 112) / mvs.length |
| )}px` |
| |
| } |
| |
| return onResize?.apply(this, arguments) |
| } |
|
|
| const onRemoved = this.onRemoved |
| this.onRemoved = () => { |
| widget.div.remove() |
| return onRemoved?.() |
| } |
|
|
| if (this.onResize) { |
| this.onResize(this.size) |
| } |
| |
| this.serialize_widgets = false |
| } |
|
|
| const onExecuted = nodeType.prototype.onExecuted |
| nodeType.prototype.onExecuted = function (message) { |
| const r = onExecuted?.apply?.(this, arguments) |
|
|
| let widget = this.widgets.filter(d => d.name == 'preview')[0] |
| console.log('Test', widget, message) |
|
|
| let meshes = message.mesh |
| widget.div.innerHTML = '' |
|
|
| for (const mesh of meshes) { |
| if (mesh) { |
| const { filename, subfolder, type } = mesh |
| const fileURL = api.apiURL( |
| `/view?filename=${encodeURIComponent( |
| filename |
| )}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}` |
| ) |
|
|
| let modelViewer = document.createElement('div') |
| modelViewer.innerHTML = `<model-viewer src="${fileURL}" |
| min-field-of-view="0deg" max-field-of-view="180deg" |
| shadow-intensity="1" |
| camera-controls |
| touch-action="pan-y" |
| style="width:100%;margin:4px;min-height:88px" |
| > |
| |
| <div class="controls"> |
| |
| <div><button class="export" style=" |
| background-color: var(--comfy-input-bg); |
| border-radius: 8px; |
| border-color: var(--border-color); |
| border-style: solid; |
| color: var(--descrip-text);cursor: pointer;">Export GLB</button></div> |
| |
| </div></model-viewer>` |
| widget.div.appendChild(modelViewer) |
| let modelViewerVariants= modelViewer |
| .querySelector('model-viewer'); |
| |
| modelViewer |
| .querySelector('.export') |
| .addEventListener('click', async e => { |
| e.preventDefault() |
| const glTF = await modelViewerVariants.exportScene() |
| const file = new File([glTF], filename) |
| const link = document.createElement('a') |
| link.download = file.name |
| link.href = URL.createObjectURL(file) |
| link.click() |
| }) |
| } |
| } |
|
|
| |
|
|
| this.onResize?.(this.size) |
|
|
| return r |
| } |
| } |
| }, |
| async loadedGraphNode (node, app) { |
| const sleep = (t = 1000) => { |
| return new Promise((res, rej) => { |
| setTimeout(() => res(1), t) |
| }) |
| } |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| } |
| }) |
|
|