bebobelbastler

import React, { useState, useCallback, useRef, useEffect } from ‚react‘; import { Trash2, Box, Layers, Magnet, Layout, Maximize, Compass, PenTool, Plus, Move, Hand, Archive, ArrowUpRight, GripHorizontal, DoorOpen, AlignVerticalSpaceAround, Save, FolderOpen, Copy, CheckCircle2, Library, Armchair, Monitor, BookmarkPlus, FilePlus, ZoomIn, Download, Upload, Grid3X3, Columns, Undo2, Redo2, Scan } from ‚lucide-react‘; const App = () => { const plateThickness = 1.6; // 16mm Fixum // Fix: Tailwind Script Injection für Umgebungen, in denen es fehlt useEffect(() => { if (!document.getElementById(‚tailwind-script‘)) { const script = document.createElement(’script‘); script.id = ‚tailwind-script‘; script.src = „https://cdn.tailwindcss.com“; document.head.appendChild(script); } }, []); // Preset-Definitionen für den Katalog (Statisch) const STATIC_PRESETS = [ { name: ‚Sideboard „Klassik“‚, icon: , config: { type: ‚corpus‘, w: 120, d: 40, h: 80, shelves: 1, shelfPositions: [], fronts: { 0: ‚door_left‘, 1: ‚door_right‘ }, verticals: { 0: 1, 1: 1 } } }, { name: ‚Kommode „Organizer“‚, icon: , config: { type: ‚corpus‘, w: 60, d: 45, h: 90, shelves: 3, shelfPositions: [], fronts: { 0: ‚drawer‘, 1: ‚drawer‘, 2: ‚drawer‘, 3: ‚drawer‘ }, verticals: {} } }, { name: ‚TV-Lowboard‘, icon: , config: { type: ‚corpus‘, w: 160, d: 50, h: 45, shelves: 0, shelfPositions: [], fronts: { 0: ‚flap_down‘ }, verticals: { 0: 2 } } }, { name: ‚Highboard „Vitrine“‚, icon: , config: { type: ‚corpus‘, w: 80, d: 35, h: 140, shelves: 3, shelfPositions: [], fronts: { 0: ‚drawer‘, 1: ‚door_left‘, 2: ’none‘, 3: ‚door_left‘ }, verticals: {} } } ]; // — STATE MANAGEMENT — // Zustand für die Objekte im Raum const [items, setItems] = useState([ { id: 1, type: ‚corpus‘, x: 0, y: 0, z: 0, w: 80, d: 40, h: 80, shelves: 2, shelfPositions: [], drawerOpen: 0, frontOpen: 0, fronts: {}, verticals: {} } ]); const [activeId, setActiveId] = useState(1); // Active Item Helper (WICHTIG: Muss vor der Verwendung definiert sein) const activeItem = items.find(i => i.id === activeId); const [isSnappingEnabled, setIsSnappingEnabled] = useState(true); // Undo/Redo History State const [history, setHistory] = useState({ past: [], future: [] }); // Viewport Settings const [showFloor, setShowFloor] = useState(true); const [blueprintView, setBlueprintView] = useState(‚front‘); // ‚front‘ | ‚top‘ | ’side‘ // Collection & Custom Presets State const [collectionName, setCollectionName] = useState(‚Meine Erste Kollektion‘); const [savedCollections, setSavedCollections] = useState([]); const [customPresets, setCustomPresets] = useState([]); const [showLoadMenu, setShowLoadMenu] = useState(false); const [saveStatus, setSaveStatus] = useState(null); // UI Resize State const [blueprintHeight, setBlueprintHeight] = useState(350); const isResizingUI = useRef(false); // 3D Viewport Rotation & Zoom State const [viewRotation, setViewRotation] = useState({ x: 62, z: -35 }); const [zoom, setZoom] = useState(0.8); const [isRotating, setIsRotating] = useState(false); const lastMousePos = useRef({ x: 0, y: 0 }); // Dragging State const [dragState, setDragState] = useState(null); const svgRef = useRef(null); const fileInputRef = useRef(null); const snapDistance = 4; // Snapping-Radius const gridStep = 5; // Raster für Items // Load Data from LocalStorage on mount useEffect(() => { try { const savedCols = localStorage.getItem(‚bebobel_collections‘); if (savedCols) setSavedCollections(JSON.parse(savedCols)); const savedPresets = localStorage.getItem(‚bebobel_presets‘); if (savedPresets) setCustomPresets(JSON.parse(savedPresets)); } catch (e) { console.error(„Fehler beim Laden der Daten“, e); } }, []); // — HELPERS — const getShelfPositions = (item) => { if (!item) return []; const innerH = item.h – 2 * plateThickness; const count = parseInt(item.shelves || 0); if (item.shelfPositions && item.shelfPositions.length === count) return item.shelfPositions; const gap = innerH / (count + 1); return Array.from({ length: count }, (_, i) => (i + 1) * gap); }; const getCompartments = (item) => { if (!item) return []; const innerH = item.h – 2 * plateThickness; const positions = getShelfPositions(item); const boundaries = [0, …positions, innerH]; const compartments = []; for (let i = 0; i < boundaries.length - 1; i++) { const bottom = boundaries[i]; const top = boundaries[i+1]; let yStart = bottom + (i === 0 ? 0 : plateThickness/2); let yEnd = top - (i === boundaries.length - 2 ? 0 : plateThickness/2); compartments.push({ index: i, yRelative: yStart, h: Math.max(0.1, yEnd - yStart), centerYRelative: yStart + (yEnd - yStart) / 2 }); } return compartments; }; const getBlueprintCoords = (item) => { if (blueprintView === ‚front‘) { return { x: item.x – item.w/2, y: -item.y – item.h, w: item.w, h: item.h, labelX: item.w, labelY: item.h }; } else if (blueprintView === ‚top‘) { return { x: item.x – item.w/2, y: item.z – item.d/2, w: item.w, h: item.d, labelX: item.w, labelY: item.d }; } else if (blueprintView === ’side‘) { return { x: item.z – item.d/2, y: -item.y – item.h, w: item.d, h: item.h, labelX: item.d, labelY: item.h }; } return { x: 0, y: 0, w: 10, h: 10 }; }; // — HISTORY — const saveToHistory = useCallback(() => { setHistory(curr => { const newPast = […curr.past, items]; if (newPast.length > 10) newPast.shift(); return { past: newPast, future: [] }; }); }, [items]); const undo = () => { if (history.past.length === 0) return; const previous = history.past[history.past.length – 1]; const newPast = history.past.slice(0, -1); setHistory({ past: newPast, future: [items, …history.future] }); setItems(previous); if (!previous.find(i => i.id === activeId)) { setActiveId(previous[0]?.id || 0); } }; const redo = () => { if (history.future.length === 0) return; const next = history.future[0]; const newFuture = history.future.slice(1); setHistory({ past: […history.past, items], future: newFuture }); setItems(next); if (!next.find(i => i.id === activeId)) { setActiveId(next[0]?.id || 0); } }; // — ACTIONS — const createNewScene = () => { saveToHistory(); let baseName = ‚Neues Projekt‘; let uniqueName = baseName; let counter = 1; while (savedCollections.some(c => c.name === uniqueName)) { uniqueName = `${baseName} ${counter}`; counter++; } const initialItem = { id: 1, type: ‚corpus‘, x: 0, y: 0, z: 0, w: 80, d: 40, h: 80, shelves: 2, shelfPositions: [], drawerOpen: 0, frontOpen: 0, fronts: {}, verticals: {} }; setItems([initialItem]); setActiveId(1); setCollectionName(uniqueName); const newCollection = { name: uniqueName, items: [initialItem], date: new Date().toISOString() }; const newCollections = […savedCollections, newCollection]; setSavedCollections(newCollections); localStorage.setItem(‚bebobel_collections‘, JSON.stringify(newCollections)); setSaveStatus(’saved‘); setTimeout(() => setSaveStatus(null), 2000); }; const saveCollection = () => { const newCollection = { name: collectionName, items: items, date: new Date().toISOString() }; const existingIndex = savedCollections.findIndex(c => c.name === collectionName); let newCollections = […savedCollections]; if (existingIndex >= 0) newCollections[existingIndex] = newCollection; else newCollections.push(newCollection); setSavedCollections(newCollections); localStorage.setItem(‚bebobel_collections‘, JSON.stringify(newCollections)); setSaveStatus(’saved‘); setTimeout(() => setSaveStatus(null), 2000); }; const handleExport = () => { const data = { name: collectionName, items: items, date: new Date().toISOString(), appVersion: ‚bebobel-1.0‘ }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: ‚application/json‘ }); const url = URL.createObjectURL(blob); const link = document.createElement(‚a‘); link.href = url; link.download = `${collectionName.replace(/[^a-z0-9]/gi, ‚_‘).toLowerCase()}.bebobel.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const handleImport = (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { try { const imported = JSON.parse(event.target.result); if (imported.items && Array.isArray(imported.items)) { saveToHistory(); setItems(imported.items); setCollectionName(imported.name || „Importiertes Projekt“); setActiveId(imported.items[0]?.id || 1); setShowLoadMenu(false); } else { alert(„Ungültiges Dateiformat.“); } } catch (error) { console.error(„Import Error“, error); alert(„Fehler beim Laden der Datei.“); } }; reader.readAsText(file); e.target.value = “; }; const addToCatalog = () => { const item = items.find(i => i.id === activeId); if (!item) return; const newPreset = { name: `Custom ${item.w}x${item.h}`, config: { …item, id: undefined, x: 0, y: 0, z: 0, shelfPositions: […(item.shelfPositions || [])], fronts: { …item.fronts }, verticals: { …item.verticals } } }; const updatedPresets = […customPresets, newPreset]; setCustomPresets(updatedPresets); localStorage.setItem(‚bebobel_presets‘, JSON.stringify(updatedPresets)); }; const loadCollection = (collection) => { saveToHistory(); setItems(collection.items); setCollectionName(collection.name); setActiveId(collection.items[0]?.id || 0); setShowLoadMenu(false); }; const duplicateItem = (id) => { saveToHistory(); const itemToClone = items.find(i => i.id === id); if (!itemToClone) return; const newId = Math.max(…items.map(i => i.id), 0) + 1; const newItem = { …itemToClone, id: newId, x: itemToClone.x + 20, y: itemToClone.y, fronts: { …itemToClone.fronts }, verticals: { …itemToClone.verticals }, shelfPositions: [ …itemToClone.shelfPositions ] }; setItems([…items, newItem]); setActiveId(newId); }; const spawnItem = (type) => { saveToHistory(); const newId = Math.max(…items.map(i => i.id), 0) + 1; let newItem = { id: newId, type: type, x: 0, y: 0, z: 0, w: 60, d: 40, h: type === ‚vertical‘ ? 72 : (type === ‚corpus‘ ? 60 : 1.6), shelves: type === ‚corpus‘ ? 0 : undefined, shelfPositions: [], drawerOpen: 0, frontOpen: 0, fronts: type === ‚corpus‘ ? {} : undefined, verticals: type === ‚corpus‘ ? {} : undefined }; setItems([…items, newItem]); setActiveId(newId); }; const spawnPreset = (presetConfig) => { saveToHistory(); const newId = Math.max(…items.map(i => i.id), 0) + 1; let newItem = { …presetConfig, id: newId, x: 0, y: 0, z: 0, drawerOpen: 0, frontOpen: 0, shelfPositions: presetConfig.shelfPositions ? […presetConfig.shelfPositions] : [], fronts: presetConfig.fronts ? {…presetConfig.fronts} : {}, verticals: presetConfig.verticals ? {…presetConfig.verticals} : {} }; setItems([…items, newItem]); setActiveId(newId); }; const updateActiveItem = (key, value) => { const parsed = (key === ‚fronts‘ || key === ‚verticals‘) ? value : parseFloat(value); const val = ((key !== ‚fronts‘ && key !== ‚verticals‘) && isNaN(parsed)) ? 0 : parsed; setItems(prev => { let extraUpdates = {}; if (key === ’shelves‘) extraUpdates = { shelfPositions: [] }; const updated = prev.map(i => i.id === activeId ? { …i, …extraUpdates, [key]: val } : i); const active = updated.find(i => i.id === activeId); if (!active) return prev; const snapped = applySnapping(active, updated); return updated.map(i => i.id === activeId ? snapped : i); }); }; const updateFrontConfig = (index, type) => { saveToHistory(); const active = items.find(i => i.id === activeId); if (!active) return; const newFronts = { …active.fronts, [index]: type }; updateActiveItem(‚fronts‘, newFronts); }; const updateVerticalConfig = (index, count) => { saveToHistory(); const active = items.find(i => i.id === activeId); if (!active) return; const newVerticals = { …active.verticals, [index]: parseInt(count) }; updateActiveItem(‚verticals‘, newVerticals); }; const removeItem = (id) => { saveToHistory(); if (items.length <= 1) return; const newItems = items.filter(i => i.id !== id); setItems(newItems); setActiveId(newItems[0].id); }; // — EVENTS — const startResizingUI = (e) => { isResizingUI.current = true; document.body.style.cursor = ‚row-resize‘; }; const stopResizingUI = () => { isResizingUI.current = false; document.body.style.cursor = ‚default‘; }; const handleMouseMoveGlobal = useCallback((e) => { if (isResizingUI.current) { const newHeight = window.innerHeight – e.clientY – 40; setBlueprintHeight(Math.max(150, Math.min(window.innerHeight – 250, newHeight))); } if (isRotating) { const deltaX = e.clientX – lastMousePos.current.x; const deltaY = e.clientY – lastMousePos.current.y; setViewRotation(prev => ({ x: Math.max(10, Math.min(90, prev.x – deltaY * 0.5)), z: prev.z + deltaX * 0.5 })); lastMousePos.current = { x: e.clientX, y: e.clientY }; } }, [isRotating]); useEffect(() => { window.addEventListener(‚mousemove‘, handleMouseMoveGlobal); window.addEventListener(‚mouseup‘, () => { stopResizingUI(); setIsRotating(false); setDragState(null); }); return () => { window.removeEventListener(‚mousemove‘, handleMouseMoveGlobal); }; }, [handleMouseMoveGlobal]); const handleMouseDown3D = (e) => { if (e.target.closest(‚.no-orbit‘)) return; setIsRotating(true); lastMousePos.current = { x: e.clientX, y: e.clientY }; }; const handleWheel = (e) => { const scaleAmount = -e.deltaY * 0.001; setZoom(prev => Math.min(Math.max(0.3, prev + scaleAmount), 2.5)); }; const applySnapping = useCallback((current, all) => { if (!isSnappingEnabled) return current; let snappedItem = { …current }; all.forEach(other => { if (other.id === current.id) return; if (Math.abs(current.y – (other.y + other.h)) < snapDistance) snappedItem.y = other.y + other.h; else if (Math.abs((current.y + current.h) - other.y) < snapDistance) snappedItem.y = other.y - current.h; if (Math.abs(current.x - other.x) < snapDistance) snappedItem.x = other.x; if (Math.abs(current.z - other.z) < snapDistance) snappedItem.z = other.z; }); return snappedItem; }, [isSnappingEnabled]); const handleSVGMouseMove = (e) => { if (!dragState || !svgRef.current) return; const svg = svgRef.current; const CTM = svg.getScreenCTM(); if (!CTM) return; const mouseX = (e.clientX – CTM.e) / CTM.a; const mouseY = (e.clientY – CTM.f) / CTM.d; if (dragState.type === ‚item‘) { setItems(prev => { const active = prev.find(i => i.id === dragState.itemId); if (!active) return prev; let newX = active.x; let newY = active.y; let newZ = active.z; // Update logic based on view plane if (blueprintView === ‚front‘) { newX = Math.round(mouseX / gridStep) * gridStep; newY = Math.round(-mouseY / gridStep) * gridStep; // SVG Y is down } else if (blueprintView === ‚top‘) { newX = Math.round(mouseX / gridStep) * gridStep; newZ = Math.round(mouseY / gridStep) * gridStep; } else if (blueprintView === ’side‘) { newZ = Math.round(mouseX / gridStep) * gridStep; newY = Math.round(-mouseY / gridStep) * gridStep; } const updated = prev.map(i => i.id === dragState.itemId ? { …i, x: newX, y: newY, z: newZ } : i); const newActive = updated.find(i => i.id === dragState.itemId); const snapped = applySnapping(newActive, updated); return updated.map(i => i.id === dragState.itemId ? snapped : i); }); } else if (dragState.type === ’shelf‘) { // Shelf dragging only works in Front view properly for now (height) if (blueprintView !== ‚front‘) return; const worldMouseY = -mouseY; // Only relevant for Front view setItems(prev => { const item = prev.find(i => i.id === dragState.itemId); if (!item) return prev; const innerH = item.h – 2 * plateThickness; let newRelY = worldMouseY – item.y – plateThickness; newRelY = Math.round(newRelY); newRelY = Math.max(2, Math.min(innerH – 2, newRelY)); const positions = […getShelfPositions(item)]; positions[dragState.shelfIndex] = newRelY; positions.sort((a, b) => a – b); return prev.map(i => i.id === dragState.itemId ? { …i, shelfPositions: positions } : i); }); } }; // 3D Render const renderBlock = (w, d, h, x, y, z, isActive, id, colorClass = „bg-white“, isDrawerFront = false, key, style = {}) => { const scale = 2.2; // Safety w = w||0; d=d||0; h=h||0; x=x||0; y=y||0; z=z||0; const borderColor = isActive ? ‚border-gray-500‘ : ‚border-black/20‘; const sideColor = isActive ? ‚bg-gray-300‘ : ‚bg-gray-100‘; return (
{ e.stopPropagation(); if (id) setActiveId(id); }} className={`absolute transition-all duration-300 transform-gpu cursor-pointer no-orbit ${isActive ? ‚z-50‘ : ‚z-0‘}`} style={{ width: `${w * scale}px`, height: `${d * scale}px`, transform: `translate3d(${(x – w/2) * scale}px, ${(z – d/2) * scale}px, ${y * scale}px)`, transformStyle: ‚preserve-3d‘, …style }} > {/* Top Face */}
{isDrawerFront &&
}
{/* Bottom Face */}
{/* Front Face (South) */}
{/* Back Face (North) */}
{/* Right Face (East) */}
{/* Left Face (West) */}
); }; return (
{/* Header */}

Bebobel Blueprint

Resizable Precision Suite // 16mm
{/* Undo / Redo */}
{/* Sidebar */} {/* Hauptbereich */}
{/* 3D Viewport */}
{/* Viewport Overlay */}
3D Viewport
setZoom(parseFloat(e.target.value))} className=“w-20 h-1 bg-gray-200 rounded-full appearance-none cursor-pointer accent-black“ /> {Math.round(zoom * 100)}%
{showFloor && (
)} {showFloor && items.map(item => { const scale = 2.2; const shadowOpacity = Math.max(0.1, 0.4 – (item.y / 200)); const blur = Math.max(8, item.y / 3); return
; })} {items.map((item) => { const isActive = activeId === item.id; if (item.type === ‚corpus‘) { const { w, d, h, x, y, z, drawerOpen, frontOpen, fronts, verticals } = item; const t = plateThickness; const internalElements = []; const openPercent = (drawerOpen || 0) / 100; const frontOpenPercent = (frontOpen || 0) / 100; const shelfPositions = getShelfPositions(item); shelfPositions.forEach((pos, i) => { const absoluteY = y + t + pos; internalElements.push(renderBlock(w – 2*t, d – t, t, x, absoluteY, z + t/2, isActive, item.id, „bg-gray-100“, false, `shelf-${item.id}-${i}`)); }); const compartments = getCompartments(item); compartments.forEach((comp, i) => { const frontType = fronts?.[i]; const numVerticals = verticals?.[i] || 0; const cH = comp.h; const cY = y + t + comp.yRelative; if (numVerticals > 0) { const innerW = w – 2*t; const sectionWidth = (innerW – numVerticals * t) / (numVerticals + 1); const startX = x – innerW/2; for (let v = 0; v < numVerticals; v++) { const vX = startX + (v + 1) * sectionWidth + v * t + t/2; internalElements.push(renderBlock(t, d - t, cH, vX, cY, z + t/2, isActive, item.id, "bg-gray-100", false, `vertical-${item.id}-${i}-${v}`)); } } if (frontType && frontType !== 'none') { const fW = w - 0.4; const fH = cH - 0.4; const fD = 1.6; // FIX: fD muss HIER definiert werden, BEVOR es in fZ benutzt wird const fZ = z + d/2 + fD/2; const rot = (frontOpenPercent * 90); if (frontType === 'drawer') { const drawerFrontH = cH - 0.2; const openOffset = openPercent * (d - 5); const boxSideH = drawerFrontH * 0.7; const boxD = d - t; const drawerY = cY; const numDrawers = numVerticals + 1; const innerW = w - 2*t; const drawerSectionW = (innerW - numVerticals * t) / numDrawers; const startX = x - innerW/2; for (let dIdx = 0; dIdx < numDrawers; dIdx++) { const currentDrawerX = startX + dIdx * (drawerSectionW + t) + drawerSectionW/2; const currentBoxW = drawerSectionW - 0.5; const currentFrontW = drawerSectionW - 0.4; if (openPercent > 0.01) { internalElements.push(renderBlock(currentBoxW – 1, boxD – 1, 0.5, currentDrawerX, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-50“, false, `drawer-base-${item.id}-${i}-${dIdx}`)); internalElements.push(renderBlock(0.5, boxD – 1, boxSideH, currentDrawerX – currentBoxW/2 + 0.5, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-100“, false, `drawer-side-l-${item.id}-${i}-${dIdx}`)); internalElements.push(renderBlock(0.5, boxD – 1, boxSideH, currentDrawerX + currentBoxW/2 – 0.5, drawerY + 0.5, z + openOffset, isActive, item.id, „bg-gray-100“, false, `drawer-side-r-${item.id}-${i}-${dIdx}`)); internalElements.push(renderBlock(currentBoxW – 1, 0.5, boxSideH, currentDrawerX, drawerY + 0.5, z – boxD/2 + 0.5 + openOffset, isActive, item.id, „bg-gray-200“, false, `drawer-back-${item.id}-${i}-${dIdx}`)); } internalElements.push(renderBlock(currentFrontW, 0.8, drawerFrontH, currentDrawerX, drawerY, z + d/2 + openOffset, isActive, item.id, „bg-white“, true, `drawer-front-${item.id}-${i}-${dIdx}`)); } } else { let styleOverride = {}; if (frontType === ‚door_left‘) styleOverride = { transformOrigin: ‚0% 50%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateZ(${rot}deg)` }; else if (frontType === ‚door_right‘) styleOverride = { transformOrigin: ‚100% 50%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateZ(${-rot}deg)` }; else if (frontType === ‚flap_down‘) styleOverride = { transformOrigin: ‚50% 0%‘, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateX(${-rot}deg)` }; else if (frontType === ‚flap_up‘) styleOverride = { transformOrigin: `50% 0% ${(fH * 2.2)}px`, transform: `translate3d(${(x – w/2 + 0.2) * 2.2}px, ${(fZ – fD/2) * 2.2}px, ${(cY) * 2.2}px) rotateX(${rot}deg)` }; internalElements.push(
); } } }); return (
{renderBlock(w, d, t, x, y, z, isActive, item.id, „bg-white“, false, `corpus-base-${item.id}`)} {renderBlock(w, d, t, x, y + h – t, z, isActive, item.id, „bg-white“, false, `corpus-top-${item.id}`)} {renderBlock(t, d, h – 2*t, x – w/2 + t/2, y + t, z, isActive, item.id, „bg-gray-50“, false, `corpus-side-l-${item.id}`)} {renderBlock(t, d, h – 2*t, x + w/2 – t/2, y + t, z, isActive, item.id, „bg-gray-50“, false, `corpus-side-r-${item.id}`)} {renderBlock(w – 2*t, t, h – 2*t, x, y + t, z – d/2 + t/2, isActive, item.id, „bg-gray-200“, false, `corpus-back-${item.id}`)} {internalElements}
); } const plateH = item.type === ‚vertical‘ ? item.h : plateThickness; return (
{/* Position Indicator Arrows for Single Items */} {isActive && ( <>
)} {renderBlock(item.w, item.d, plateH, item.x, item.y, item.z, isActive, item.id, „bg-white“, false, `plate-${item.id}`)}
); })}
{/* 2D Blueprint */}
{/* … Blueprint Header … */}
Blueprint
Raster: {gridStep} cm Active
{ if(dragState) saveToHistory(); setDragState(null); }} onMouseLeave={() => { if(dragState) saveToHistory(); setDragState(null); }} > {items.map(item => { const isActive = item.id === activeId; const coords = getBlueprintCoords(item); const shelfPositions = getShelfPositions(item); const compartments = getCompartments(item); return ( setActiveId(item.id)} onMouseDown={(e) => { if(isActive && !dragState) { e.stopPropagation(); setDragState({ type: ‚item‘, itemId: item.id }); } }}> {isActive && ( <> {coords.labelX}cm {coords.labelY}cm )} {item.type === ‚corpus‘ && blueprintView === ‚front‘ && ( {shelfPositions.map((pos, i) => { const shelfY = -item.y – plateThickness – pos; return ( { if(isActive) { e.stopPropagation(); setDragState({ type: ’shelf‘, itemId: item.id, shelfIndex: i }); } }} className=“hover:cursor-row-resize group“> ); })} {compartments.map((comp, i) => { const cH = comp.h; const cYRel = comp.yRelative; const rectY = -item.y – plateThickness – cYRel – cH; const numVerticals = item.verticals?.[i] || 0; return ( {numVerticals > 0 && Array.from({length: numVerticals}).map((_, v) => { const innerW = item.w – 2*plateThickness; const sectionWidth = (innerW – numVerticals * plateThickness) / (numVerticals + 1); const startX = item.x – innerW/2; const vX = startX + (v + 1) * sectionWidth + v * plateThickness + plateThickness/2; return ; })} ) })} )} ); })} {/* Axis Indicator */}
{blueprintView === ‚front‘ ? ‚X / Y (Front)‘ : blueprintView === ‚top‘ ? ‚X / Z (Top)‘ : ‚Z / Y (Side)‘}
Material: 16mmElemente: {items.length}Snapping: Active
Konstruktiver Flow aktiv.