[3d] fully convert load 3d nodes into vue (#2590)

This commit is contained in:
Terry Jia
2025-02-16 20:15:49 -05:00
committed by GitHub
parent 9ebb5b2a0c
commit b2375a150c
11 changed files with 877 additions and 373 deletions

View File

@@ -0,0 +1,140 @@
<template>
<div class="relative w-full h-full">
<Load3DScene
:node="node"
:type="type"
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:lightIntensity="lightIntensity"
:fov="fov"
:cameraType="cameraType"
:showPreview="showPreview"
@materialModeChange="listenMaterialModeChange"
@backgroundColorChange="listenBackgroundColorChange"
@lightIntensityChange="listenLightIntensityChange"
@fovChange="listenFOVChange"
@cameraTypeChange="listenCameraTypeChange"
@showGridChange="listenShowGridChange"
@showPreviewChange="listenShowPreviewChange"
/>
<Load3DControls
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:showPreview="showPreview"
:lightIntensity="lightIntensity"
:showLightIntensityButton="showLightIntensityButton"
:fov="fov"
:showFOVButton="showFOVButton"
:showPreviewButton="showPreviewButton"
:cameraType="cameraType"
@switchCamera="switchCamera"
@toggleGrid="toggleGrid"
@updateBackgroundColor="handleBackgroundColorChange"
@updateLightIntensity="handleUpdateLightIntensity"
@togglePreview="togglePreview"
@updateFOV="handleUpdateFOV"
/>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import Load3DControls from '@/components/load3d/Load3DControls.vue'
import Load3DScene from '@/components/load3d/Load3DScene.vue'
const props = defineProps<{
node: any
type: 'Load3D' | 'Preview3D'
}>()
const node = ref(props.node)
const backgroundColor = ref('#000000')
const showGrid = ref(true)
const showPreview = ref(false)
const lightIntensity = ref(5)
const showLightIntensityButton = ref(true)
const fov = ref(75)
const showFOVButton = ref(true)
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
const showPreviewButton = computed(() => {
return !props.type.includes('Preview')
})
const switchCamera = () => {
cameraType.value =
cameraType.value === 'perspective' ? 'orthographic' : 'perspective'
showFOVButton.value = cameraType.value === 'perspective'
node.value.properties['Camera Type'] = cameraType.value
}
const togglePreview = (value: boolean) => {
showPreview.value = value
node.value.properties['Show Preview'] = showPreview.value
}
const toggleGrid = (value: boolean) => {
showGrid.value = value
node.value.properties['Show Grid'] = showGrid.value
}
const handleUpdateLightIntensity = (value: number) => {
lightIntensity.value = value
node.value.properties['Light Intensity'] = lightIntensity.value
}
const handleUpdateFOV = (value: number) => {
fov.value = value
node.value.properties['FOV'] = fov.value
}
const materialMode = ref<'original' | 'normal' | 'wireframe' | 'depth'>(
'original'
)
const handleBackgroundColorChange = (value: string) => {
backgroundColor.value = value
node.value.properties['Background Color'] = value
}
const listenMaterialModeChange = (
mode: 'original' | 'normal' | 'wireframe' | 'depth'
) => {
materialMode.value = mode
showLightIntensityButton.value = mode === 'original'
}
const listenBackgroundColorChange = (value: string) => {
backgroundColor.value = value
}
const listenLightIntensityChange = (value: number) => {
lightIntensity.value = value
}
const listenFOVChange = (value: number) => {
fov.value = value
}
const listenCameraTypeChange = (value: 'perspective' | 'orthographic') => {
cameraType.value = value
showFOVButton.value = cameraType.value === 'perspective'
}
const listenShowGridChange = (value: boolean) => {
showGrid.value = value
}
const listenShowPreviewChange = (value: boolean) => {
showPreview.value = value
}
</script>

View File

@@ -0,0 +1,180 @@
<template>
<div class="relative w-full h-full">
<Load3DAnimationScene
:node="node"
:type="type"
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:lightIntensity="lightIntensity"
:fov="fov"
:cameraType="cameraType"
:showPreview="showPreview"
:materialMode="materialMode"
:showFOVButton="showFOVButton"
:showLightIntensityButton="showLightIntensityButton"
:playing="playing"
:selectedSpeed="selectedSpeed"
:selectedAnimation="selectedAnimation"
@materialModeChange="listenMaterialModeChange"
@backgroundColorChange="listenBackgroundColorChange"
@lightIntensityChange="listenLightIntensityChange"
@fovChange="listenFOVChange"
@cameraTypeChange="listenCameraTypeChange"
@showGridChange="listenShowGridChange"
@showPreviewChange="listenShowPreviewChange"
@animationListChange="animationListChange"
/>
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
<Load3DControls
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:showPreview="showPreview"
:lightIntensity="lightIntensity"
:showLightIntensityButton="showLightIntensityButton"
:fov="fov"
:showFOVButton="showFOVButton"
:showPreviewButton="showPreviewButton"
:cameraType="cameraType"
@switchCamera="switchCamera"
@toggleGrid="toggleGrid"
@updateBackgroundColor="handleBackgroundColorChange"
@updateLightIntensity="handleUpdateLightIntensity"
@togglePreview="togglePreview"
@updateFOV="handleUpdateFOV"
/>
<Load3DAnimationControls
:animations="animations"
:playing="playing"
@togglePlay="togglePlay"
@speedChange="speedChange"
@animationChange="animationChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import Load3DAnimationControls from '@/components/load3d/Load3DAnimationControls.vue'
import Load3DAnimationScene from '@/components/load3d/Load3DAnimationScene.vue'
import Load3DControls from '@/components/load3d/Load3DControls.vue'
import type { AnimationItem } from '@/extensions/core/load3d/Load3dAnimation'
const props = defineProps<{
node: any
type: 'Load3DAnimation' | 'Preview3DAnimation'
}>()
const node = ref(props.node)
const backgroundColor = ref('#000000')
const showGrid = ref(true)
const showPreview = ref(false)
const lightIntensity = ref(5)
const showLightIntensityButton = ref(true)
const fov = ref(75)
const showFOVButton = ref(true)
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
const animations = ref<AnimationItem[]>([])
const playing = ref(false)
const selectedSpeed = ref(1)
const selectedAnimation = ref(0)
const showPreviewButton = computed(() => {
return !props.type.includes('Preview')
})
const switchCamera = () => {
cameraType.value =
cameraType.value === 'perspective' ? 'orthographic' : 'perspective'
showFOVButton.value = cameraType.value === 'perspective'
node.value.properties['Camera Type'] = cameraType.value
}
const togglePreview = (value: boolean) => {
showPreview.value = value
node.value.properties['Show Preview'] = showPreview.value
}
const toggleGrid = (value: boolean) => {
showGrid.value = value
node.value.properties['Show Grid'] = showGrid.value
}
const handleUpdateLightIntensity = (value: number) => {
lightIntensity.value = value
node.value.properties['Light Intensity'] = lightIntensity.value
}
const handleUpdateFOV = (value: number) => {
fov.value = value
node.value.properties['FOV'] = fov.value
}
const materialMode = ref<'original' | 'normal' | 'wireframe' | 'depth'>(
'original'
)
const handleBackgroundColorChange = (value: string) => {
backgroundColor.value = value
node.value.properties['Background Color'] = value
}
const togglePlay = (value: boolean) => {
playing.value = value
}
const speedChange = (value: number) => {
selectedSpeed.value = value
}
const animationChange = (value: number) => {
selectedAnimation.value = value
}
const animationListChange = (value: any) => {
animations.value = value
}
const listenMaterialModeChange = (
mode: 'original' | 'normal' | 'wireframe' | 'depth'
) => {
materialMode.value = mode
showLightIntensityButton.value = mode === 'original'
}
const listenBackgroundColorChange = (value: string) => {
backgroundColor.value = value
}
const listenLightIntensityChange = (value: number) => {
lightIntensity.value = value
}
const listenFOVChange = (value: number) => {
fov.value = value
}
const listenCameraTypeChange = (value: 'perspective' | 'orthographic') => {
cameraType.value = value
showFOVButton.value = cameraType.value === 'perspective'
}
const listenShowGridChange = (value: boolean) => {
showGrid.value = value
}
const listenShowPreviewChange = (value: boolean) => {
showPreview.value = value
}
</script>

View File

@@ -1,55 +1,31 @@
<template>
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
<Load3DControls
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:showPreview="showPreview"
:lightIntensity="lightIntensity"
:showLightIntensityButton="showLightIntensityButton"
:fov="fov"
:showFOVButton="showFOVButton"
:showPreviewButton="showPreviewButton"
@toggleCamera="onToggleCamera"
@toggleGrid="onToggleGrid"
@togglePreview="onTogglePreview"
@updateBackgroundColor="onUpdateBackgroundColor"
@updateLightIntensity="onUpdateLightIntensity"
@updateFOV="onUpdateFOV"
ref="load3dControlsRef"
<div
v-if="animations && animations.length > 0"
class="absolute top-0 left-0 w-full flex justify-center pt-2 gap-2 items-center pointer-events-auto z-10"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<i
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-white text-lg']"
></i>
</Button>
<Select
v-model="selectedSpeed"
:options="speedOptions"
optionLabel="name"
optionValue="value"
@change="speedChange"
class="w-24"
/>
<div
v-if="animations && animations.length > 0"
class="absolute top-0 left-0 w-full flex justify-center pt-2 gap-2 items-center pointer-events-auto z-10"
>
<Button class="p-button-rounded p-button-text" @click="togglePlay">
<i
:class="[
'pi',
playing ? 'pi-pause' : 'pi-play',
'text-white text-lg'
]"
></i>
</Button>
<Select
v-model="selectedSpeed"
:options="speedOptions"
optionLabel="name"
optionValue="value"
@change="speedChange"
class="w-24"
/>
<Select
v-model="selectedAnimation"
:options="animations"
optionLabel="name"
optionValue="index"
@change="animationChange"
class="w-32"
/>
</div>
<Select
v-model="selectedAnimation"
:options="animations"
optionLabel="name"
optionValue="index"
@change="animationChange"
class="w-32"
/>
</div>
</template>
@@ -58,46 +34,21 @@ import Button from 'primevue/button'
import Select from 'primevue/select'
import { ref, watch } from 'vue'
import Load3DControls from '@/components/load3d/Load3DControls.vue'
const props = defineProps<{
animations: Array<{ name: string; index: number }>
playing: boolean
backgroundColor: string
showGrid: boolean
showPreview: boolean
lightIntensity: number
showLightIntensityButton: boolean
fov: number
showFOVButton: boolean
showPreviewButton: boolean
}>()
const emit = defineEmits<{
(e: 'toggleCamera'): void
(e: 'toggleGrid', value: boolean): void
(e: 'togglePreview', value: boolean): void
(e: 'updateBackgroundColor', color: string): void
(e: 'togglePlay', value: boolean): void
(e: 'speedChange', value: number): void
(e: 'animationChange', value: number): void
(e: 'updateLightIntensity', value: number): void
(e: 'updateFOV', value: number): void
}>()
const animations = ref(props.animations)
const playing = ref(props.playing)
const selectedSpeed = ref(1)
const selectedAnimation = ref(0)
const backgroundColor = ref(props.backgroundColor)
const showGrid = ref(props.showGrid)
const showPreview = ref(props.showPreview)
const lightIntensity = ref(props.lightIntensity)
const showLightIntensityButton = ref(props.showLightIntensityButton)
const fov = ref(props.fov)
const showFOVButton = ref(props.showFOVButton)
const showPreviewButton = ref(props.showPreviewButton)
const load3dControlsRef = ref(null)
const speedOptions = [
{ name: '0.1x', value: 0.1 },
@@ -107,42 +58,16 @@ const speedOptions = [
{ name: '2x', value: 2 }
]
watch(backgroundColor, (newValue) => {
load3dControlsRef.value.backgroundColor = newValue
})
watch(showLightIntensityButton, (newValue) => {
load3dControlsRef.value.showLightIntensityButton = newValue
})
watch(showFOVButton, (newValue) => {
load3dControlsRef.value.showFOVButton = newValue
})
watch(showPreviewButton, (newValue) => {
load3dControlsRef.value.showPreviewButton = newValue
})
const onToggleCamera = () => {
emit('toggleCamera')
}
const onToggleGrid = (value: boolean) => emit('toggleGrid', value)
const onTogglePreview = (value: boolean) => {
emit('togglePreview', value)
}
const onUpdateBackgroundColor = (color: string) =>
emit('updateBackgroundColor', color)
const onUpdateLightIntensity = (lightIntensity: number) => {
emit('updateLightIntensity', lightIntensity)
}
const onUpdateFOV = (fov: number) => {
emit('updateFOV', fov)
}
watch(
() => props.animations,
(newVal) => {
animations.value = newVal
}
)
const togglePlay = () => {
playing.value = !playing.value
emit('togglePlay', playing.value)
}
@@ -153,16 +78,4 @@ const speedChange = () => {
const animationChange = () => {
emit('animationChange', selectedAnimation.value)
}
defineExpose({
animations,
selectedAnimation,
playing,
backgroundColor,
showGrid,
lightIntensity,
showLightIntensityButton,
fov,
showFOVButton
})
</script>

View File

@@ -0,0 +1,162 @@
<template>
<Load3DScene
:node="node"
:type="type"
:backgroundColor="backgroundColor"
:showGrid="showGrid"
:lightIntensity="lightIntensity"
:fov="fov"
:cameraType="cameraType"
:showPreview="showPreview"
:extraListeners="animationListeners"
@materialModeChange="listenMaterialModeChange"
@backgroundColorChange="listenBackgroundColorChange"
@lightIntensityChange="listenLightIntensityChange"
@fovChange="listenFOVChange"
@cameraTypeChange="listenCameraTypeChange"
@showGridChange="listenShowGridChange"
@showPreviewChange="listenShowPreviewChange"
ref="load3DSceneRef"
/>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import Load3DScene from '@/components/load3d/Load3DScene.vue'
const props = defineProps<{
node: any
type: 'Load3DAnimation' | 'Preview3DAnimation'
backgroundColor: string
showGrid: boolean
lightIntensity: number
fov: number
cameraType: 'perspective' | 'orthographic'
showPreview: boolean
materialMode: 'original' | 'normal' | 'wireframe' | 'depth'
showFOVButton: boolean
showLightIntensityButton: boolean
playing: boolean
selectedSpeed: number
selectedAnimation: number
}>()
const node = ref(props.node)
const backgroundColor = ref(props.backgroundColor)
const showPreview = ref(props.showPreview)
const fov = ref(props.fov)
const lightIntensity = ref(props.lightIntensity)
const cameraType = ref(props.cameraType)
const showGrid = ref(props.showGrid)
const materialMode = ref(props.materialMode)
const showFOVButton = ref(props.showFOVButton)
const showLightIntensityButton = ref(props.showLightIntensityButton)
const load3DSceneRef = ref(null)
watch(
() => props.cameraType,
(newValue) => {
cameraType.value = newValue
}
)
watch(
() => props.showGrid,
(newValue) => {
showGrid.value = newValue
}
)
watch(
() => props.backgroundColor,
(newValue) => {
backgroundColor.value = newValue
}
)
watch(
() => props.lightIntensity,
(newValue) => {
lightIntensity.value = newValue
}
)
watch(
() => props.fov,
(newValue) => {
fov.value = newValue
}
)
watch(
() => props.showPreview,
(newValue) => {
showPreview.value = newValue
}
)
watch(
() => props.playing,
(newValue) => {
load3DSceneRef.value.load3d.toggleAnimation(newValue)
}
)
watch(
() => props.selectedSpeed,
(newValue) => {
load3DSceneRef.value.load3d.setAnimationSpeed(newValue)
}
)
watch(
() => props.selectedAnimation,
(newValue) => {
load3DSceneRef.value.load3d.updateSelectedAnimation(newValue)
}
)
const emit = defineEmits<{
(e: 'animationListChange', animationList: string): void
}>()
const listenMaterialModeChange = (
mode: 'original' | 'normal' | 'wireframe' | 'depth'
) => {
materialMode.value = mode
showLightIntensityButton.value = mode === 'original'
}
const listenBackgroundColorChange = (value: string) => {
backgroundColor.value = value
}
const listenLightIntensityChange = (value: number) => {
lightIntensity.value = value
}
const listenFOVChange = (value: number) => {
fov.value = value
}
const listenCameraTypeChange = (value: 'perspective' | 'orthographic') => {
cameraType.value = value
showFOVButton.value = cameraType.value === 'perspective'
}
const listenShowGridChange = (value: boolean) => {
showGrid.value = value
}
const listenShowPreviewChange = (value: boolean) => {
showPreview.value = value
}
const animationListeners = {
animationListChange: (newValue: any) => {
emit('animationListChange', newValue)
}
}
</script>

View File

@@ -2,9 +2,9 @@
<div
class="absolute top-2 left-2 flex flex-col gap-2 pointer-events-auto z-20"
>
<Button class="p-button-rounded p-button-text" @click="toggleCamera">
<Button class="p-button-rounded p-button-text" @click="switchCamera">
<i
class="pi pi-camera text-white text-lg"
:class="['pi', getCameraIcon, 'text-white text-lg']"
v-tooltip.right="{ value: t('load3d.switchCamera'), showDelay: 300 }"
></i>
</Button>
@@ -13,9 +13,11 @@
class="p-button-rounded p-button-text"
:class="{ 'p-button-outlined': showGrid }"
@click="toggleGrid"
v-tooltip.right="{ value: t('load3d.showGrid'), showDelay: 300 }"
>
<i class="pi pi-table text-white text-lg"></i>
<i
class="pi pi-table text-white text-lg"
v-tooltip.right="{ value: t('load3d.showGrid'), showDelay: 300 }"
></i>
</Button>
<Button class="p-button-rounded p-button-text" @click="openColorPicker">
@@ -34,7 +36,7 @@
/>
</Button>
<div class="relative" v-if="showLightIntensityButton">
<div class="relative show-light-intensity" v-if="showLightIntensityButton">
<Button
class="p-button-rounded p-button-text"
@click="toggleLightIntensity"
@@ -63,7 +65,7 @@
</div>
</div>
<div class="relative" v-if="showFOVButton">
<div class="relative show-fov" v-if="showFOVButton">
<Button class="p-button-rounded p-button-text" @click="toggleFOV">
<i
class="pi pi-expand text-white text-lg"
@@ -102,12 +104,15 @@
</template>
<script setup lang="ts">
import { Tooltip } from 'primevue'
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { t } from '@/i18n'
const vTooltip = Tooltip
const props = defineProps<{
backgroundColor: string
showGrid: boolean
@@ -117,10 +122,11 @@ const props = defineProps<{
fov: number
showFOVButton: boolean
showPreviewButton: boolean
cameraType: 'perspective' | 'orthographic'
}>()
const emit = defineEmits<{
(e: 'toggleCamera'): void
(e: 'switchCamera'): void
(e: 'toggleGrid', value: boolean): void
(e: 'updateBackgroundColor', color: string): void
(e: 'updateLightIntensity', value: number): void
@@ -140,8 +146,8 @@ const showFOV = ref(false)
const showFOVButton = ref(props.showFOVButton)
const showPreviewButton = ref(props.showPreviewButton)
const toggleCamera = () => {
emit('toggleCamera')
const switchCamera = () => {
emit('switchCamera')
}
const toggleGrid = () => {
@@ -181,12 +187,64 @@ const updateFOV = () => {
const closeSlider = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.relative')) {
showLightIntensity.value = false
if (!target.closest('.show-fov')) {
showFOV.value = false
}
if (!target.closest('.show-light-intensity')) {
showLightIntensity.value = false
}
}
watch(
() => props.backgroundColor,
(newValue) => {
backgroundColor.value = newValue
}
)
watch(
() => props.fov,
(newValue) => {
fov.value = newValue
}
)
watch(
() => props.lightIntensity,
(newValue) => {
lightIntensity.value = newValue
}
)
watch(
() => props.showFOVButton,
(newValue) => {
showFOVButton.value = newValue
}
)
watch(
() => props.showLightIntensityButton,
(newValue) => {
showLightIntensityButton.value = newValue
}
)
watch(
() => props.showPreviewButton,
(newValue) => {
showPreviewButton.value = newValue
}
)
watch(
() => props.showPreview,
(newValue) => {
showPreview.value = newValue
}
)
onMounted(() => {
document.addEventListener('click', closeSlider)
})
@@ -195,13 +253,7 @@ onUnmounted(() => {
document.removeEventListener('click', closeSlider)
})
defineExpose({
backgroundColor,
showGrid,
lightIntensity,
showLightIntensityButton,
fov,
showFOVButton,
showPreviewButton
const getCameraIcon = computed(() => {
return props.cameraType === 'perspective' ? 'pi-camera' : 'pi-th-large'
})
</script>

View File

@@ -0,0 +1,96 @@
<template>
<div ref="container" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { LGraphNode } from '@comfyorg/litegraph'
import { onMounted, onUnmounted, ref, toRaw, watchEffect } from 'vue'
import Load3d from '@/extensions/core/load3d/Load3d'
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
import { useLoad3dService } from '@/services/load3dService'
const props = defineProps<{
node: any
type: 'Load3D' | 'Load3DAnimation' | 'Preview3D' | 'Preview3DAnimation'
backgroundColor: string
showGrid: boolean
lightIntensity: number
fov: number
cameraType: 'perspective' | 'orthographic'
showPreview: boolean
extraListeners?: Record<string, (value: any) => void>
}>()
const container = ref<HTMLElement | null>(null)
const node = ref(props.node)
const load3d = ref<Load3d | Load3dAnimation | null>(null)
const eventConfig = {
materialModeChange: (value: string) => emit('materialModeChange', value),
backgroundColorChange: (value: string) =>
emit('backgroundColorChange', value),
lightIntensityChange: (value: number) => emit('lightIntensityChange', value),
fovChange: (value: number) => emit('fovChange', value),
cameraTypeChange: (value: string) => emit('cameraTypeChange', value),
showGridChange: (value: boolean) => emit('showGridChange', value),
showPreviewChange: (value: boolean) => emit('showPreviewChange', value)
} as const
watchEffect(() => {
if (load3d.value) {
const rawLoad3d = toRaw(load3d.value)
rawLoad3d.setBackgroundColor(props.backgroundColor)
rawLoad3d.toggleGrid(props.showGrid)
rawLoad3d.setLightIntensity(props.lightIntensity)
rawLoad3d.setFOV(props.fov)
rawLoad3d.toggleCamera(props.cameraType)
rawLoad3d.togglePreview(props.showPreview)
}
})
const emit = defineEmits<{
(e: 'materialModeChange', materialMode: string): void
(e: 'backgroundColorChange', color: string): void
(e: 'lightIntensityChange', lightIntensity: number): void
(e: 'fovChange', fov: number): void
(e: 'cameraTypeChange', cameraType: string): void
(e: 'showGridChange', showGrid: boolean): void
(e: 'showPreviewChange', showPreview: boolean): void
}>()
const handleEvents = (action: 'add' | 'remove') => {
if (!load3d.value) return
Object.entries(eventConfig).forEach(([event, handler]) => {
const method = `${action}EventListener` as const
load3d.value?.[method](event, handler)
})
if (props.extraListeners) {
Object.entries(props.extraListeners).forEach(([event, handler]) => {
const method = `${action}EventListener` as const
load3d.value?.[method](event, handler)
})
}
}
onMounted(() => {
load3d.value = useLoad3dService().registerLoad3d(
node.value as LGraphNode,
container.value,
props.type
)
handleEvents('add')
})
onUnmounted(() => {
handleEvents('remove')
useLoad3dService().removeLoad3d(node.value as LGraphNode)
})
defineExpose({
load3d
})
</script>