133 lines
4.5 KiB
HTML
133 lines
4.5 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<title>ViewDesignEngine 3D Viewer</title>
|
||
|
|
<style>
|
||
|
|
body { margin: 0; overflow: hidden; font-family: sans-serif; }
|
||
|
|
canvas { display: block; }
|
||
|
|
#info { position: absolute; top: 10px; left: 10px; color: white; background: rgba(0,0,0,0.7); padding: 8px 12px; border-radius: 4px; font-size: 12px; }
|
||
|
|
#drop { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: #aaa; font-size: 18px; pointer-events: none; }
|
||
|
|
#toolbar { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; }
|
||
|
|
#toolbar button { padding: 6px 14px; border: 1px solid #666; background: rgba(0,0,0,0.6); color: white; border-radius: 3px; cursor: pointer; font-size: 12px; }
|
||
|
|
#toolbar button:hover { background: rgba(255,255,255,0.2); }
|
||
|
|
#file-input { display: none; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="info">ViewDesignEngine 3D Viewer</div>
|
||
|
|
<div id="drop">拖放 .glb / .stl 文件到这里</div>
|
||
|
|
<div id="toolbar">
|
||
|
|
<button onclick="document.getElementById('file-input').click()">📁 打开文件</button>
|
||
|
|
<button onclick="toggleWireframe()">🔲 线框</button>
|
||
|
|
<button onclick="resetCamera()">🔄 重置视角</button>
|
||
|
|
</div>
|
||
|
|
<input type="file" id="file-input" accept=".glb,.gltf,.stl">
|
||
|
|
|
||
|
|
<script type="importmap">
|
||
|
|
{
|
||
|
|
"imports": {
|
||
|
|
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
||
|
|
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
<script type="module">
|
||
|
|
import * as THREE from 'three';
|
||
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||
|
|
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
|
||
|
|
|
||
|
|
const scene = new THREE.Scene();
|
||
|
|
scene.background = new THREE.Color(0x222233);
|
||
|
|
|
||
|
|
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
|
||
|
|
camera.position.set(5, 4, 8);
|
||
|
|
|
||
|
|
const renderer = new THREE.WebGLRenderer({antialias: true});
|
||
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||
|
|
document.body.appendChild(renderer.domElement);
|
||
|
|
|
||
|
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||
|
|
controls.enableDamping = true;
|
||
|
|
|
||
|
|
const ambientLight = new THREE.AmbientLight(0x404050);
|
||
|
|
scene.add(ambientLight);
|
||
|
|
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||
|
|
dirLight.position.set(10, 15, 10);
|
||
|
|
scene.add(dirLight);
|
||
|
|
|
||
|
|
const gridHelper = new THREE.GridHelper(10, 10, 0x444466, 0x333344);
|
||
|
|
scene.add(gridHelper);
|
||
|
|
|
||
|
|
let currentMesh = null;
|
||
|
|
|
||
|
|
function loadGLB(url) {
|
||
|
|
new GLTFLoader().load(url, (gltf) => {
|
||
|
|
if (currentMesh) scene.remove(currentMesh);
|
||
|
|
currentMesh = gltf.scene;
|
||
|
|
scene.add(currentMesh);
|
||
|
|
document.getElementById('drop').style.display = 'none';
|
||
|
|
document.getElementById('info').textContent = `GLB loaded: ${url.split('/').pop()}`;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadSTL(url) {
|
||
|
|
new STLLoader().load(url, (geometry) => {
|
||
|
|
if (currentMesh) scene.remove(currentMesh);
|
||
|
|
const material = new THREE.MeshPhongMaterial({color: 0x4499cc, specular: 0x111111, shininess: 30});
|
||
|
|
currentMesh = new THREE.Mesh(geometry, material);
|
||
|
|
scene.add(currentMesh);
|
||
|
|
document.getElementById('drop').style.display = 'none';
|
||
|
|
document.getElementById('info').textContent = `STL loaded: ${url.split('/').pop()}`;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// File open
|
||
|
|
document.getElementById('file-input').addEventListener('change', (e) => {
|
||
|
|
const file = e.target.files[0];
|
||
|
|
if (!file) return;
|
||
|
|
const url = URL.createObjectURL(file);
|
||
|
|
if (file.name.endsWith('.stl')) loadSTL(url);
|
||
|
|
else loadGLB(url);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Drag & drop
|
||
|
|
document.addEventListener('dragover', (e) => { e.preventDefault(); });
|
||
|
|
document.addEventListener('drop', (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
const file = e.dataTransfer.files[0];
|
||
|
|
if (!file) return;
|
||
|
|
const url = URL.createObjectURL(file);
|
||
|
|
if (file.name.endsWith('.stl')) loadSTL(url);
|
||
|
|
else loadGLB(url);
|
||
|
|
});
|
||
|
|
|
||
|
|
window.toggleWireframe = () => {
|
||
|
|
if (currentMesh) {
|
||
|
|
currentMesh.traverse((child) => { if (child.isMesh) child.material.wireframe = !child.material.wireframe; });
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
window.resetCamera = () => {
|
||
|
|
camera.position.set(5, 4, 8);
|
||
|
|
controls.target.set(0, 0, 0);
|
||
|
|
controls.update();
|
||
|
|
};
|
||
|
|
|
||
|
|
function animate() {
|
||
|
|
requestAnimationFrame(animate);
|
||
|
|
controls.update();
|
||
|
|
renderer.render(scene, camera);
|
||
|
|
}
|
||
|
|
animate();
|
||
|
|
window.addEventListener('resize', () => {
|
||
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
|
camera.updateProjectionMatrix();
|
||
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|