diff --git a/src/components/PreviewStep.tsx b/src/components/PreviewStep.tsx index 3baa028..bff0b81 100644 --- a/src/components/PreviewStep.tsx +++ b/src/components/PreviewStep.tsx @@ -1,6 +1,6 @@ import React, { Suspense, useEffect, useRef, useState, useMemo } from 'react'; import { Canvas } from '@react-three/fiber'; -import { OrbitControls, Center, Environment } from '@react-three/drei'; +import { OrbitControls, Center, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import JSZip from 'jszip'; import { AppConfig, GeneratedPart, LayoutSplits, PerforationConfig } from '../types'; @@ -52,18 +52,138 @@ const BinMesh: React.FC = ({ part, thickness, cornerRadius, perfor return new THREE.EdgesGeometry(geometry, 20); }, [geometry]); + // 3. Вычисляем размеры и позиции для каждой под-ячейки + // 3. Вычисляем размеры и позиции для реальных отсеков (с учетом мерджинга) + const dimLabels = useMemo(() => { + const xOffsets = new Set([0, 1]); + const zOffsets = new Set([0, 1]); + + part.internalPartitions.forEach(p => { + if (p.axis === 'x') xOffsets.add(p.offset); + if (p.axis === 'y') zOffsets.add(p.offset); + }); + + const xSplits = Array.from(xOffsets).sort((a, b) => a - b); + const zSplits = Array.from(zOffsets).sort((a, b) => a - b); + + const numCols = xSplits.length - 1; + const numRows = zSplits.length - 1; + if (numCols === 0 || numRows === 0) return []; + + // Union-Find для объединения ячеек, не разделенных стеной + const parent = new Int32Array(numCols * numRows).map((_, i) => i); + const find = (i: number): number => { + if (parent[i] === i) return i; + parent[i] = find(parent[i]); + return parent[i]; + } + const union = (i: number, j: number) => { + const rootI = find(i); + const rootJ = find(j); + if (rootI !== rootJ) parent[rootJ] = rootI; + } + const getIdx = (c: number, r: number) => r * numCols + c; + + // Вертикальные границы (X) + for (let i = 0; i < numCols - 1; i++) { + const boundaryX = xSplits[i + 1]; + for (let j = 0; j < numRows; j++) { + const zMid = (zSplits[j] + zSplits[j + 1]) / 2; + // Проверяем наличие перегородки axis='x' + const isBlocked = part.internalPartitions.some(p => + p.axis === 'x' && + Math.abs(p.offset - boundaryX) < 0.001 && + (p.min ?? 0) <= zMid && (p.max ?? 1) >= zMid + ); + if (!isBlocked) union(getIdx(i, j), getIdx(i + 1, j)); + } + } + + // Горизонтальные границы (Z/Y) + for (let j = 0; j < numRows - 1; j++) { + const boundaryZ = zSplits[j + 1]; + for (let i = 0; i < numCols; i++) { + const xMid = (xSplits[i] + xSplits[i + 1]) / 2; + // Проверяем наличие перегородки axis='y' + const isBlocked = part.internalPartitions.some(p => + p.axis === 'y' && + Math.abs(p.offset - boundaryZ) < 0.001 && + (p.min ?? 0) <= xMid && (p.max ?? 1) >= xMid + ); + if (!isBlocked) union(getIdx(i, j), getIdx(i, j + 1)); + } + } + + // Агрегируем регионы + const regions: Record = {}; + for (let j = 0; j < numRows; j++) { + for (let i = 0; i < numCols; i++) { + const root = find(getIdx(i, j)); + if (!regions[root]) regions[root] = { minC: i, maxC: i, minR: j, maxR: j }; + else { + const r = regions[root]; + r.minC = Math.min(r.minC, i); + r.maxC = Math.max(r.maxC, i); + r.minR = Math.min(r.minR, j); + r.maxR = Math.max(r.maxR, j); + } + } + } + + return Object.values(regions).map((r, idx) => { + const fXStart = xSplits[r.minC]; + const fXEnd = xSplits[r.maxC + 1]; + const fZStart = zSplits[r.minR]; + const fZEnd = zSplits[r.maxR + 1]; + + const fracW = fXEnd - fXStart; + const fracD = fZEnd - fZStart; + + const dimX = Math.max(0, fracW * (part.width - thickness) - thickness); + const dimZ = Math.max(0, fracD * (part.depth - thickness) - thickness); + + const cx = -part.width / 2 + (fXStart + fXEnd) / 2 * part.width; + const cz = -part.depth / 2 + (fZStart + fZEnd) / 2 * part.depth; + + return { + key: `region-${idx}`, + pos: [cx, thickness + 0.2, cz] as [number, number, number], + text: `${dimX.toFixed(0)} x ${dimZ.toFixed(0)}` + }; + }); + }, [part, thickness]); + return ( - + { e.stopPropagation(); onClick(); }} + > {/* Сама модель */} - { e.stopPropagation(); onClick(); }}> + + {/* Текстовые метки размеров внутри каждой ячейки */} + {dimLabels.map(label => ( + + {label.text} + + ))} + {/* Белая подсветка при выборе */} {isSelected && (