refactor: stl->3mf
This commit is contained in:
@@ -5,13 +5,13 @@ import {
|
||||
Show, createEffect,
|
||||
} from "solid-js";
|
||||
import * as THREE from "three";
|
||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
|
||||
import { ThreeMFLoader } from "three/addons/loaders/3MFLoader.js";
|
||||
|
||||
export interface TokenViewerProps {
|
||||
stlUrl: string | null;
|
||||
}
|
||||
|
||||
export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
|
||||
const [viewerRef, setViewerRef] = createSignal<HTMLDivElement | null>(null);
|
||||
const [isLoaded, setIsLoaded] = createSignal(false);
|
||||
@@ -21,49 +21,66 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
let camera: THREE.PerspectiveCamera | null = null;
|
||||
let renderer: THREE.WebGLRenderer | null = null;
|
||||
let mesh: THREE.Mesh | null = null;
|
||||
let group: THREE.Group | null = null;
|
||||
let animationId: number | null = null;
|
||||
let isDragging = false;
|
||||
let previousMousePosition = { x: 0, y: 0 };
|
||||
|
||||
// 加载 STL 用于预览
|
||||
// 加载 3MF 用于预览
|
||||
const loadSTL = async (url: string) => {
|
||||
const viewerEl = viewerRef();
|
||||
if (!viewerEl) return;
|
||||
|
||||
// 清理旧的场景
|
||||
if (mesh) {
|
||||
scene?.remove(mesh);
|
||||
mesh.geometry.dispose();
|
||||
(mesh.material as THREE.Material).dispose();
|
||||
if (group) {
|
||||
scene?.remove(group);
|
||||
group.traverse((obj) => {
|
||||
if ((obj as THREE.Mesh).isMesh) {
|
||||
const meshObj = obj as THREE.Mesh;
|
||||
meshObj.geometry.dispose();
|
||||
(meshObj.material as THREE.Material).dispose();
|
||||
}
|
||||
});
|
||||
group = null;
|
||||
mesh = null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const stlText = await response.text();
|
||||
const loader = new ThreeMFLoader();
|
||||
const object = await loader.loadAsync(url);
|
||||
|
||||
// 解析 STL
|
||||
const loader = new STLLoader();
|
||||
const geometry = loader.parse(stlText);
|
||||
// 3MF 文件可能返回一个 Group,包含多个 Mesh
|
||||
group = object instanceof THREE.Group ? object : new THREE.Group().add(object);
|
||||
|
||||
// 计算边界并居中
|
||||
geometry.center();
|
||||
// 居中并调整方向
|
||||
group.center();
|
||||
group.rotateX(Math.PI);
|
||||
|
||||
// 创建材质
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0x808080,
|
||||
metalness: 0.3,
|
||||
roughness: 0.7,
|
||||
side: THREE.DoubleSide,
|
||||
// 为每个 mesh 启用原始颜色
|
||||
group.traverse((child) => {
|
||||
if ((child as THREE.Mesh).isMesh) {
|
||||
const childMesh = child as THREE.Mesh;
|
||||
// 保留原始顶点颜色或材质颜色
|
||||
if (childMesh.geometry.attributes.color) {
|
||||
childMesh.material.vertexColors = true;
|
||||
}
|
||||
// 确保材质是标准材质以支持光照
|
||||
if (!(childMesh.material instanceof THREE.MeshStandardMaterial)) {
|
||||
childMesh.material = new THREE.MeshStandardMaterial({
|
||||
color: childMesh.material.color,
|
||||
metalness: 0.3,
|
||||
roughness: 0.7,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.rotateX(Math.PI);
|
||||
scene?.add(mesh);
|
||||
scene?.add(group);
|
||||
mesh = group.children[0] as THREE.Mesh;
|
||||
|
||||
// 调整相机
|
||||
if (camera && scene) {
|
||||
const box = new THREE.Box3().setFromObject(mesh);
|
||||
const box = new THREE.Box3().setFromObject(group);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const maxDim = Math.max(size.x, size.y, size.z);
|
||||
|
||||
@@ -73,8 +90,8 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
|
||||
setIsLoaded(true);
|
||||
} catch (e) {
|
||||
console.error("加载 STL 预览失败:", e);
|
||||
setError(e instanceof Error ? e.message : "加载 STL 失败");
|
||||
console.error("加载 3MF 预览失败:", e);
|
||||
setError(e instanceof Error ? e.message : "加载模型失败");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -125,13 +142,13 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isDragging || !mesh) return;
|
||||
if (!isDragging || !group) return;
|
||||
|
||||
const deltaX = e.clientX - previousMousePosition.x;
|
||||
const deltaY = e.clientY - previousMousePosition.y;
|
||||
|
||||
mesh.rotation.y += deltaX * 0.01;
|
||||
mesh.rotation.x += deltaY * 0.01;
|
||||
group.rotation.y += deltaX * 0.01;
|
||||
group.rotation.x += deltaY * 0.01;
|
||||
|
||||
previousMousePosition = { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
@@ -154,9 +171,9 @@ export default function MdTokenViewer(props: TokenViewerProps) {
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
if (scene && camera && renderer && mesh) {
|
||||
if (scene && camera && renderer && group) {
|
||||
if (!isDragging) {
|
||||
mesh.rotation.y += 0.005; // 自动旋转
|
||||
group.rotation.y += 0.005; // 自动旋转
|
||||
}
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user