import * as THREE from 'three'; import { STLExporter, mergeBufferGeometries } from 'three-stdlib'; import { AppConfig, LayoutSplits, GeneratedPart } from '../types'; /** * Calculates the final list of bins based on layout. * Removes printer bed constraints logic. */ export const calculateParts = ( config: AppConfig, splits: LayoutSplits ): GeneratedPart[] => { const parts: GeneratedPart[] = []; // 1. Sort splits to create segments // CRITICAL FIX: Create a copy of the array before sorting to avoid mutating React state const xPoints = [0, ...[...splits.x].sort((a, b) => a - b), 1]; const yPoints = [0, ...[...splits.y].sort((a, b) => a - b), 1]; let partCounter = 1; // 2. Iterate through the grid defined by the user for (let i = 0; i < xPoints.length - 1; i++) { for (let j = 0; j < yPoints.length - 1; j++) { const segmentX = xPoints[i] * config.drawer.width; const segmentY = yPoints[j] * config.drawer.depth; const segmentW = (xPoints[i + 1] - xPoints[i]) * config.drawer.width; const segmentD = (yPoints[j + 1] - yPoints[j]) * config.drawer.depth; // Apply Printer Tolerance (Gap) // We subtract the tolerance from the width/depth and shift the position // so the gap is evenly distributed around the part center. const realWidth = segmentW - config.printerTolerance; const realDepth = segmentD - config.printerTolerance; const realX = segmentX + (config.printerTolerance / 2); const realY = segmentY + (config.printerTolerance / 2); // Filter out extremely small parts (likely errors/double lines or consumed by tolerance) if (realWidth < 1 || realDepth < 1) { continue; } parts.push({ id: `part-${partCounter}`, name: `Ячейка ${i+1}-${j+1}`, width: realWidth, depth: realDepth, height: config.drawer.height, x: realX, y: realY, color: `hsl(${Math.random() * 360}, 70%, 50%)` }); partCounter++; } } return parts; }; /** * Generates a Three.js Geometry for a hollow bin. * The floor is at y=0..thickness. * The walls sit on top of the floor, y=thickness..height. */ export const createBinGeometry = ( width: number, depth: number, height: number, thickness: number ): THREE.BufferGeometry => { const geometries: THREE.BufferGeometry[] = []; // 1. Floor (Base) // Positioned at the bottom (0 to thickness) const floorGeo = new THREE.BoxGeometry(width, thickness, depth); floorGeo.translate(0, thickness / 2, 0); geometries.push(floorGeo); // Wall dimensions // Walls sit ON TOP of the floor to avoid overlap/z-fighting const wallHeight = height - thickness; const wallCenterY = thickness + (wallHeight / 2); if (wallHeight > 0) { // 2. Left & Right Walls (Full depth) const wallLRGeo = new THREE.BoxGeometry(thickness, wallHeight, depth); const leftWall = wallLRGeo.clone(); leftWall.translate(-(width/2) + (thickness/2), wallCenterY, 0); geometries.push(leftWall); const rightWall = wallLRGeo.clone(); rightWall.translate((width/2) - (thickness/2), wallCenterY, 0); geometries.push(rightWall); // 3. Front & Back Walls (Width minus LR thickness to fit between) // Width is reduced by 2*thickness const wallFBWidth = Math.max(0, width - (2 * thickness)); const wallFBGeo = new THREE.BoxGeometry(wallFBWidth, wallHeight, thickness); const frontWall = wallFBGeo.clone(); frontWall.translate(0, wallCenterY, (depth/2) - (thickness/2)); geometries.push(frontWall); const backWall = wallFBGeo.clone(); backWall.translate(0, wallCenterY, -(depth/2) + (thickness/2)); geometries.push(backWall); } // Merge geometries into a single mesh for clean STL export const merged = mergeBufferGeometries(geometries); return merged || new THREE.BoxGeometry(1, 1, 1); }; // Generates STL binary data without triggering download export const generateSTL = (mesh: THREE.Object3D): Uint8Array | string => { const exporter = new STLExporter(); const result = exporter.parse(mesh, { binary: true }); // JSZip requires Uint8Array or ArrayBuffer, not DataView if (result instanceof DataView) { return new Uint8Array(result.buffer, result.byteOffset, result.byteLength); } return result as string; }; // Triggers download immediately export const exportSTL = (mesh: THREE.Object3D, filename: string) => { const result = generateSTL(mesh); const blob = new Blob([result], { type: 'application/octet-stream' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = filename; link.click(); };