import { useState, useEffect, useMemo, useRef } from 'react' import GeschossManager from './components/GeschossManager' import EbenenManager from './components/EbenenManager' import AusschnittLayerDialog from './components/AusschnittLayerDialog' import { applyAll, setActiveZeichnungsebene, setActiveEbene, onMessage, notifyReady, applyVisibility, getCombination, applyCombination, saveCurrentAsCombination, deleteCombinationPreset, saveCombinationPreset, } from './lib/rhinoBridge' export function recalcOkff(list) { let acc = 0 return list.map(z => { if (z.isGeschoss) { const next = { ...z, okff: parseFloat(acc.toFixed(3)) } acc += (z.hoehe ?? 3.0) return next } return { ...z, okff: undefined } }) } const INITIAL_ZEICHNUNGSEBENEN = recalcOkff([ { id: 'eg', name: 'EG', isGeschoss: true, hoehe: 3.50, schnitthoehe: 1.00, visible: true }, { id: '1og', name: '1OG', isGeschoss: true, hoehe: 3.00, schnitthoehe: 1.00, visible: true }, { id: '2og', name: '2OG', isGeschoss: true, hoehe: 3.00, schnitthoehe: 1.00, visible: true }, ]) const INITIAL_EBENEN = [ { code: '00', name: 'RASTER', color: '#484850', lw: 0.13, visible: true, locked: false }, { code: '01', name: 'VERMESSUNG', color: '#707078', lw: 0.18, visible: true, locked: false }, { code: '10', name: 'SITUATION', color: '#909090', lw: 0.18, visible: true, locked: false }, { code: '11', name: 'STRASSE', color: '#a89070', lw: 0.18, visible: true, locked: false }, { code: '12', name: 'GEBÄUDE', color: '#888888', lw: 0.25, visible: true, locked: false }, { code: '13', name: 'BÄUME', color: '#50a050', lw: 0.13, visible: true, locked: false }, { code: '14', name: 'HÖHENLINIEN', color: '#909050', lw: 0.18, visible: true, locked: false }, { code: '20', name: 'WÄNDE', color: '#0a0a0a', lw: 0.50, visible: true, locked: false }, { code: '21', name: 'TÜREN_FENSTER', color: '#5080c8', lw: 0.25, visible: true, locked: false }, { code: '22', name: 'MÖBEL', color: '#909090', lw: 0.13, visible: true, locked: false }, { code: '25', name: 'STÜTZEN', color: '#c87050', lw: 0.50, visible: true, locked: false }, { code: '30', name: 'DECKEN', color: '#605850', lw: 0.35, visible: true, locked: false }, { code: '31', name: 'DÄCHER', color: '#7a4a3a', lw: 0.35, visible: true, locked: false }, { code: '35', name: 'TRÄGER', color: '#a87858', lw: 0.50, visible: true, locked: false }, { code: '50', name: 'TEXT', color: '#d0d0d0', lw: 0.13, visible: true, locked: false }, { code: '60', name: 'PLANGRAFIK', color: '#c0a040', lw: 0.13, visible: true, locked: false }, { code: '90', name: 'REFERENZEN', color: '#585860', lw: 0.13, visible: true, locked: false }, { code: '99', name: 'KONSTRUKTION', color: '#404048', lw: 0.13, visible: true, locked: false }, ] export default function App() { const [zeichnungsebenen, setZeichnungsebenen] = useState(INITIAL_ZEICHNUNGSEBENEN) const [ebenen, setEbenen] = useState(INITIAL_EBENEN) const [activeId, setActiveId] = useState('eg') const [activeCode, setActiveCode] = useState('20') const [appliedZ, setAppliedZ] = useState(INITIAL_ZEICHNUNGSEBENEN) const [appliedE, setAppliedE] = useState(INITIAL_EBENEN) const [zMode, setZMode] = useState('active') const [eMode, setEMode] = useState('all') const [hatchPatterns, setHatchPatterns] = useState(['Solid', 'Hatch1', 'Hatch2', 'Hatch3', 'Plus', 'Squares', 'Grid', 'Grid60']) // Ebenenkombinationen (geteilter Store mit Ausschnitten) const [combinations, setCombinations] = useState([]) // Liste { name, layers } const [activeCombName, setActiveCombName] = useState(null) // null = "Eigene" // Dialog fuer "alle bearbeiten" (Pencil-Button) const [combDialog, setCombDialog] = useState(null) // { layers, presets } oder null const wantCombDialogRef = useRef(false) useEffect(() => { onMessage('STATE_SYNC', ({ zeichnungsebenen: z, ebenen: e, hatchPatterns: hp }) => { if (z) { const r = recalcOkff(z); setZeichnungsebenen(r); setAppliedZ(r) const active = r.find(zz => zz.id === activeId) || r[0] if (active) { setActiveZeichnungsebene(active) // Auch den Sublayer-Code aktiv setzen, damit Rhino's Current-Layer // beim Panel-Start sofort auf der Wahl im Panel landet (sonst bleibt // "Default" und neue Objekte landen dort). if (activeCode) setActiveEbene(activeCode) } } if (e) { setEbenen(e); setAppliedE(e) } if (Array.isArray(hp) && hp.length > 0) setHatchPatterns(hp) }) onMessage('COMBINATION_DATA', ({ layers, presets }) => { setCombinations(presets || []) if (wantCombDialogRef.current) { wantCombDialogRef.current = false setCombDialog({ layers: layers || [], presets: presets || [] }) } else if (combDialog) { // Dialog ist offen — Layer-Liste live aktualisieren (z.B. nach Preset-Save) setCombDialog(d => d ? { ...d, layers: layers || d.layers, presets: presets || [] } : d) } }) onMessage('FIRST_RUN', () => { applyAll(INITIAL_ZEICHNUNGSEBENEN, INITIAL_EBENEN) setAppliedZ(INITIAL_ZEICHNUNGSEBENEN) setAppliedE(INITIAL_EBENEN) const active = INITIAL_ZEICHNUNGSEBENEN.find(zz => zz.id === activeId) || INITIAL_ZEICHNUNGSEBENEN[0] if (active) { setActiveZeichnungsebene(active) if (activeCode) setActiveEbene(activeCode) } }) notifyReady() // Initial Liste der Kombinationen holen setTimeout(() => getCombination(), 200) // Native Browser-Context-Menu global unterdruecken const blockContext = (ev) => ev.preventDefault() document.addEventListener('contextmenu', blockContext) return () => document.removeEventListener('contextmenu', blockContext) }, []) // Sichtbarkeit live anwenden — bei relevanten Aenderungen const visibilityKey = useMemo(() => ( activeId + '|' + activeCode + '|' + zMode + '|' + eMode + '|' + zeichnungsebenen.map(z => `${z.id}:${z.visible !== false ? 1 : 0}`).join(',') + '|' + ebenen.map(e => `${e.code}:${e.visible !== false ? 1 : 0}:${e.locked === true ? 1 : 0}`).join(',') ), [activeId, activeCode, zMode, eMode, zeichnungsebenen, ebenen]) useEffect(() => { const activeZ = zeichnungsebenen.find(z => z.id === activeId) if (activeZ) applyVisibility(activeZ, zeichnungsebenen, activeCode, ebenen, zMode, eMode) // eslint-disable-next-line react-hooks/exhaustive-deps }, [visibilityKey]) // Auto-Apply bei strukturellen Aenderungen (add/remove/rename) UND // wenn die Ebenen-Settings (z.B. fill) sich aendern — Python braucht den // neuen Stand in doc.Strings damit Auto-Fill und 'Nach Ebene' korrekt lesen. // Kanonische Signatur: leere/None-Fills sind alle aequivalent — sonst loest // schon das blosse Oeffnen+Schliessen des Settings-Dialogs ein applyAll aus // (Dialog initialisiert fill mit Default-Werten). const fillSig = (e) => { const f = e.fill if (!f || !f.pattern || f.pattern === 'None') return '' return [f.pattern, f.source || 'layer', f.color || '', f.scale ?? 1, f.rotation ?? 0].join('|') } const structureKey = useMemo(() => ( zeichnungsebenen.map(z => `${z.id}:${z.name}:${z.isGeschoss ? 1 : 0}`).join(',') + '|' + ebenen.map(e => `${e.code}:${e.name}:${fillSig(e)}`).join(',') ), [zeichnungsebenen, ebenen]) const appliedStructureKey = useMemo(() => ( appliedZ.map(z => `${z.id}:${z.name}:${z.isGeschoss ? 1 : 0}`).join(',') + '|' + appliedE.map(e => `${e.code}:${e.name}:${fillSig(e)}`).join(',') ), [appliedZ, appliedE]) useEffect(() => { if (structureKey === appliedStructureKey) return const t = setTimeout(() => { applyAll(zeichnungsebenen, ebenen) setAppliedZ(zeichnungsebenen) setAppliedE(ebenen) }, 200) return () => clearTimeout(t) // eslint-disable-next-line react-hooks/exhaustive-deps }, [structureKey, appliedStructureKey]) // --- Ebenen-Kombinationen ---------------------------------------------- const handlePickCombination = (name) => { if (!name) { setActiveCombName(null); return } const preset = combinations.find(p => p.name === name) if (!preset) return // Eye-State bevorzugen wenn im Preset vorhanden (= verlustfreie Wiederherstellung, // beruecksichtigt z_mode/e_mode); fallback auf doc.Layer-Liste fuer alte Presets. applyCombination({ layers: preset.layers || [], dossierEbenen: preset.dossierEbenen, dossierZeichnungsebenen: preset.dossierZeichnungsebenen, }) setActiveCombName(name) } const handleSaveCurrentCombination = () => { const suggested = activeCombName || `Kombi ${combinations.length + 1}` const name = (window.prompt('Name für Ebenenkombination:', suggested) || '').trim() if (!name) return if (combinations.some(p => p.name === name) && !window.confirm(`"${name}" ueberschreiben?`)) return saveCurrentAsCombination(name) setActiveCombName(name) } const handleDeleteCombination = (name) => { if (!name) return if (!window.confirm(`Ebenenkombination "${name}" löschen?`)) return deleteCombinationPreset(name) if (activeCombName === name) setActiveCombName(null) } const handleOpenCombDialog = () => { wantCombDialogRef.current = true getCombination() } // Wenn der User Sichtbarkeit/Lock manuell aendert -> "Eigene". // Wird direkt von EbenenManager aufgerufen, kein Effect-Race. const handleUserVisibilityChange = () => { if (activeCombName !== null) setActiveCombName(null) } const handleActiveChange = (id) => { setActiveId(id) const z = zeichnungsebenen.find(x => x.id === id) if (z) { setActiveZeichnungsebene(z) if (activeCode) setActiveEbene(activeCode) } } return (
setZeichnungsebenen(recalcOkff(updated))} recalcOkff={recalcOkff} mode={zMode} onModeChange={setZMode} /> { setActiveCode(code); setActiveEbene(code) }} onChange={setEbenen} mode={eMode} onModeChange={setEMode} hatchPatterns={hatchPatterns} combinations={combinations} activeCombName={activeCombName} onPickCombination={handlePickCombination} onSaveCurrentCombination={handleSaveCurrentCombination} onDeleteCombination={handleDeleteCombination} onEditCombinations={handleOpenCombDialog} onUserVisibilityChange={handleUserVisibilityChange} />
{combDialog && ( setCombDialog(null)} onSave={(layers) => { applyCombination(layers) setActiveCombName(null) setCombDialog(null) }} onSavePreset={(name, layers) => { saveCombinationPreset(name, layers) setCombDialog(d => d ? { ...d, presets: [...d.presets.filter(p => p.name !== name), { name, layers }], } : d) }} onDeletePreset={(name) => { deleteCombinationPreset(name) setCombDialog(d => d ? { ...d, presets: d.presets.filter(p => p.name !== name), } : d) if (activeCombName === name) setActiveCombName(null) }} /> )}
) }