From 43a54c99a6f0626508572d1cf7e866e9583f6897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Sun, 11 Jan 2026 23:11:09 +0300 Subject: [PATCH] 1 --- src/services/geometryGenerator.ts | 64 +++++++++++++------------------ 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/src/services/geometryGenerator.ts b/src/services/geometryGenerator.ts index 05a1e2c..5403b86 100644 --- a/src/services/geometryGenerator.ts +++ b/src/services/geometryGenerator.ts @@ -5,12 +5,15 @@ import { AppConfig, LayoutSplits, GeneratedPart, Partition } from '../types'; type Limits = { min: number; max: number }; type LimitMap = Record; -// --- SOLVER --- +// --- SOLVER: Идентичен тому, что в LayoutStep.tsx --- +// Гарантирует, что 3D модель будет выглядеть точно так же, как 2D макет const solveWallLimits = (partitions: Partition[]): LimitMap => { const limits: LimitMap = {}; + // 1. Сброс границ partitions.forEach(p => { limits[p.id] = { min: 0, max: 1 }; }); - for (let pass = 0; pass < 3; pass++) { + // 2. Итеративное решение коллизий (4 прохода для надежности) + for (let pass = 0; pass < 4; pass++) { partitions.forEach(target => { let newMin = 0; let newMax = 1; @@ -19,10 +22,15 @@ const solveWallLimits = (partitions: Partition[]): LimitMap => { partitions.forEach(obstacle => { if (target.id === obstacle.id || target.axis === obstacle.axis) return; + // Берем актуальные границы препятствия const obsMin = limits[obstacle.id].min; const obsMax = limits[obstacle.id].max; - if (target.offset > obsMin && target.offset < obsMax) { + // ВАЖНО: Используем тот же допуск (EPSILON), что и в 2D редакторе + const EPS = 0.002; + + // Проверяем пересечение + if (target.offset >= obsMin - EPS && target.offset <= obsMax + EPS) { if (obstacle.offset < center) newMin = Math.max(newMin, obstacle.offset); else if (obstacle.offset > center) newMax = Math.min(newMax, obstacle.offset); } @@ -53,6 +61,7 @@ export const calculateParts = (config: AppConfig, splits: LayoutSplits): Generat const rawX = xPoints[i] * config.drawer.width; const rawY = yPoints[j] * config.drawer.depth; + const internalPartitions = safeParts[`${i}-${j}`] || []; const realWidth = rawW - config.printerTolerance; @@ -141,11 +150,13 @@ export const createBinGeometry = ( wallGeo.translate(0, thickness, 0); geometries.push(wallGeo); - // ВНУТРЕННИЕ ПЕРЕГОРОДКИ + // ВНУТРЕННИЕ ПЕРЕГОРОДКИ (С SOLVER'ом) const limitMap = solveWallLimits(partitions); partitions.forEach(p => { + // Используем рассчитанные границы, а не старые сохраненные const { min: pMin, max: pMax } = limitMap[p.id]; + if (pMax - pMin < 0.01) return; const lengthRatio = pMax - pMin; @@ -171,43 +182,29 @@ export const createBinGeometry = ( partGeo.translate(pX, thickness, pY); geometries.push(partGeo); - // --- ДОБАВЛЕНИЕ ГАЛТЕЛЕЙ (FILLETS) --- + // СКРУГЛЕНИЯ if (p.rounded && radius > 1) { const filletR = Math.min(radius, 5); const filletShape = createConcaveFilletShape(filletR); - - // ФУНКЦИЯ ДЛЯ ПОИСКА ВЫСОТЫ СОСЕДА + + // Функция высоты соседа (чтобы скругление не висело в воздухе) const getNeighborHeight = (pos: number) => { - // Если это край ящика (0 или 1), то высота равна высоте ящика - if (pos < 0.001 || pos > 0.999) return height; - - // Ищем внутреннюю стенку в этой точке + if (pos < 0.001 || pos > 0.999) return height; // Край ящика = полная высота + const neighbor = partitions.find(n => { - if (n.axis === p.axis) return false; // Должна быть перпендикулярна - - // Соседка должна находиться в точке pos (по своей оси смещения) - if (Math.abs(n.offset - pos) > 0.001) return false; - - // И соседка должна перекрывать нашу стенку (по своей длине) - const nLims = limitMap[n.id]; - return p.offset >= nLims.min && p.offset <= nLims.max; + if (n.axis === p.axis) return false; + // Проверка на пересечение координат + const nLims = limitMap[n.id]; // Берем актуальные границы соседа + return Math.abs(n.offset - pos) < 0.002 && p.offset >= nLims.min && p.offset <= nLims.max; }); - - // Если нашли соседа - возвращаем его высоту, иначе 0 (не должно случаться при корректном Solver) return neighbor ? neighbor.height : 0; }; - // Вычисляем высоту скругления для начала и конца стенки - // Высота не может быть больше самой стенки (p.height) и больше соседа - const startNeighborH = getNeighborHeight(pMin); - const endNeighborH = getNeighborHeight(pMax); + const hStart = Math.min(p.height, getNeighborHeight(pMin)); + const hEnd = Math.min(p.height, getNeighborHeight(pMax)); - const hStart = Math.min(p.height, startNeighborH); - const hEnd = Math.min(p.height, endNeighborH); - - // Функция добавления с конкретной высотой const addFillet = (x: number, y: number, rotY: number, h: number) => { - if (h <= 1) return; // Если высота слишком мала, не рисуем + if (h <= 1) return; const geo = new THREE.ExtrudeGeometry(filletShape, { depth: h, bevelEnabled: false }); geo.rotateX(-Math.PI / 2); geo.rotateY(rotY); @@ -218,28 +215,21 @@ export const createBinGeometry = ( const t = thickness / 2; if (p.axis === 'x') { - // VERTICAL WALL const topY = (-innerDepth / 2) + (innerDepth * pMin); const botY = (-innerDepth / 2) + (innerDepth * pMax); - // Top End (pMin) -> hStart addFillet(pX - t, topY, Math.PI, hStart); addFillet(pX + t, topY, -Math.PI / 2, hStart); - // Bottom End (pMax) -> hEnd addFillet(pX - t, botY, Math.PI / 2, hEnd); addFillet(pX + t, botY, 0, hEnd); - } else { - // HORIZONTAL WALL const leftX = (-innerWidth / 2) + (innerWidth * pMin); const rightX = (-innerWidth / 2) + (innerWidth * pMax); - // Left End (pMin) -> hStart addFillet(leftX, pY - t, 0, hStart); addFillet(leftX, pY + t, -Math.PI / 2, hStart); - // Right End (pMax) -> hEnd addFillet(rightX, pY - t, Math.PI / 2, hEnd); addFillet(rightX, pY + t, Math.PI, hEnd); }