Fix view
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { Suspense, useEffect, useRef, useState } from 'react';
|
import React, { Suspense, useEffect, useRef, useState, useMemo } from 'react';
|
||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
import { OrbitControls, Center, Environment } from '@react-three/drei';
|
import { OrbitControls, Center, Environment } from '@react-three/drei';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
@@ -8,6 +8,7 @@ import { createBinGeometry, generateSTL, exportSTL } from '../services/geometryG
|
|||||||
import { Download, Package, Info, Loader2, Share2, Check, Ruler } from 'lucide-react';
|
import { Download, Package, Info, Loader2, Share2, Check, Ruler } from 'lucide-react';
|
||||||
import { generateShareUrl } from '../utils/share';
|
import { generateShareUrl } from '../utils/share';
|
||||||
|
|
||||||
|
// --- DrawerFrame (Каркас ящика) ---
|
||||||
const DrawerFrame = ({ config }: { config: AppConfig }) => {
|
const DrawerFrame = ({ config }: { config: AppConfig }) => {
|
||||||
const { width, depth, height } = config.drawer;
|
const { width, depth, height } = config.drawer;
|
||||||
const offset = 0.5;
|
const offset = 0.5;
|
||||||
@@ -21,29 +22,43 @@ const DrawerFrame = ({ config }: { config: AppConfig }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- BinMesh (Ячейка) ---
|
||||||
interface BinMeshProps {
|
interface BinMeshProps {
|
||||||
part: GeneratedPart;
|
part: GeneratedPart;
|
||||||
thickness: number;
|
thickness: number;
|
||||||
cornerRadius: number; // <--- ADDED PROP
|
cornerRadius: number; // Важный проп для радиуса
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, cornerRadius, isSelected, onClick }) => {
|
const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, cornerRadius, isSelected, onClick }) => {
|
||||||
const geometry = React.useMemo(() => {
|
// 1. Создаем основную геометрию ячейки
|
||||||
// PASS cornerRadius HERE
|
const geometry = useMemo(() => {
|
||||||
return createBinGeometry(part.width, part.depth, part.height, thickness, cornerRadius);
|
return createBinGeometry(part.width, part.depth, part.height, thickness, cornerRadius);
|
||||||
}, [part, thickness, cornerRadius]);
|
}, [part, thickness, cornerRadius]);
|
||||||
|
|
||||||
|
// 2. Создаем геометрию для рамки выделения
|
||||||
|
// Используем EdgesGeometry с порогом 20 градусов.
|
||||||
|
// Это скроет линии на плавных изгибах (где угол между полигонами ~5-6 градусов),
|
||||||
|
// но оставит четкие грани сверху, снизу и по углам.
|
||||||
|
const edgesGeometry = useMemo(() => {
|
||||||
|
return new THREE.EdgesGeometry(geometry, 20);
|
||||||
|
}, [geometry]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={[part.x + part.width/2, 0, part.y + part.depth/2]}>
|
<group position={[part.x + part.width/2, 0, part.y + part.depth/2]}>
|
||||||
|
{/* Сама модель */}
|
||||||
<mesh geometry={geometry} onClick={(e) => { e.stopPropagation(); onClick(); }}>
|
<mesh geometry={geometry} onClick={(e) => { e.stopPropagation(); onClick(); }}>
|
||||||
<meshStandardMaterial color={isSelected ? '#f59e0b' : part.color} roughness={0.5} metalness={0.1}/>
|
<meshStandardMaterial
|
||||||
|
color={isSelected ? '#f59e0b' : part.color}
|
||||||
|
roughness={0.5}
|
||||||
|
metalness={0.1}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
|
{/* Подсветка выделения (теперь повторяет форму скругления) */}
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<lineSegments position={[0, part.height/2, 0]}>
|
<lineSegments geometry={edgesGeometry}>
|
||||||
{/* Для подсветки оставляем обычный бокс, т.к. edgesGeometry на extrude выглядит грязно */}
|
|
||||||
<edgesGeometry args={[new THREE.BoxGeometry(part.width, part.height, part.depth)]} />
|
|
||||||
<lineBasicMaterial color="white" linewidth={2} />
|
<lineBasicMaterial color="white" linewidth={2} />
|
||||||
</lineSegments>
|
</lineSegments>
|
||||||
)}
|
)}
|
||||||
@@ -51,6 +66,7 @@ const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, cornerRadius, isSele
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- PreviewStep (Основной компонент) ---
|
||||||
interface Props {
|
interface Props {
|
||||||
parts: GeneratedPart[];
|
parts: GeneratedPart[];
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
@@ -70,7 +86,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
}, [selectedId]);
|
}, [selectedId]);
|
||||||
|
|
||||||
const handleDownload = (part: GeneratedPart) => {
|
const handleDownload = (part: GeneratedPart) => {
|
||||||
// PASS config.cornerRadius HERE
|
|
||||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
||||||
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
||||||
exportSTL(mesh, `${part.name.replace(/\s+/g, '_')}.stl`);
|
exportSTL(mesh, `${part.name.replace(/\s+/g, '_')}.stl`);
|
||||||
@@ -82,7 +97,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
try {
|
try {
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
parts.forEach(part => {
|
parts.forEach(part => {
|
||||||
// PASS config.cornerRadius HERE
|
|
||||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
||||||
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
||||||
const stlData = generateSTL(mesh);
|
const stlData = generateSTL(mesh);
|
||||||
@@ -135,12 +149,15 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
|
{/* Верхняя панель: Размеры + Поделиться */}
|
||||||
<div className="flex flex-col xl:flex-row justify-between items-center bg-slate-800/80 p-4 rounded-xl border border-slate-700 mb-4 gap-4 backdrop-blur-sm shadow-lg">
|
<div className="flex flex-col xl:flex-row justify-between items-center bg-slate-800/80 p-4 rounded-xl border border-slate-700 mb-4 gap-4 backdrop-blur-sm shadow-lg">
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-6 justify-center md:justify-start">
|
<div className="flex flex-wrap items-center gap-6 justify-center md:justify-start">
|
||||||
<div className="hidden md:flex items-center gap-2 text-gray-300 mr-2">
|
<div className="hidden md:flex items-center gap-2 text-gray-300 mr-2">
|
||||||
<Ruler className="text-primary" size={20} />
|
<Ruler className="text-primary" size={20} />
|
||||||
<span className="font-medium text-sm uppercase tracking-wide opacity-70">Размеры ящика:</span>
|
<span className="font-medium text-sm uppercase tracking-wide opacity-70">Размеры ящика:</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-6 font-mono text-white items-baseline">
|
<div className="flex flex-wrap gap-6 font-mono text-white items-baseline">
|
||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-slate-500 text-sm font-bold uppercase tracking-wider">Ширина:</span>
|
<span className="text-slate-500 text-sm font-bold uppercase tracking-wider">Ширина:</span>
|
||||||
@@ -157,6 +174,7 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
<span className="text-sm text-slate-500 font-bold self-baseline">мм</span>
|
<span className="text-sm text-slate-500 font-bold self-baseline">мм</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
className={`
|
className={`
|
||||||
@@ -173,6 +191,7 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row h-full gap-6 relative flex-1 min-h-0">
|
<div className="flex flex-col lg:flex-row h-full gap-6 relative flex-1 min-h-0">
|
||||||
|
{/* 3D Viewer */}
|
||||||
<div className="flex-1 bg-slate-900 rounded-xl overflow-hidden shadow-2xl border border-slate-800 relative min-h-[400px]">
|
<div className="flex-1 bg-slate-900 rounded-xl overflow-hidden shadow-2xl border border-slate-800 relative min-h-[400px]">
|
||||||
<div className="absolute top-4 right-4 z-10 bg-black/60 p-3 rounded-lg text-xs text-gray-300 backdrop-blur pointer-events-none border border-slate-700">
|
<div className="absolute top-4 right-4 z-10 bg-black/60 p-3 rounded-lg text-xs text-gray-300 backdrop-blur pointer-events-none border border-slate-700">
|
||||||
<div className="flex items-center gap-2 mb-1 text-primary font-bold">
|
<div className="flex items-center gap-2 mb-1 text-primary font-bold">
|
||||||
@@ -184,6 +203,7 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
<li>• Скролл: Масштаб</li>
|
<li>• Скролл: Масштаб</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Canvas
|
<Canvas
|
||||||
shadows
|
shadows
|
||||||
dpr={[1, 2]}
|
dpr={[1, 2]}
|
||||||
@@ -207,7 +227,7 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
key={part.id}
|
key={part.id}
|
||||||
part={part}
|
part={part}
|
||||||
thickness={config.wallThickness}
|
thickness={config.wallThickness}
|
||||||
cornerRadius={config.cornerRadius || 0} // PASS PROP
|
cornerRadius={config.cornerRadius || 0} // <-- ИСПРАВЛЕНО: Передаем радиус
|
||||||
isSelected={selectedId === part.id}
|
isSelected={selectedId === part.id}
|
||||||
onClick={() => setSelectedId(part.id)}
|
onClick={() => setSelectedId(part.id)}
|
||||||
/>
|
/>
|
||||||
@@ -219,6 +239,7 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
|||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar List */}
|
||||||
<div className="w-full lg:w-96 bg-slate-900 p-6 rounded-xl border border-slate-800 flex flex-col h-full shadow-xl">
|
<div className="w-full lg:w-96 bg-slate-900 p-6 rounded-xl border border-slate-800 flex flex-col h-full shadow-xl">
|
||||||
<div className="flex justify-between items-center mb-6 shrink-0">
|
<div className="flex justify-between items-center mb-6 shrink-0">
|
||||||
<h2 className="text-xl font-bold flex items-center gap-2 text-primary">
|
<h2 className="text-xl font-bold flex items-center gap-2 text-primary">
|
||||||
|
|||||||
Reference in New Issue
Block a user