Initial commit: 3D Viewer application
Features: - Vue 3 frontend with Three.js/Online3DViewer - Node.js API with PostgreSQL and Redis - Python worker for model conversion - Docker Compose for deployment - ViewCube navigation with drag rotation and 90° snap - Cross-section, exploded view, and render settings - Parts tree with visibility controls 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
73
frontend/src/components/viewer/ViewCube.vue
Normal file
73
frontend/src/components/viewer/ViewCube.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useViewerStore } from '@/stores/viewer'
|
||||
import {
|
||||
getViewCubeService,
|
||||
resetViewCubeService,
|
||||
type ViewDirection,
|
||||
} from '@/services/viewCubeService'
|
||||
|
||||
const viewerStore = useViewerStore()
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
let renderLoopId: number | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (containerRef.value) {
|
||||
const service = getViewCubeService()
|
||||
service.initialize(
|
||||
containerRef.value,
|
||||
(direction: ViewDirection) => {
|
||||
viewerStore.animateCameraToView(direction)
|
||||
},
|
||||
(deltaX: number, deltaY: number) => {
|
||||
viewerStore.rotateCamera(deltaX, deltaY)
|
||||
},
|
||||
(direction: ViewDirection) => {
|
||||
// When directly facing a face and clicking it, rotate 90° clockwise
|
||||
viewerStore.rotateCameraAroundAxis(direction)
|
||||
}
|
||||
)
|
||||
|
||||
// Start render loop to keep ViewCube in sync with main camera
|
||||
startRenderLoop()
|
||||
}
|
||||
})
|
||||
|
||||
function startRenderLoop(): void {
|
||||
const sync = () => {
|
||||
const service = getViewCubeService()
|
||||
if (service.isInitialized() && viewerStore.camera) {
|
||||
service.syncWithMainCamera(viewerStore.camera)
|
||||
}
|
||||
renderLoopId = requestAnimationFrame(sync)
|
||||
}
|
||||
renderLoopId = requestAnimationFrame(sync)
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (renderLoopId !== null) {
|
||||
cancelAnimationFrame(renderLoopId)
|
||||
}
|
||||
resetViewCubeService()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="viewcube-container" ref="containerRef"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.viewcube-container {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
z-index: 20;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user