import * as THREE from "three"; import { exportTo3MF } from "three-3mf-exporter"; import type { TraceResult } from "./image-tracer"; import {Vector3} from "three"; export interface ExtrusionSettings { size: number; // 模型整体尺寸 (mm) layers: Array<{ id: string; thickness: number; // 图层厚度 (mm) }>; } export interface LayerMesh { id: string; mesh: THREE.Mesh; thickness: number; color: number; } /** * 从图层索引生成颜色(使用黄金角确保颜色分散) */ function generateLayerColor(index: number): number { const hue = (index * 137.508) % 360; // 黄金角 return new THREE.Color(`hsl(${hue}, 70%, 50%)`).getHex(); } /** * 将矢量路径生成 3MF 文件 * @param image - 原始图片 * @param traceResult - 矢量追踪结果 * @param settings - 挤压设置 * @returns 3MF Blob */ export async function generate3MF( image: HTMLImageElement, traceResult: TraceResult, settings: ExtrusionSettings ): Promise { // 创建 Three.js 场景 const scene = new THREE.Scene(); // 计算缩放比例,使模型适应指定尺寸 const maxDimension = Math.max(traceResult.width, traceResult.height); const scale = settings.size / maxDimension; // 中心偏移 const offsetX = -traceResult.width / 2; const offsetY = -traceResult.height / 2; // 为每个启用的图层创建网格 let currentHeight = 0; const meshes: LayerMesh[] = []; let layerIndex = 0; let boundingBox = new THREE.Box3(); for (const layerSetting of settings.layers) { const layer = traceResult.layers.find((l) => l.id === layerSetting.id); if (!layer) continue; // 创建挤压几何体 const extrudeSettings: THREE.ExtrudeGeometryOptions = { depth: layerSetting.thickness, curveSegments: 36, bevelEnabled: true, bevelSize: 0.4 }; const geometry: THREE.ExtrudeGeometry = new THREE.ExtrudeGeometry(layer.paths, extrudeSettings); geometry.computeBoundingBox(); // 为该图层生成颜色 const color = generateLayerColor(layerIndex); const material = new THREE.MeshStandardMaterial({ color, metalness: 0.3, roughness: 0.7, }); const mesh = new THREE.Mesh(geometry, material); boundingBox.expandByObject(mesh); // 设置图层高度(堆叠) mesh.position.y = currentHeight; scene.add(mesh); meshes.push({ id: layerSetting.id, mesh, thickness: layerSetting.thickness, color, }); currentHeight += layerSetting.thickness; layerIndex++; } const center = boundingBox.getCenter(new Vector3()); for(const mesh of meshes){ mesh.mesh.position.sub(center); } if (meshes.length === 0) { throw new Error("没有可生成的图层"); } // 导出为 3MF const data = await exportTo3MF(scene); const blob = new Blob([data], { type: "model/3mf" }); // 清理 scene.clear(); meshes.forEach(({ mesh }) => { mesh.geometry.dispose(); (mesh.material as THREE.Material).dispose(); }); return blob; }