diff --git a/src/components/load3d/Load3DAnimationControls.vue b/src/components/load3d/Load3DAnimationControls.vue new file mode 100644 index 000000000..cdb280b5f --- /dev/null +++ b/src/components/load3d/Load3DAnimationControls.vue @@ -0,0 +1,117 @@ + + + diff --git a/src/components/load3d/Load3DControls.vue b/src/components/load3d/Load3DControls.vue new file mode 100644 index 000000000..a0eb99304 --- /dev/null +++ b/src/components/load3d/Load3DControls.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index 4110ae357..c091b1bea 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -280,7 +280,7 @@ app.registerExtension({ const [oldWidth, oldHeight] = node.size - node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 700)]) + node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 700)]) await nextTick() @@ -414,7 +414,7 @@ app.registerExtension({ const [oldWidth, oldHeight] = node.size - node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 550)]) + node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 550)]) await nextTick() diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts index 53fb6fd42..ffa560dc6 100644 --- a/src/extensions/core/load3d/Load3d.ts +++ b/src/extensions/core/load3d/Load3d.ts @@ -7,7 +7,9 @@ import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { STLLoader } from 'three/examples/jsm/loaders/STLLoader' +import { App, createApp } from 'vue' +import Load3DControls from '@/components/load3d/Load3DControls.vue' import { useToastStore } from '@/stores/toastStore' class Load3d { @@ -44,7 +46,9 @@ class Load3d { cameraSwitcherContainer: HTMLDivElement = {} as HTMLDivElement gridSwitcherContainer: HTMLDivElement = {} as HTMLDivElement node: LGraphNode = {} as LGraphNode - bgColorInput: HTMLInputElement = {} as HTMLInputElement + + protected controlsApp: App | null = null + protected controlsContainer: HTMLDivElement constructor(container: Element | HTMLElement) { this.scene = new THREE.Scene() @@ -124,17 +128,39 @@ class Load3d { this.createViewHelper(container) - this.createGridSwitcher(container) - - this.createCameraSwitcher(container) + this.controlsContainer = document.createElement('div') + this.controlsContainer.style.position = 'absolute' + this.controlsContainer.style.top = '0' + this.controlsContainer.style.left = '0' + this.controlsContainer.style.width = '100%' + this.controlsContainer.style.height = '100%' + this.controlsContainer.style.pointerEvents = 'none' + this.controlsContainer.style.zIndex = '1' + container.appendChild(this.controlsContainer) - this.createColorPicker(container) + this.mountControls() this.handleResize() this.startAnimation() } + protected mountControls() { + const controlsMount = document.createElement('div') + controlsMount.style.pointerEvents = 'auto' + this.controlsContainer.appendChild(controlsMount) + + this.controlsApp = createApp(Load3DControls, { + backgroundColor: '#282828', + showGrid: true, + onToggleCamera: () => this.toggleCamera(), + onToggleGrid: (show: boolean) => this.toggleGrid(show), + onUpdateBackgroundColor: (color: string) => this.setBackgroundColor(color) + }) + + this.controlsApp.mount(controlsMount) + } + setNode(node: LGraphNode) { this.node = node } @@ -184,145 +210,6 @@ class Load3d { this.viewHelper.center = this.controls.target } - createGridSwitcher(container: Element | HTMLElement) { - this.gridSwitcherContainer = document.createElement('div') - this.gridSwitcherContainer.style.position = 'absolute' - this.gridSwitcherContainer.style.top = '28px' // 修改这里,让按钮在相机按钮下方 - this.gridSwitcherContainer.style.left = '3px' // 与相机按钮左对齐 - this.gridSwitcherContainer.style.width = '20px' - this.gridSwitcherContainer.style.height = '20px' - this.gridSwitcherContainer.style.cursor = 'pointer' - this.gridSwitcherContainer.style.alignItems = 'center' - this.gridSwitcherContainer.style.justifyContent = 'center' - this.gridSwitcherContainer.style.transition = 'background-color 0.2s' - - const gridIcon = document.createElement('div') - gridIcon.innerHTML = ` - - - - - - - - ` - - const updateButtonState = () => { - if (this.gridHelper.visible) { - this.gridSwitcherContainer.style.backgroundColor = - 'rgba(255, 255, 255, 0.2)' - } else { - this.gridSwitcherContainer.style.backgroundColor = 'transparent' - } - } - - updateButtonState() - - this.gridSwitcherContainer.addEventListener('mouseenter', () => { - if (!this.gridHelper.visible) { - this.gridSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - } - }) - - this.gridSwitcherContainer.addEventListener('mouseleave', () => { - if (!this.gridHelper.visible) { - this.gridSwitcherContainer.style.backgroundColor = 'transparent' - } - }) - - this.gridSwitcherContainer.title = 'Toggle Grid' - - this.gridSwitcherContainer.addEventListener('click', (event) => { - event.stopPropagation() - this.toggleGrid(!this.gridHelper.visible) - updateButtonState() - }) - - this.gridSwitcherContainer.appendChild(gridIcon) - container.appendChild(this.gridSwitcherContainer) - } - - createCameraSwitcher(container: Element | HTMLElement) { - this.cameraSwitcherContainer = document.createElement('div') - this.cameraSwitcherContainer.style.position = 'absolute' - this.cameraSwitcherContainer.style.top = '3px' - this.cameraSwitcherContainer.style.left = '3px' - this.cameraSwitcherContainer.style.width = '20px' - this.cameraSwitcherContainer.style.height = '20px' - this.cameraSwitcherContainer.style.cursor = 'pointer' - this.cameraSwitcherContainer.style.alignItems = 'center' - this.cameraSwitcherContainer.style.justifyContent = 'center' - - const cameraIcon = document.createElement('div') - cameraIcon.innerHTML = ` - - - - - - ` - this.cameraSwitcherContainer.addEventListener('mouseenter', () => { - this.cameraSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - }) - - this.cameraSwitcherContainer.addEventListener('mouseleave', () => { - this.cameraSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.3)' - }) - - this.cameraSwitcherContainer.title = - 'Switch Camera (Perspective/Orthographic)' - - this.cameraSwitcherContainer.addEventListener('click', (event) => { - event.stopPropagation() - this.toggleCamera() - }) - - this.cameraSwitcherContainer.appendChild(cameraIcon) - - container.appendChild(this.cameraSwitcherContainer) - } - - createColorPicker(container: Element | HTMLElement) { - const colorPickerContainer = document.createElement('div') - colorPickerContainer.style.position = 'absolute' - colorPickerContainer.style.top = '53px' - colorPickerContainer.style.left = '3px' - colorPickerContainer.style.width = '20px' - colorPickerContainer.style.height = '20px' - colorPickerContainer.style.cursor = 'pointer' - colorPickerContainer.style.alignItems = 'center' - colorPickerContainer.style.justifyContent = 'center' - colorPickerContainer.title = 'Background Color' - - const colorInput = document.createElement('input') - colorInput.type = 'color' - colorInput.style.opacity = '0' - colorInput.style.position = 'absolute' - colorInput.style.width = '100%' - colorInput.style.height = '100%' - colorInput.style.cursor = 'pointer' - - const colorIcon = document.createElement('div') - colorIcon.innerHTML = ` - - - - - - ` - - colorInput.addEventListener('input', (event) => { - const color = (event.target as HTMLInputElement).value - this.setBackgroundColor(color) - this.storeNodeProperty('Background Color', color) - }) - - this.bgColorInput = colorInput - colorPickerContainer.appendChild(colorInput) - colorPickerContainer.appendChild(colorIcon) - container.appendChild(colorPickerContainer) - } - setFOV(fov: number) { if (this.activeCamera === this.perspectiveCamera) { this.perspectiveCamera.fov = fov @@ -350,15 +237,6 @@ class Load3d { zoom: number cameraType: 'perspective' | 'orthographic' }) { - if ( - this.activeCamera !== - (state.cameraType === 'perspective' - ? this.perspectiveCamera - : this.orthographicCamera) - ) { - //this.toggleCamera(state.cameraType) - } - this.activeCamera.position.copy(state.position) this.controls.target.copy(state.target) @@ -725,6 +603,10 @@ class Load3d { this.controls.dispose() this.viewHelper.dispose() this.renderer.dispose() + if (this.controlsApp) { + this.controlsApp.unmount() + this.controlsApp = null + } this.renderer.domElement.remove() this.scene.clear() } @@ -992,9 +874,11 @@ class Load3d { this.renderer.setClearColor(new THREE.Color(color)) this.renderer.render(this.scene, this.activeCamera) - if (this.bgColorInput) { - this.bgColorInput.value = color + if (this.controlsApp?._instance?.exposed) { + this.controlsApp._instance.exposed.backgroundColor.value = color } + + this.storeNodeProperty('Background Color', color) } } diff --git a/src/extensions/core/load3d/Load3dAnimation.ts b/src/extensions/core/load3d/Load3dAnimation.ts index 91f5ee0cd..cb8a713f4 100644 --- a/src/extensions/core/load3d/Load3dAnimation.ts +++ b/src/extensions/core/load3d/Load3dAnimation.ts @@ -1,5 +1,8 @@ +import PrimeVue from 'primevue/config' import * as THREE from 'three' +import { createApp } from 'vue' +import Load3DAnimationControls from '@/components/load3d/Load3DAnimationControls.vue' import Load3d from '@/extensions/core/load3d/Load3d' class Load3dAnimation extends Load3d { @@ -10,164 +13,47 @@ class Load3dAnimation extends Load3d { isAnimationPlaying: boolean = false animationSpeed: number = 1.0 - playPauseContainer: HTMLDivElement = {} as HTMLDivElement - animationSelect: HTMLSelectElement = {} as HTMLSelectElement - speedSelect: HTMLSelectElement = {} as HTMLSelectElement constructor(container: Element | HTMLElement) { super(container) - this.createPlayPauseButton(container) - this.createAnimationList(container) - this.createSpeedSelect(container) } - createAnimationList(container: Element | HTMLElement) { - this.animationSelect = document.createElement('select') - Object.assign(this.animationSelect.style, { - position: 'absolute', - top: '3px', - left: '50%', - transform: 'translateX(15px)', - width: '90px', - height: '20px', - backgroundColor: 'rgba(0, 0, 0, 0.3)', - color: 'white', - border: 'none', - borderRadius: '4px', - fontSize: '12px', - padding: '0 8px', - cursor: 'pointer', - display: 'none', - outline: 'none' - }) - - this.animationSelect.addEventListener('mouseenter', () => { - this.animationSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - }) - - this.animationSelect.addEventListener('mouseleave', () => { - this.animationSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.3)' - }) - - this.animationSelect.addEventListener('change', (event) => { - const select = event.target as HTMLSelectElement - this.updateSelectedAnimation(select.selectedIndex) - }) - - container.appendChild(this.animationSelect) + protected mountControls() { + const controlsMount = document.createElement('div') + controlsMount.style.pointerEvents = 'auto' + this.controlsContainer.appendChild(controlsMount) + + this.controlsApp = createApp(Load3DAnimationControls, { + backgroundColor: '#282828', + showGrid: true, + animations: [], + playing: false, + onToggleCamera: () => this.toggleCamera(), + onToggleGrid: (show: boolean) => this.toggleGrid(show), + onUpdateBackgroundColor: (color: string) => + this.setBackgroundColor(color), + onTogglePlay: (play: boolean) => this.toggleAnimation(play), + onSpeedChange: (speed: number) => this.setAnimationSpeed(speed), + onAnimationChange: (selectedAnimation: number) => + this.updateSelectedAnimation(selectedAnimation) + }) + + this.controlsApp.use(PrimeVue) + this.controlsApp.mount(controlsMount) } updateAnimationList() { - this.animationSelect.innerHTML = '' - this.animationClips.forEach((clip, index) => { - const option = document.createElement('option') - option.value = index.toString() - option.text = clip.name || `Animation ${index + 1}` - option.selected = index === this.selectedAnimationIndex - this.animationSelect.appendChild(option) - }) - } - - createPlayPauseButton(container: Element | HTMLElement) { - this.playPauseContainer = document.createElement('div') - this.playPauseContainer.style.position = 'absolute' - this.playPauseContainer.style.top = '3px' - this.playPauseContainer.style.left = '50%' - this.playPauseContainer.style.transform = 'translateX(-50%)' - this.playPauseContainer.style.width = '20px' - this.playPauseContainer.style.height = '20px' - this.playPauseContainer.style.cursor = 'pointer' - this.playPauseContainer.style.alignItems = 'center' - this.playPauseContainer.style.justifyContent = 'center' - - const updateButtonState = () => { - const icon = this.playPauseContainer.querySelector('svg') - if (icon) { - if (this.isAnimationPlaying) { - icon.innerHTML = ` - - ` - this.playPauseContainer.title = 'Pause Animation' - } else { - icon.innerHTML = ` - - ` - this.playPauseContainer.title = 'Play Animation' - } + if (this.controlsApp?._instance?.exposed) { + if (this.animationClips.length > 0) { + this.controlsApp._instance.exposed.animations.value = + this.animationClips.map((clip, index) => ({ + name: clip.name || `Animation ${index + 1}`, + index + })) + } else { + this.controlsApp._instance.exposed.animations.value = [] } } - - const playIcon = document.createElement('div') - playIcon.innerHTML = ` - - - - ` - - this.playPauseContainer.addEventListener('mouseenter', () => { - this.playPauseContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - }) - - this.playPauseContainer.addEventListener('mouseleave', () => { - this.playPauseContainer.style.backgroundColor = 'transparent' - }) - - this.playPauseContainer.addEventListener('click', (event) => { - event.stopPropagation() - this.toggleAnimation() - updateButtonState() - }) - - this.playPauseContainer.appendChild(playIcon) - container.appendChild(this.playPauseContainer) - - this.playPauseContainer.style.display = 'none' - } - - createSpeedSelect(container: Element | HTMLElement) { - this.speedSelect = document.createElement('select') - Object.assign(this.speedSelect.style, { - position: 'absolute', - top: '3px', - left: '50%', - transform: 'translateX(-75px)', - width: '60px', - height: '20px', - backgroundColor: 'rgba(0, 0, 0, 0.3)', - color: 'white', - border: 'none', - borderRadius: '4px', - fontSize: '12px', - padding: '0 8px', - cursor: 'pointer', - display: 'none', - outline: 'none' - }) - - const speeds = [0.1, 0.5, 1, 1.5, 2] - speeds.forEach((speed) => { - const option = document.createElement('option') - option.value = speed.toString() - option.text = `${speed}x` - option.selected = speed === 1 - this.speedSelect.appendChild(option) - }) - - this.speedSelect.addEventListener('mouseenter', () => { - this.speedSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.5)' - }) - - this.speedSelect.addEventListener('mouseleave', () => { - this.speedSelect.style.backgroundColor = 'rgba(0, 0, 0, 0.3)' - }) - - this.speedSelect.addEventListener('change', (event) => { - const select = event.target as HTMLSelectElement - const newSpeed = parseFloat(select.value) - this.setAnimationSpeed(newSpeed) - }) - - container.appendChild(this.speedSelect) } protected async setupModel(model: THREE.Object3D) { @@ -200,22 +86,7 @@ class Load3dAnimation extends Load3d { } } - if (this.animationClips.length > 0) { - this.playPauseContainer.style.display = 'block' - } else { - this.playPauseContainer.style.display = 'none' - } - - if (this.animationClips.length > 0) { - this.playPauseContainer.style.display = 'block' - this.animationSelect.style.display = 'block' - this.speedSelect.style.display = 'block' - this.updateAnimationList() - } else { - this.playPauseContainer.style.display = 'none' - this.animationSelect.style.display = 'none' - this.speedSelect.style.display = 'none' - } + this.updateAnimationList() } setAnimationSpeed(speed: number) { @@ -261,7 +132,9 @@ class Load3dAnimation extends Load3d { this.animationActions = [action] - this.updateAnimationList() + if (this.controlsApp?._instance?.exposed) { + this.controlsApp._instance.exposed.selectedAnimation.value = index + } } clearModel() { @@ -277,23 +150,12 @@ class Load3dAnimation extends Load3d { this.isAnimationPlaying = false this.animationSpeed = 1.0 - super.clearModel() - - if (this.animationSelect) { - this.animationSelect.style.display = 'none' - this.animationSelect.innerHTML = '' + if (this.controlsApp?._instance?.exposed) { + this.controlsApp._instance.exposed.animations.value = [] + this.controlsApp._instance.exposed.selectedAnimation.value = 0 } - if (this.speedSelect) { - this.speedSelect.style.display = 'none' - this.speedSelect.value = '1' - } - } - - getAnimationNames(): string[] { - return this.animationClips.map((clip, index) => { - return clip.name || `Animation ${index + 1}` - }) + super.clearModel() } toggleAnimation(play?: boolean) { @@ -304,15 +166,8 @@ class Load3dAnimation extends Load3d { this.isAnimationPlaying = play ?? !this.isAnimationPlaying - const icon = this.playPauseContainer.querySelector('svg') - if (icon) { - if (this.isAnimationPlaying) { - icon.innerHTML = '' - this.playPauseContainer.title = 'Pause Animation' - } else { - icon.innerHTML = '' - this.playPauseContainer.title = 'Play Animation' - } + if (this.controlsApp?._instance?.exposed) { + this.controlsApp._instance.exposed.playing.value = this.isAnimationPlaying } this.animationActions.forEach((action) => {