diff --git a/src/App.tsx b/src/App.tsx index bdd5826..225b87d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,11 +19,10 @@ const App = () => { cornerRadius: 4, }); - // ВАЖНО: Инициализируем subdivisions пустым объектом + // Чистый стейт без subdivisions const [splits, setSplits] = useState({ x: [], - y: [], - subdivisions: {} + y: [] }); // --- ЛОГИКА ВОССТАНОВЛЕНИЯ ИЗ ССЫЛКИ --- @@ -31,10 +30,10 @@ const App = () => { const sharedData = parseShareUrl(); if (sharedData) { setConfig(sharedData.config); - // При восстановлении тоже гарантируем наличие subdivisions + // Принудительно чистим объект от старых полей, если они были в ссылке setSplits({ - ...sharedData.splits, - subdivisions: sharedData.splits.subdivisions || {} + x: sharedData.splits.x || [], + y: sharedData.splits.y || [] }); setStep(3); setIsLoadedFromUrl(true); @@ -128,7 +127,7 @@ const App = () => { - - - -
+
- {/* Instruction Overlay */}
- - {mode === 'lines' ? 'Режим: Линии' : 'Режим: Ячейки'} + Инструкция
- {mode === 'lines' ? ( -
    -
  • Клик: Новая линия
  • -
  • Драг: Двигать линию
  • -
  • ПКМ: Удалить линию
  • -
- ) : ( -
    -
  • Клик по ячейке: Настройка
  • -
  • Добавляй ряды и колонки внутри
  • -
- )} +
    +
  • Клик у края: Новая линия
  • +
  • Перетаскивание: Изменить размер
  • +
  • Двойной клик/ПКМ: Удалить
  • +
- {/* Rulers */}
0 {config.drawer.width} мм @@ -198,12 +138,12 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
@@ -223,134 +163,111 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { - {/* --- Рендеринг ячеек --- */} + {/* --- Labels --- */} {sortedX.slice(0, -1).map((x1, i) => { const x2 = sortedX[i + 1]; return sortedY.slice(0, -1).map((y1, j) => { - const cellX = x1 * viewBoxW; - const cellY = y1 * viewBoxH; - const cellW = (x2 - x1) * viewBoxW; - const cellH = (y2 - y1) * viewBoxH; + const y2 = sortedY[j + 1]; + const width = (x2 - x1) * config.drawer.width; + const depth = (y2 - y1) * config.drawer.depth; + const centerX = ((x1 + x2) / 2) * viewBoxW; + const centerY = ((y1 + y2) / 2) * viewBoxH; - const isSelected = selectedCell?.i === i && selectedCell?.j === j; - const subdiv = getSubdivision(i, j); + const cellWidthSVG = (x2 - x1) * viewBoxW; + const cellHeightSVG = (y2 - y1) * viewBoxH; + + let fontSize = Math.min(36, cellHeightSVG * 0.6); + fontSize = Math.min(fontSize, cellWidthSVG * 0.25); + + if (fontSize < 10) return null; return ( - - {/* Прямоугольник ячейки */} - { - if (mode === 'cells') { - e.stopPropagation(); - setSelectedCell({ i, j }); - } - }} - /> - - {/* Внутренние линии (пунктир) */} - {subdiv.cols > 1 && Array.from({ length: subdiv.cols - 1 }).map((_, cI) => { - const splitX = cellX + (cellW / subdiv.cols) * (cI + 1); - return ; - })} - {subdiv.rows > 1 && Array.from({ length: subdiv.rows - 1 }).map((_, rI) => { - const splitY = cellY + (cellH / subdiv.rows) * (rI + 1); - return ; - })} - - {/* Размеры (если не разбито) */} - {subdiv.rows === 1 && subdiv.cols === 1 && ( - - {((x2 - x1) * config.drawer.width).toFixed(0)}×{((y2 - y1) * config.drawer.depth).toFixed(0)} - - )} - + + {width.toFixed(0)} × {depth.toFixed(0)} + ); }); })} + + {/* --- X Lines (Vertical) --- */} + {safeX.map((x, i) => { + const isHovered = hoveredSplit?.axis === 'x' && hoveredSplit.index === i; + const isDragging = dragging?.axis === 'x' && dragging.index === i; + const color = isHovered || isDragging ? '#f59e0b' : '#64748b'; + const width = isHovered || isDragging ? 8 : 4; - {/* --- Линии границ (X) --- */} - {safeX.map((x, i) => ( - handleSplitHover(e, 'x', i)}> - - - {mode === 'lines' && hoveredSplit?.axis === 'x' && hoveredSplit.index === i && ( - { e.stopPropagation(); removeSplit('x', i); }} - onMouseEnter={() => setIsButtonHovered(true)} onMouseLeave={() => setIsButtonHovered(false)} - > - - - - )} - - ))} + return ( + removeSplit('x', i)} + onMouseMove={(e) => handleSplitHover(e, 'x', i)} + > + + + {(isHovered || isDragging) && ( + { e.stopPropagation(); removeSplit('x', i); }} + onMouseEnter={() => setIsButtonHovered(true)} onMouseLeave={() => setIsButtonHovered(false)} + > + + + + )} + + ); + })} - {/* --- Линии границ (Y) --- */} - {safeY.map((y, i) => ( - handleSplitHover(e, 'y', i)}> - - - {mode === 'lines' && hoveredSplit?.axis === 'y' && hoveredSplit.index === i && ( - { e.stopPropagation(); removeSplit('y', i); }} - onMouseEnter={() => setIsButtonHovered(true)} onMouseLeave={() => setIsButtonHovered(false)} - > - - - - )} - - ))} + {/* --- Y Lines (Horizontal) --- */} + {safeY.map((y, i) => { + const isHovered = hoveredSplit?.axis === 'y' && hoveredSplit.index === i; + const isDragging = dragging?.axis === 'y' && dragging.index === i; + const color = isHovered || isDragging ? '#f59e0b' : '#64748b'; + const width = isHovered || isDragging ? 8 : 4; - {/* --- Фантомные линии --- */} - {mode === 'lines' && !hoveredSplit && !dragging && phantomAxis === 'x' && ( - + return ( + removeSplit('y', i)} + onMouseMove={(e) => handleSplitHover(e, 'y', i)} + > + + + {(isHovered || isDragging) && ( + { e.stopPropagation(); removeSplit('y', i); }} + onMouseEnter={() => setIsButtonHovered(true)} onMouseLeave={() => setIsButtonHovered(false)} + > + + + + )} + + ); + })} + + {/* --- Phantom Lines --- */} + {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'x' && ( + + + + )} - {mode === 'lines' && !hoveredSplit && !dragging && phantomAxis === 'y' && ( - + {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'y' && ( + + + + )} - - {/* --- Панель управления выбранной ячейкой (HTML) --- */} - {mode === 'cells' && selectedCell && ( -
e.stopPropagation()} - > - {/* Ряды */} -
-
X
- - {getSubdivision(selectedCell.i, selectedCell.j).cols} - -
- - {/* Колонки */} -
-
Y
- - {getSubdivision(selectedCell.i, selectedCell.j).rows} - -
- - -
- )} -
diff --git a/src/services/geometryGenerator.ts b/src/services/geometryGenerator.ts index a4ae8e2..8ad4d65 100644 --- a/src/services/geometryGenerator.ts +++ b/src/services/geometryGenerator.ts @@ -8,10 +8,9 @@ export const calculateParts = ( ): GeneratedPart[] => { const parts: GeneratedPart[] = []; - // Безопасное чтение данных - const safeX = splits?.x || []; - const safeY = splits?.y || []; - const safeSub = splits?.subdivisions || {}; + // Safe Access + const safeX = splits.x || []; + const safeY = splits.y || []; const xPoints = [0, ...[...safeX].sort((a, b) => a - b), 1]; const yPoints = [0, ...[...safeY].sort((a, b) => a - b), 1]; @@ -21,58 +20,37 @@ export const calculateParts = ( for (let i = 0; i < xPoints.length - 1; i++) { for (let j = 0; j < yPoints.length - 1; j++) { - const rawX = xPoints[i] * config.drawer.width; - const rawY = yPoints[j] * config.drawer.depth; - const rawW = (xPoints[i + 1] - xPoints[i]) * config.drawer.width; - const rawD = (yPoints[j + 1] - yPoints[j]) * config.drawer.depth; + 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; - // Получаем настройки деления - const subdiv = safeSub[`${i}-${j}`] || { rows: 1, cols: 1 }; - - // Размеры под-ячейки - const subCellWidth = rawW / subdiv.cols; - const subCellDepth = rawD / subdiv.rows; + const realWidth = segmentW - config.printerTolerance; + const realDepth = segmentD - config.printerTolerance; + const realX = segmentX + (config.printerTolerance / 2); + const realY = segmentY + (config.printerTolerance / 2); - // Генерируем под-ячейки - for (let r = 0; r < subdiv.rows; r++) { - for (let c = 0; c < subdiv.cols; c++) { - - const subX = rawX + (c * subCellWidth); - const subY = rawY + (r * subCellDepth); - - const realWidth = subCellWidth - config.printerTolerance; - const realDepth = subCellDepth - config.printerTolerance; - const realX = subX + (config.printerTolerance / 2); - const realY = subY + (config.printerTolerance / 2); - - if (realWidth < 5 || realDepth < 5) continue; - - // Имя: если деление, добавляем суффикс - let partName = `Ячейка ${i+1}-${j+1}`; - if (subdiv.rows > 1 || subdiv.cols > 1) { - partName += ` (${r+1}-${c+1})`; - } - - parts.push({ - id: `part-${partCounter}`, - name: partName, - width: realWidth, - depth: realDepth, - height: config.drawer.height, - x: realX, - y: realY, - color: `hsl(${Math.random() * 360}, 70%, 50%)` - }); - partCounter++; - } + if (realWidth < 5 || realDepth < 5) { + 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; }; -// ... Остальной код (createRoundedRectShape, createBinGeometry) как в работающей версии ... const createRoundedRectShape = (width: number, height: number, radius: number): THREE.Shape => { const shape = new THREE.Shape(); const x = -width / 2; diff --git a/src/types.ts b/src/types.ts index 4008ee5..1bdb5ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,17 +11,9 @@ export interface AppConfig { cornerRadius: number; } -// Новая структура: настройки деления внутри одной ячейки -export interface CellSubdivision { - rows: number; // горизонтальные ряды - cols: number; // вертикальные колонки -} - export interface LayoutSplits { x: number[]; y: number[]; - // Ключ: "indexX-indexY" (например "0-0"), Значение: {rows: 2, cols: 1} - subdivisions: Record; } export interface GeneratedPart {