Add corner
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { AppConfig } from '../types';
|
||||
import { Ruler, Box, Layers, Minimize2 } from 'lucide-react';
|
||||
import { Ruler, Box, Layers, Minimize2, CircleDashed } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
config: AppConfig;
|
||||
@@ -73,6 +73,7 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
|
||||
<Layers className="text-accent" size={18} /> Параметры печати
|
||||
</h3>
|
||||
|
||||
{/* Wall Thickness */}
|
||||
<div className="mb-6">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="text-sm font-medium text-gray-300">Толщина стенок</label>
|
||||
@@ -80,7 +81,6 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
|
||||
{config.wallThickness.toFixed(1)} мм
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-xs text-gray-500 font-mono">0.4</span>
|
||||
<input
|
||||
@@ -96,6 +96,32 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Corner Radius (NEW) */}
|
||||
<div className="mb-6">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="text-sm font-medium text-gray-300 flex items-center gap-1">
|
||||
<CircleDashed size={14} className="text-gray-400"/> Радиус скругления
|
||||
</label>
|
||||
<span className="text-purple-400 font-bold bg-purple-400/10 px-2 py-1 rounded text-sm border border-purple-400/20">
|
||||
{config.cornerRadius?.toFixed(0) || 0} мм
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-xs text-gray-500 font-mono">0</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="20"
|
||||
step="1"
|
||||
value={config.cornerRadius || 0}
|
||||
onChange={(e) => onChange({...config, cornerRadius: parseFloat(e.target.value)})}
|
||||
className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-purple-500 hover:accent-purple-400 transition-all"
|
||||
/>
|
||||
<span className="text-xs text-gray-500 font-mono">20</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Printer Tolerance */}
|
||||
<div className="mb-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="text-sm font-medium text-gray-300 flex items-center gap-1">
|
||||
@@ -105,7 +131,6 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
|
||||
{config.printerTolerance.toFixed(1)} мм
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-xs text-gray-500 font-mono">0.0</span>
|
||||
<input
|
||||
@@ -121,11 +146,6 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900/50 p-3 rounded border border-slate-700/50">
|
||||
<p className="text-xs text-gray-400 leading-relaxed">
|
||||
<span className="text-accent font-semibold">Зазор</span> уменьшает размер каждой ячейки, чтобы они легко вставлялись в ящик и друг в друга. Рекомендуется 0.5 мм.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { createBinGeometry, generateSTL, exportSTL } from '../services/geometryG
|
||||
import { Download, Package, Info, Loader2, Share2, Check, Ruler } from 'lucide-react';
|
||||
import { generateShareUrl } from '../utils/share';
|
||||
|
||||
// --- DrawerFrame (Каркас) ---
|
||||
const DrawerFrame = ({ config }: { config: AppConfig }) => {
|
||||
const { width, depth, height } = config.drawer;
|
||||
const offset = 0.5;
|
||||
@@ -22,18 +21,19 @@ const DrawerFrame = ({ config }: { config: AppConfig }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// --- BinMesh (Ячейка) ---
|
||||
interface BinMeshProps {
|
||||
part: GeneratedPart;
|
||||
thickness: number;
|
||||
cornerRadius: number; // <--- ADDED PROP
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, isSelected, onClick }) => {
|
||||
const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, cornerRadius, isSelected, onClick }) => {
|
||||
const geometry = React.useMemo(() => {
|
||||
return createBinGeometry(part.width, part.depth, part.height, thickness);
|
||||
}, [part, thickness]);
|
||||
// PASS cornerRadius HERE
|
||||
return createBinGeometry(part.width, part.depth, part.height, thickness, cornerRadius);
|
||||
}, [part, thickness, cornerRadius]);
|
||||
|
||||
return (
|
||||
<group position={[part.x + part.width/2, 0, part.y + part.depth/2]}>
|
||||
@@ -42,6 +42,7 @@ const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, isSelected, onClick
|
||||
</mesh>
|
||||
{isSelected && (
|
||||
<lineSegments position={[0, part.height/2, 0]}>
|
||||
{/* Для подсветки оставляем обычный бокс, т.к. edgesGeometry на extrude выглядит грязно */}
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(part.width, part.height, part.depth)]} />
|
||||
<lineBasicMaterial color="white" linewidth={2} />
|
||||
</lineSegments>
|
||||
@@ -50,7 +51,6 @@ const BinMesh: React.FC<BinMeshProps> = ({ part, thickness, isSelected, onClick
|
||||
);
|
||||
};
|
||||
|
||||
// --- PreviewStep (Основной) ---
|
||||
interface Props {
|
||||
parts: GeneratedPart[];
|
||||
config: AppConfig;
|
||||
@@ -70,7 +70,8 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
}, [selectedId]);
|
||||
|
||||
const handleDownload = (part: GeneratedPart) => {
|
||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness);
|
||||
// PASS config.cornerRadius HERE
|
||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
||||
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
||||
exportSTL(mesh, `${part.name.replace(/\s+/g, '_')}.stl`);
|
||||
};
|
||||
@@ -81,7 +82,8 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
parts.forEach(part => {
|
||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness);
|
||||
// PASS config.cornerRadius HERE
|
||||
const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness, config.cornerRadius);
|
||||
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
|
||||
const stlData = generateSTL(mesh);
|
||||
zip.file(`${part.name.replace(/\s+/g, '_')}.stl`, stlData);
|
||||
@@ -100,18 +102,14 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// --- Функция копирования ---
|
||||
const handleShare = async () => {
|
||||
const url = generateShareUrl(config, splits);
|
||||
let success = false;
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(url);
|
||||
success = true;
|
||||
} else {
|
||||
throw new Error('Clipboard API unavailable');
|
||||
}
|
||||
} else { throw new Error('Clipboard API unavailable'); }
|
||||
} catch (err) {
|
||||
try {
|
||||
const textArea = document.createElement("textarea");
|
||||
@@ -125,11 +123,8 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
const result = document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
if (result) success = true;
|
||||
} catch (e) {
|
||||
console.error("Copy failed", e);
|
||||
}
|
||||
} catch (e) { console.error("Copy failed", e); }
|
||||
}
|
||||
|
||||
if (success) {
|
||||
setShareUrlCopied(true);
|
||||
setTimeout(() => setShareUrlCopied(false), 3000);
|
||||
@@ -140,16 +135,12 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
|
||||
return (
|
||||
<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-wrap items-center gap-6 justify-center md:justify-start">
|
||||
<div className="hidden md:flex items-center gap-2 text-gray-300 mr-2">
|
||||
<Ruler className="text-primary" size={20} />
|
||||
<span className="font-medium text-sm uppercase tracking-wide opacity-70">Размеры ящика:</span>
|
||||
</div>
|
||||
|
||||
{/* --- ОБНОВЛЕННЫЙ БЛОК РАЗМЕРОВ --- */}
|
||||
<div className="flex flex-wrap gap-6 font-mono text-white items-baseline">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-slate-500 text-sm font-bold uppercase tracking-wider">Ширина:</span>
|
||||
@@ -166,7 +157,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
<span className="text-sm text-slate-500 font-bold self-baseline">мм</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className={`
|
||||
@@ -183,7 +173,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
</div>
|
||||
|
||||
<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="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">
|
||||
@@ -195,7 +184,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
<li>• Скролл: Масштаб</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Canvas
|
||||
shadows
|
||||
dpr={[1, 2]}
|
||||
@@ -216,7 +204,10 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
<DrawerFrame config={config} />
|
||||
{parts.map(part => (
|
||||
<BinMesh
|
||||
key={part.id} part={part} thickness={config.wallThickness}
|
||||
key={part.id}
|
||||
part={part}
|
||||
thickness={config.wallThickness}
|
||||
cornerRadius={config.cornerRadius || 0} // PASS PROP
|
||||
isSelected={selectedId === part.id}
|
||||
onClick={() => setSelectedId(part.id)}
|
||||
/>
|
||||
@@ -228,7 +219,6 @@ export const PreviewStep: React.FC<Props> = ({ parts, config, splits }) => {
|
||||
</Canvas>
|
||||
</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="flex justify-between items-center mb-6 shrink-0">
|
||||
<h2 className="text-xl font-bold flex items-center gap-2 text-primary">
|
||||
|
||||
Reference in New Issue
Block a user