[3d] apply new vue desige (#3018)

This commit is contained in:
Terry Jia
2025-03-13 20:37:54 -04:00
committed by GitHub
parent 744b5fab68
commit 0cfd6a487a
6 changed files with 128 additions and 219 deletions

View File

@@ -61,16 +61,22 @@ import Load3DScene from '@/components/load3d/Load3DScene.vue'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { import {
CameraType, CameraType,
Load3DNodeType,
MaterialMode, MaterialMode,
UpDirection UpDirection
} from '@/extensions/core/load3d/interfaces' } from '@/extensions/core/load3d/interfaces'
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { ComponentWidget } from '@/scripts/domWidget'
const props = defineProps<{ const { widget } = defineProps<{
node: any widget: ComponentWidget<string[]>
type: 'Load3D' | 'Preview3D'
}>() }>()
const node = ref(props.node) const inputSpec = widget.inputSpec as CustomInputSpec
const node = widget.node
const type = inputSpec.type as Load3DNodeType
const backgroundColor = ref('#000000') const backgroundColor = ref('#000000')
const showGrid = ref(true) const showGrid = ref(true)
const showPreview = ref(false) const showPreview = ref(false)
@@ -86,7 +92,7 @@ const materialMode = ref<MaterialMode>('original')
const edgeThreshold = ref(85) const edgeThreshold = ref(85)
const showPreviewButton = computed(() => { const showPreviewButton = computed(() => {
return !props.type.includes('Preview') return !type.includes('Preview')
}) })
const switchCamera = () => { const switchCamera = () => {
@@ -95,68 +101,68 @@ const switchCamera = () => {
showFOVButton.value = cameraType.value === 'perspective' showFOVButton.value = cameraType.value === 'perspective'
node.value.properties['Camera Type'] = cameraType.value node.properties['Camera Type'] = cameraType.value
} }
const togglePreview = (value: boolean) => { const togglePreview = (value: boolean) => {
showPreview.value = value showPreview.value = value
node.value.properties['Show Preview'] = showPreview.value node.properties['Show Preview'] = showPreview.value
} }
const toggleGrid = (value: boolean) => { const toggleGrid = (value: boolean) => {
showGrid.value = value showGrid.value = value
node.value.properties['Show Grid'] = showGrid.value node.properties['Show Grid'] = showGrid.value
} }
const handleUpdateLightIntensity = (value: number) => { const handleUpdateLightIntensity = (value: number) => {
lightIntensity.value = value lightIntensity.value = value
node.value.properties['Light Intensity'] = lightIntensity.value node.properties['Light Intensity'] = lightIntensity.value
} }
const handleBackgroundImageUpdate = async (file: File | null) => { const handleBackgroundImageUpdate = async (file: File | null) => {
if (!file) { if (!file) {
hasBackgroundImage.value = false hasBackgroundImage.value = false
backgroundImage.value = '' backgroundImage.value = ''
node.value.properties['Background Image'] = '' node.properties['Background Image'] = ''
return return
} }
backgroundImage.value = await Load3dUtils.uploadFile(file) backgroundImage.value = await Load3dUtils.uploadFile(file)
node.value.properties['Background Image'] = backgroundImage.value node.properties['Background Image'] = backgroundImage.value
} }
const handleUpdateFOV = (value: number) => { const handleUpdateFOV = (value: number) => {
fov.value = value fov.value = value
node.value.properties['FOV'] = fov.value node.properties['FOV'] = fov.value
} }
const handleUpdateEdgeThreshold = (value: number) => { const handleUpdateEdgeThreshold = (value: number) => {
edgeThreshold.value = value edgeThreshold.value = value
node.value.properties['Edge Threshold'] = edgeThreshold.value node.properties['Edge Threshold'] = edgeThreshold.value
} }
const handleBackgroundColorChange = (value: string) => { const handleBackgroundColorChange = (value: string) => {
backgroundColor.value = value backgroundColor.value = value
node.value.properties['Background Color'] = value node.properties['Background Color'] = value
} }
const handleUpdateUpDirection = (value: UpDirection) => { const handleUpdateUpDirection = (value: UpDirection) => {
upDirection.value = value upDirection.value = value
node.value.properties['Up Direction'] = value node.properties['Up Direction'] = value
} }
const handleUpdateMaterialMode = (value: MaterialMode) => { const handleUpdateMaterialMode = (value: MaterialMode) => {
materialMode.value = value materialMode.value = value
node.value.properties['Material Mode'] = value node.properties['Material Mode'] = value
} }
const listenMaterialModeChange = (mode: MaterialMode) => { const listenMaterialModeChange = (mode: MaterialMode) => {

View File

@@ -74,16 +74,22 @@ import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { import {
AnimationItem, AnimationItem,
CameraType, CameraType,
Load3DAnimationNodeType,
MaterialMode, MaterialMode,
UpDirection UpDirection
} from '@/extensions/core/load3d/interfaces' } from '@/extensions/core/load3d/interfaces'
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import type { ComponentWidget } from '@/scripts/domWidget'
const props = defineProps<{ const { widget } = defineProps<{
node: any widget: ComponentWidget<string[]>
type: 'Load3DAnimation' | 'Preview3DAnimation'
}>() }>()
const node = ref(props.node) const inputSpec = widget.inputSpec as CustomInputSpec
const node = widget.node
const type = inputSpec.type as Load3DAnimationNodeType
const backgroundColor = ref('#000000') const backgroundColor = ref('#000000')
const showGrid = ref(true) const showGrid = ref(true)
const showPreview = ref(false) const showPreview = ref(false)
@@ -101,7 +107,7 @@ const selectedAnimation = ref(0)
const backgroundImage = ref('') const backgroundImage = ref('')
const showPreviewButton = computed(() => { const showPreviewButton = computed(() => {
return !props.type.includes('Preview') return !type.includes('Preview')
}) })
const switchCamera = () => { const switchCamera = () => {
@@ -110,44 +116,44 @@ const switchCamera = () => {
showFOVButton.value = cameraType.value === 'perspective' showFOVButton.value = cameraType.value === 'perspective'
node.value.properties['Camera Type'] = cameraType.value node.properties['Camera Type'] = cameraType.value
} }
const togglePreview = (value: boolean) => { const togglePreview = (value: boolean) => {
showPreview.value = value showPreview.value = value
node.value.properties['Show Preview'] = showPreview.value node.properties['Show Preview'] = showPreview.value
} }
const toggleGrid = (value: boolean) => { const toggleGrid = (value: boolean) => {
showGrid.value = value showGrid.value = value
node.value.properties['Show Grid'] = showGrid.value node.properties['Show Grid'] = showGrid.value
} }
const handleUpdateLightIntensity = (value: number) => { const handleUpdateLightIntensity = (value: number) => {
lightIntensity.value = value lightIntensity.value = value
node.value.properties['Light Intensity'] = lightIntensity.value node.properties['Light Intensity'] = lightIntensity.value
} }
const handleBackgroundImageUpdate = async (file: File | null) => { const handleBackgroundImageUpdate = async (file: File | null) => {
if (!file) { if (!file) {
hasBackgroundImage.value = false hasBackgroundImage.value = false
backgroundImage.value = '' backgroundImage.value = ''
node.value.properties['Background Image'] = '' node.properties['Background Image'] = ''
return return
} }
backgroundImage.value = await Load3dUtils.uploadFile(file) backgroundImage.value = await Load3dUtils.uploadFile(file)
node.value.properties['Background Image'] = backgroundImage.value node.properties['Background Image'] = backgroundImage.value
} }
const handleUpdateFOV = (value: number) => { const handleUpdateFOV = (value: number) => {
fov.value = value fov.value = value
node.value.properties['FOV'] = fov.value node.properties['FOV'] = fov.value
} }
const materialMode = ref<MaterialMode>('original') const materialMode = ref<MaterialMode>('original')
@@ -156,19 +162,19 @@ const upDirection = ref<UpDirection>('original')
const handleUpdateUpDirection = (value: UpDirection) => { const handleUpdateUpDirection = (value: UpDirection) => {
upDirection.value = value upDirection.value = value
node.value.properties['Up Direction'] = value node.properties['Up Direction'] = value
} }
const handleUpdateMaterialMode = (value: MaterialMode) => { const handleUpdateMaterialMode = (value: MaterialMode) => {
materialMode.value = value materialMode.value = value
node.value.properties['Material Mode'] = value node.properties['Material Mode'] = value
} }
const handleBackgroundColorChange = (value: string) => { const handleBackgroundColorChange = (value: string) => {
backgroundColor.value = value backgroundColor.value = value
node.value.properties['Background Color'] = value node.properties['Background Color'] = value
} }
const togglePlay = (value: boolean) => { const togglePlay = (value: boolean) => {

View File

@@ -28,13 +28,14 @@ import { ref, watch } from 'vue'
import Load3DScene from '@/components/load3d/Load3DScene.vue' import Load3DScene from '@/components/load3d/Load3DScene.vue'
import { import {
CameraType, CameraType,
Load3DAnimationNodeType,
MaterialMode, MaterialMode,
UpDirection UpDirection
} from '@/extensions/core/load3d/interfaces' } from '@/extensions/core/load3d/interfaces'
const props = defineProps<{ const props = defineProps<{
node: any node: any
type: 'Load3DAnimation' | 'Preview3DAnimation' type: Load3DAnimationNodeType
backgroundColor: string backgroundColor: string
showGrid: boolean showGrid: boolean
lightIntensity: number lightIntensity: number

View File

@@ -1,5 +1,5 @@
<template> <template>
<div ref="container" class="w-full h-full relative"> <div ref="container" class="w-full h-full relative comfy-load-3d">
<LoadingOverlay ref="loadingOverlayRef" /> <LoadingOverlay ref="loadingOverlayRef" />
</div> </div>
</template> </template>
@@ -13,6 +13,8 @@ import Load3d from '@/extensions/core/load3d/Load3d'
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation' import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
import { import {
CameraType, CameraType,
Load3DAnimationNodeType,
Load3DNodeType,
MaterialMode, MaterialMode,
UpDirection UpDirection
} from '@/extensions/core/load3d/interfaces' } from '@/extensions/core/load3d/interfaces'
@@ -20,8 +22,8 @@ import { t } from '@/i18n'
import { useLoad3dService } from '@/services/load3dService' import { useLoad3dService } from '@/services/load3dService'
const props = defineProps<{ const props = defineProps<{
node: any node: LGraphNode
type: 'Load3D' | 'Load3DAnimation' | 'Preview3D' | 'Preview3DAnimation' type: Load3DNodeType | Load3DAnimationNodeType
backgroundColor: string backgroundColor: string
showGrid: boolean showGrid: boolean
lightIntensity: number lightIntensity: number

View File

@@ -1,65 +1,27 @@
// @ts-strict-ignore // @ts-strict-ignore
import { IWidget } from '@comfyorg/litegraph' import { IWidget } from '@comfyorg/litegraph'
import { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets' import { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
import PrimeVue from 'primevue/config' import { nextTick } from 'vue'
import { createApp, h, nextTick, render } from 'vue'
import Load3D from '@/components/load3d/Load3D.vue' import Load3D from '@/components/load3d/Load3D.vue'
import Load3DAnimation from '@/components/load3d/Load3DAnimation.vue' import Load3DAnimation from '@/components/load3d/Load3DAnimation.vue'
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation' import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import { app } from '@/scripts/app' import { app } from '@/scripts/app'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
import { useLoad3dService } from '@/services/load3dService' import { useLoad3dService } from '@/services/load3dService'
import { useToastStore } from '@/stores/toastStore' import { useToastStore } from '@/stores/toastStore'
import { generateUUID } from '@/utils/formatUtil'
app.registerExtension({ app.registerExtension({
name: 'Comfy.Load3D', name: 'Comfy.Load3D',
getCustomWidgets(app) { getCustomWidgets() {
return { return {
LOAD_3D(node, inputName) { LOAD_3D(node) {
node.addProperty('Camera Info', '')
const container = document.createElement('div')
container.classList.add('comfy-load-3d')
/* Hold off for now
const mountComponent = () => {
const vnode = h(Load3D, {
node: node,
type: 'Load3D'
})
render(vnode, container)
}
*/
let controlsApp = createApp(Load3D, {
node: node,
type: 'Load3D'
})
controlsApp.mount(container)
const origOnRemoved = node.onRemoved
node.onRemoved = function () {
/*
render(null, container)
container.remove()
*/
if (controlsApp) {
controlsApp.unmount()
controlsApp = null
}
origOnRemoved?.apply(this, [])
}
const fileInput = document.createElement('input') const fileInput = document.createElement('input')
fileInput.type = 'file' fileInput.type = 'file'
fileInput.accept = '.gltf,.glb,.obj,.mtl,.fbx,.stl' fileInput.accept = '.gltf,.glb,.obj,.mtl,.fbx,.stl'
@@ -112,11 +74,23 @@ app.registerExtension({
} }
}) })
//mountComponent() const inputSpec: CustomInputSpec = {
name: 'image',
return { type: 'Load3D'
widget: node.addDOMWidget(inputName, 'LOAD_3D', container)
} }
const widget = new ComponentWidgetImpl({
id: generateUUID(),
node,
name: inputSpec.name,
component: Load3D,
inputSpec,
options: {}
})
addWidget(node, widget)
return { widget }
} }
} }
}, },
@@ -130,8 +104,6 @@ app.registerExtension({
await nextTick() await nextTick()
const sceneWidget = node.widgets.find((w: IWidget) => w.name === 'image')
const load3d = useLoad3dService().getLoad3d(node) const load3d = useLoad3dService().getLoad3d(node)
const modelWidget = node.widgets.find( const modelWidget = node.widgets.find(
@@ -147,6 +119,8 @@ app.registerExtension({
config.configure('input', modelWidget, cameraState, width, height) config.configure('input', modelWidget, cameraState, width, height)
const sceneWidget = node.widgets.find((w: IWidget) => w.name === 'image')
sceneWidget.serializeValue = async () => { sceneWidget.serializeValue = async () => {
node.properties['Camera Info'] = load3d.getCameraState() node.properties['Camera Info'] = load3d.getCameraState()
@@ -173,52 +147,9 @@ app.registerExtension({
app.registerExtension({ app.registerExtension({
name: 'Comfy.Load3DAnimation', name: 'Comfy.Load3DAnimation',
getCustomWidgets(app) { getCustomWidgets() {
return { return {
LOAD_3D_ANIMATION(node, inputName) { LOAD_3D_ANIMATION(node) {
node.addProperty('Camera Info', '')
const container = document.createElement('div')
container.classList.add('comfy-load-3d-animation')
/*
const mountComponent = () => {
const vnode = h(Load3DAnimation, {
node: node,
type: 'Load3DAnimation'
})
render(vnode, container)
}
*/
let controlsApp = createApp(Load3DAnimation, {
node: node,
type: 'Load3DAnimation'
})
controlsApp.use(PrimeVue)
controlsApp.mount(container)
const origOnRemoved = node.onRemoved
node.onRemoved = function () {
/*
render(null, container)
container.remove()
*/
if (controlsApp) {
controlsApp.unmount()
controlsApp = null
}
origOnRemoved?.apply(this, [])
}
const fileInput = document.createElement('input') const fileInput = document.createElement('input')
fileInput.type = 'file' fileInput.type = 'file'
fileInput.accept = '.fbx,glb,gltf' fileInput.accept = '.fbx,glb,gltf'
@@ -269,11 +200,23 @@ app.registerExtension({
} }
}) })
//mountComponent() const inputSpec: CustomInputSpec = {
name: 'image',
return { type: 'Load3DAnimation'
widget: node.addDOMWidget(inputName, 'LOAD_3D_ANIMATION', container)
} }
const widget = new ComponentWidgetImpl({
id: generateUUID(),
node,
name: inputSpec.name,
component: Load3DAnimation,
inputSpec,
options: {}
})
addWidget(node, widget)
return { widget }
} }
} }
}, },
@@ -333,62 +276,32 @@ app.registerExtension({
name: 'Comfy.Preview3D', name: 'Comfy.Preview3D',
async beforeRegisterNodeDef(nodeType, nodeData) { async beforeRegisterNodeDef(nodeType, nodeData) {
if ( if ('Preview3D' === nodeData.name) {
// @ts-expect-error ComfyNode
['Preview3D'].includes(nodeType.comfyClass)
) {
// @ts-expect-error InputSpec is not typed correctly // @ts-expect-error InputSpec is not typed correctly
nodeData.input.required.image = ['PREVIEW_3D'] nodeData.input.required.image = ['PREVIEW_3D']
} }
}, },
getCustomWidgets(app) { getCustomWidgets() {
return { return {
PREVIEW_3D(node, inputName) { PREVIEW_3D(node) {
const container = document.createElement('div') const inputSpec: CustomInputSpec = {
name: 'image',
container.classList.add('comfy-preview-3d')
/*
const mountComponent = () => {
const vnode = h(Load3D, {
node: node,
type: 'Preview3D'
})
render(vnode, container)
}
*/
let controlsApp = createApp(Load3D, {
node: node,
type: 'Preview3D' type: 'Preview3D'
}
const widget = new ComponentWidgetImpl({
id: generateUUID(),
node,
name: inputSpec.name,
component: Load3D,
inputSpec,
options: {}
}) })
controlsApp.mount(container) addWidget(node, widget)
const origOnRemoved = node.onRemoved return { widget }
node.onRemoved = function () {
/*
render(null, container)
container.remove()
*/
if (controlsApp) {
controlsApp.unmount()
controlsApp = null
}
origOnRemoved?.apply(this, [])
}
//mountComponent()
return {
widget: node.addDOMWidget(inputName, 'PREVIEW_3D', container)
}
} }
} }
}, },
@@ -436,55 +349,32 @@ app.registerExtension({
name: 'Comfy.Preview3DAnimation', name: 'Comfy.Preview3DAnimation',
async beforeRegisterNodeDef(nodeType, nodeData) { async beforeRegisterNodeDef(nodeType, nodeData) {
if ( if ('Preview3DAnimation' === nodeData.name) {
// @ts-expect-error ComfyNode
['Preview3DAnimation'].includes(nodeType.comfyClass)
) {
// @ts-expect-error InputSpec is not typed correctly // @ts-expect-error InputSpec is not typed correctly
nodeData.input.required.image = ['PREVIEW_3D_ANIMATION'] nodeData.input.required.image = ['PREVIEW_3D_ANIMATION']
} }
}, },
getCustomWidgets(app) { getCustomWidgets() {
return { return {
PREVIEW_3D_ANIMATION(node, inputName) { PREVIEW_3D_ANIMATION(node) {
const container = document.createElement('div') const inputSpec: CustomInputSpec = {
name: 'image',
container.classList.add('comfy-preview-3d-animation')
let controlsApp = createApp(Load3DAnimation, {
node: node,
type: 'Preview3DAnimation' type: 'Preview3DAnimation'
}
const widget = new ComponentWidgetImpl({
id: generateUUID(),
node,
name: inputSpec.name,
component: Load3DAnimation,
inputSpec,
options: {}
}) })
controlsApp.use(PrimeVue) addWidget(node, widget)
controlsApp.mount(container) return { widget }
const origOnRemoved = node.onRemoved
node.onRemoved = function () {
/*
render(null, container)
container.remove()
*/
if (controlsApp) {
controlsApp.unmount()
controlsApp = null
}
origOnRemoved?.apply(this, [])
}
return {
widget: node.addDOMWidget(
inputName,
'PREVIEW_3D_ANIMATION',
container
)
}
} }
} }
}, },

View File

@@ -8,6 +8,10 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
export type Load3DNodeType = 'Load3D' | 'Preview3D'
export type Load3DAnimationNodeType = 'Load3DAnimation' | 'Preview3DAnimation'
export type MaterialMode = export type MaterialMode =
| 'original' | 'original'
| 'normal' | 'normal'