Files
ComfyUI_frontend/src/components/load3d/Load3D.vue
Terry Jia afa10f7a1e [refactor] refactor load3d (#5765)
Summary

Fully Refactored the Load3D module to improve architecture and
maintainability by consolidating functionality into a
centralized composable pattern and simplifying component structure. and
support VueNodes system

  Changes

- Architecture: Introduced new useLoad3d composable to centralize 3D
loading logic and state
  management
- Component Simplification: Removed redundant components
(Load3DAnimation.vue, Load3DAnimationScene.vue,
  PreviewManager.ts) 
- Support VueNodes
- improve config store
- remove lineart output due Animation doesnot support it, may add it
back later
- remove Preview screen and keep scene in fixed ratio in load3d (not
affect preview3d)
- improve record video feature which will already record video by same
ratio as scene
Need BE change https://github.com/comfyanonymous/ComfyUI/pull/10025


https://github.com/user-attachments/assets/9e038729-84a0-45ad-b0f2-11c57d7e0c9a



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5765-refactor-refactor-load3d-2796d73d365081728297cc486e2e9052)
by [Unito](https://www.unito.io)
2025-10-31 16:19:35 -04:00

142 lines
4.0 KiB
Vue

<template>
<div
class="widget-expands relative h-full w-full"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@pointerdown.stop
@pointermove.stop
@pointerup.stop
>
<Load3DScene
v-if="node"
ref="load3DSceneRef"
:initialize-load3d="initializeLoad3d"
:cleanup="cleanup"
:loading="loading"
:loading-message="loadingMessage"
:on-model-drop="isPreview ? undefined : handleModelDrop"
:is-preview="isPreview"
/>
<div class="pointer-events-none absolute top-0 left-0 h-full w-full">
<Load3DControls
v-model:scene-config="sceneConfig"
v-model:model-config="modelConfig"
v-model:camera-config="cameraConfig"
v-model:light-config="lightConfig"
@update-background-image="handleBackgroundImageUpdate"
@export-model="handleExportModel"
/>
<AnimationControls
v-if="animations && animations.length > 0"
v-model:animations="animations"
v-model:playing="playing"
v-model:selected-speed="selectedSpeed"
v-model:selected-animation="selectedAnimation"
/>
</div>
<div
v-if="enable3DViewer && node"
class="pointer-events-auto absolute top-12 right-2 z-20"
>
<ViewerControls :node="node as LGraphNode" />
</div>
<div
v-if="!isPreview"
class="pointer-events-auto absolute right-2 z-20"
:class="{
'top-12': !enable3DViewer,
'top-24': enable3DViewer
}"
>
<RecordingControls
v-model:is-recording="isRecording"
v-model:has-recording="hasRecording"
v-model:recording-duration="recordingDuration"
@start-recording="handleStartRecording"
@stop-recording="handleStopRecording"
@export-recording="handleExportRecording"
@clear-recording="handleClearRecording"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import type { Ref } from 'vue'
import Load3DControls from '@/components/load3d/Load3DControls.vue'
import Load3DScene from '@/components/load3d/Load3DScene.vue'
import AnimationControls from '@/components/load3d/controls/AnimationControls.vue'
import RecordingControls from '@/components/load3d/controls/RecordingControls.vue'
import ViewerControls from '@/components/load3d/controls/ViewerControls.vue'
import { useLoad3d } from '@/composables/useLoad3d'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
import { app } from '@/scripts/app'
import type { ComponentWidget } from '@/scripts/domWidget'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
const props = defineProps<{
widget: ComponentWidget<string[]> | SimplifiedWidget
nodeId?: NodeId
}>()
function isComponentWidget(
widget: ComponentWidget<string[]> | SimplifiedWidget
): widget is ComponentWidget<string[]> {
return 'node' in widget && widget.node !== undefined
}
const node = ref<LGraphNode | null>(null)
if (isComponentWidget(props.widget)) {
node.value = props.widget.node
} else if (props.nodeId) {
onMounted(() => {
node.value = app.rootGraph?.getNodeById(props.nodeId!) || null
})
}
const load3DSceneRef = ref<InstanceType<typeof Load3DScene> | null>(null)
const {
// configs
sceneConfig,
modelConfig,
cameraConfig,
lightConfig,
// other state
isRecording,
isPreview,
hasRecording,
recordingDuration,
animations,
playing,
selectedSpeed,
selectedAnimation,
loading,
loadingMessage,
// Methods
initializeLoad3d,
handleMouseEnter,
handleMouseLeave,
handleStartRecording,
handleStopRecording,
handleExportRecording,
handleClearRecording,
handleBackgroundImageUpdate,
handleExportModel,
handleModelDrop,
cleanup
} = useLoad3d(node as Ref<LGraphNode | null>)
const enable3DViewer = computed(() =>
useSettingStore().get('Comfy.Load3D.3DViewerEnable')
)
</script>