Snapshot: Wand/Öffnung Multi-Surface-Select + Z-Drag + Brüstungs-Mitnahme

Stable working state after a long iteration session. The plugin now supports:
- Multi-Surface-Select für alle Element-Typen (Türen/Fenster/Treppen/Tragwerk)
- Wand-Z-Drag → unbound mode (UK/OK-Override, Wand vom Geschoss entkoppelt)
- Wand-Z-Drag nimmt verknüpfte Öffnungen mit (Brüstung += delta_z via Idle-Pfad)
- Öffnungs-XY-Drag snapt direktional auf Wand-Tangente
- Öffnungs-Z-Drag passt Brüstung an (Fenster sofort sync, Tür deferred)
- Wand-Delete kaskadiert Öffnungen (deferred via Idle, robust gegen _Rotate/_Move)
- Source-Cascade beim Öffnungs-Delete (deferred analog Wand-Kaskade)
- Listener-Cleanup robust gegen _reset_panels.py Reload (Refs in
  _dossier_runtime_event_refs gespeichert, vor Re-Install deregistriert)
- _count_same_id_type filtert IsDeleted (verhindert Source-Duplikat-Bug bei Move)
- Frontend: Brüstungs-Slider für Tür ("Schwelle"), Flügel-Block nur bei Fenster

Plus aus früherer Phase dieser Session:
- Dossier-Launcher Auto-Load via Rhinos StartupCommands-XML
- Default-Pfad zeigt auf gebundeltes startup.py (out-of-the-box für neue User)
- Splash-Window beim Plugin-Load mit native macOS rounded corners
- Diverse Launcher-Verbesserungen (Brüstungs-Default, tauri.conf, capabilities)

Known issue: bei Multi-Select-Move mit vielen Sub-Volumen kann sporadisch
"Unable to transform" auftreten (Rhinos Move-Operation kollidiert mit Wand-
Regen). Tür-spezifischer Defer-Pfad mildert das, Fenster läuft sync.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 01:50:45 +02:00
parent 1180d7bedf
commit 961b3c0396
52 changed files with 10760 additions and 765 deletions
+26 -6
View File
@@ -92,10 +92,21 @@ export default function App() {
setCombDialog(d => d ? { ...d, layers: layers || d.layers, presets: presets || [] } : d)
}
})
onMessage('FIRST_RUN', () => {
applyAll(INITIAL_ZEICHNUNGSEBENEN, INITIAL_EBENEN)
onMessage('FIRST_RUN', ({ defaultEbenen } = {}) => {
// Wenn der Dossier-Launcher ein eigenes Schema definiert hat, nutzen wir
// das statt der hardcoded INITIAL_EBENEN. Felder ohne `visible`/`locked`
// werden mit Defaults ergaenzt damit die UI-Komponenten keine undefineds
// sehen.
const useEbenen = (Array.isArray(defaultEbenen) && defaultEbenen.length)
? defaultEbenen.map(e => ({
visible: true, locked: false,
...e,
}))
: INITIAL_EBENEN
setEbenen(useEbenen)
applyAll(INITIAL_ZEICHNUNGSEBENEN, useEbenen)
setAppliedZ(INITIAL_ZEICHNUNGSEBENEN)
setAppliedE(INITIAL_EBENEN)
setAppliedE(useEbenen)
const active = INITIAL_ZEICHNUNGSEBENEN.find(zz => zz.id === activeId) || INITIAL_ZEICHNUNGSEBENEN[0]
if (active) {
setActiveZeichnungsebene(active)
@@ -136,13 +147,22 @@ export default function App() {
if (!f || !f.pattern || f.pattern === 'None') return ''
return [f.pattern, f.source || 'layer', f.color || '', f.scale ?? 1, f.rotation ?? 0].join('|')
}
// WICHTIG: alle Felder die das Backend braucht hier mit drin haben — sonst
// triggert Aenderung an z.B. hasClipping/schnitthoehe kein Apply, und das
// Backend sieht den neuen Stand nie. Frueher waren nur id/name/isGeschoss
// drin -> Clipping-Toggle blieb wirkungslos.
const zSig = (z) => [
z.id, z.name, z.isGeschoss ? 1 : 0,
z.hoehe ?? '', z.schnitthoehe ?? '',
z.hasClipping ? 1 : 0,
].join(':')
const structureKey = useMemo(() => (
zeichnungsebenen.map(z => `${z.id}:${z.name}:${z.isGeschoss ? 1 : 0}`).join(',') + '|' +
zeichnungsebenen.map(zSig).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(',') + '|' +
appliedZ.map(zSig).join(',') + '|' +
appliedE.map(e => `${e.code}:${e.name}:${fillSig(e)}`).join(',')
), [appliedZ, appliedE])
@@ -176,7 +196,7 @@ export default function App() {
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
!window.confirm(`"${name}" überschreiben?`)) return
saveCurrentAsCombination(name)
setActiveCombName(name)
}
+1 -1
View File
@@ -228,7 +228,7 @@ export default function DimensionenApp() {
<Icon name="aspect_ratio" size={32} style={{ color: 'var(--text-muted)', opacity: 0.5 }} />
<div style={{ marginTop: 8 }}>Keine Selektion.</div>
<div style={{ marginTop: 4, fontSize: 10 }}>
In Rhino ein oder mehrere Objekte auswaehlen.
In Rhino ein oder mehrere Objekte auswählen.
</div>
</div>
) : (
+693 -90
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -217,7 +217,7 @@ function PenLw({ sel }) {
<button
className="btn-icon-sm"
onClick={() => setLwSource(source === 'object' ? 'layer' : 'object', source === 'object' ? null : effective)}
title={source === 'object' ? 'Nach Ebene' : 'Uebersteuern'}
title={source === 'object' ? 'Nach Ebene' : 'Übersteuern'}
style={{ color: source === 'layer' ? 'var(--text-primary)' : 'var(--text-muted)' }}
>
<Icon name={source === 'layer' ? 'link' : 'link_off'} size={14} />
+5 -5
View File
@@ -203,9 +203,9 @@ export default function LayoutsApp() {
onClick: () => setLayoutFolder(l.id, '') },
{ divider: true },
] : []),
{ label: 'Loeschen', icon: 'delete', danger: true,
{ label: 'Löschen', icon: 'delete', danger: true,
onClick: () => {
if (window.confirm(`Layout "${l.name}" loeschen?`)) deleteLayout(l.id)
if (window.confirm(`Layout "${l.name}" löschen?`)) deleteLayout(l.id)
} },
]
@@ -217,7 +217,7 @@ export default function LayoutsApp() {
icon: collapsedFolders.has(folderName) ? 'expand_more' : 'expand_less',
onClick: () => toggleFolderCollapse(folderName) },
{ divider: true },
{ label: 'Alle ankreuzen / abwaehlen',
{ label: 'Alle ankreuzen / abwählen',
icon: 'check_box',
onClick: () => checkAllInFolder(items) },
{ label: `Ordner als PDF (${items.length})`,
@@ -545,7 +545,7 @@ export default function LayoutsApp() {
<Icon name="crop_landscape" size={24} style={{ color: 'var(--text-muted)', opacity: 0.5 }} />
<div style={{ marginTop: 6 }}>Keine Details auf diesem Layout.</div>
<div style={{ marginTop: 4, fontSize: 10 }}>
Oben <Icon name="add" size={11} /> klicken um eines hinzuzufuegen.
Oben <Icon name="add" size={11} /> klicken um eines hinzuzufügen.
</div>
</div>
) : (
@@ -723,7 +723,7 @@ function LayoutDialog({ mode, layout, onCancel, onSubmit }) {
style={{ flex: 1, fontFamily: 'DM Mono, monospace', fontSize: 11 }} />
<span style={{ color: 'var(--text-muted)', fontSize: 10 }}>×</span>
<input type="text" value={ch} onChange={(e) => setCh(e.target.value)}
placeholder="Hoehe"
placeholder="Höhe"
style={{ flex: 1, fontFamily: 'DM Mono, monospace', fontSize: 11 }} />
<span style={{ color: 'var(--text-muted)', fontSize: 10 }}>mm</span>
</div>
+6 -6
View File
@@ -168,7 +168,7 @@ export default function MassstabApp() {
value={dropdownValue}
onChange={(e) => applyDropdown(e.target.value)}
style={{ ...cellInput, width: 80 }}
title="Massstab waehlen"
title="Massstab wählen"
>
<option value="__none__">1:?</option>
{PRESETS.map(p => (
@@ -201,7 +201,7 @@ export default function MassstabApp() {
style={cellBtn}
title={appliedScale
? `Zoom auf eingestellten Massstab snappen (1:${appliedScale >= 10 ? Math.round(appliedScale) : appliedScale})`
: 'Erst einen Massstab waehlen'}
: 'Erst einen Massstab wählen'}
>100%</button>
<button onClick={zoomExtents} style={cellBtn} title="Auf gesamten Inhalt zoomen">
<Icon name="fit_screen" size={14} />
@@ -224,8 +224,8 @@ export default function MassstabApp() {
borderColor: state.showLineweights ? 'var(--accent)' : 'var(--border)',
}}
title={state.showLineweights
? 'Strichstaerken werden angezeigt (Print-View) — klicken zum Ausschalten'
: 'Strichstaerken als Hairlines (Edit-View) — klicken um Print-View zu zeigen'}
? 'Strichstärken werden angezeigt (Print-View) — klicken zum Ausschalten'
: 'Strichstärken als Hairlines (Edit-View) — klicken um Print-View zu zeigen'}
>
<Icon name="edit" size={14} style={{ display: state.showLineweights ? 'none' : 'inline-block' }} />
<Icon name="print" size={14} style={{ display: state.showLineweights ? 'inline-block' : 'none' }} />
@@ -268,7 +268,7 @@ export default function MassstabApp() {
boxShadow: '0 4px 12px rgba(0,0,0,0.15)', zIndex: 10, minWidth: 220,
}}>
<div style={{ fontSize: 10, color: 'var(--text-muted)' }}>
Bildschirm-Aufloesung fuer Massstab-Berechnung
Bildschirm-Auflösung für Massstab-Berechnung
</div>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input
@@ -284,7 +284,7 @@ export default function MassstabApp() {
<button
onClick={() => { detectMassstabDpi(); setDpiOpen(false) }}
style={{ ...cellBtn, fontSize: 10, justifyContent: 'flex-start' }}
title="DPI automatisch ueber EDID des Bildschirms ermitteln"
title="DPI automatisch über EDID des Bildschirms ermitteln"
>
<Icon name="auto_fix_high" size={12} /> Auto-Detect (EDID)
</button>
+16 -2
View File
@@ -7,6 +7,7 @@ import {
setMassstabDpi, detectMassstabDpi,
setView, setDisplayMode,
toggleOverrides, setOverridesPreset, openOverridesPanel,
openDossierSettings,
} from './lib/rhinoBridge'
const PRESETS = [
@@ -218,6 +219,19 @@ export default function OberleisteApp() {
>
DOSSIER <span style={{ opacity: 0.55 }}>{__APP_VERSION__}</span>
</span>
<button
onClick={() => openDossierSettings()}
title="Dossier-Einstellungen"
style={{
background: 'transparent', border: 'none', padding: '2px 4px',
cursor: 'pointer', color: 'var(--text-muted)',
display: 'flex', alignItems: 'center', flexShrink: 0,
}}
onMouseEnter={(e) => e.currentTarget.style.color = 'var(--text-primary)'}
onMouseLeave={(e) => e.currentTarget.style.color = 'var(--text-muted)'}
>
<Icon name="settings" size={14} />
</button>
<div style={sep} />
{/* ====== GRUPPE: VIEW ====== */}
<span style={groupLabel}>View</span>
@@ -302,7 +316,7 @@ export default function OberleisteApp() {
onClick={apply100}
disabled={isPerspective || !appliedScale}
label="100%"
title={appliedScale ? `Zoom auf eingestellten Massstab snappen (1:${appliedScale >= 10 ? Math.round(appliedScale) : appliedScale})` : 'Erst einen Massstab waehlen'}
title={appliedScale ? `Zoom auf eingestellten Massstab snappen (1:${appliedScale >= 10 ? Math.round(appliedScale) : appliedScale})` : 'Erst einen Massstab wählen'}
/>
<button className="btn-icon" onClick={zoomExtents}
style={pillIconBtn}
@@ -318,7 +332,7 @@ export default function OberleisteApp() {
onClick={() => setShowLineweights(!state.showLineweights)}
active={state.showLineweights}
label={state.showLineweights ? 'Print' : 'Edit'}
title={state.showLineweights ? 'Print-View aktiv — klick zum Ausschalten' : 'Strichstaerken anzeigen (Print-View)'}
title={state.showLineweights ? 'Print-View aktiv — klick zum Ausschalten' : 'Strichstärken anzeigen (Print-View)'}
icon={state.showLineweights ? 'print' : 'edit'}
/>
+5 -5
View File
@@ -436,10 +436,10 @@ export default function OverridesApp() {
icon: 'content_copy',
onClick: () => duplicateRule(ruleId) },
{ divider: true },
{ label: 'Loeschen',
{ label: 'Löschen',
icon: 'delete', danger: true,
onClick: () => {
if (window.confirm(`Regel "${rule.name || '(ohne Name)'}" loeschen?`)) deleteRule(ruleId)
if (window.confirm(`Regel "${rule.name || '(ohne Name)'}" löschen?`)) deleteRule(ruleId)
} },
]
}
@@ -516,10 +516,10 @@ export default function OverridesApp() {
if (selectedPreset) { savePreset(selectedPreset); return }
const existing = (state.presets || []).map(p => p.name)
const def = `Kombination ${existing.length + 1}`
const name = window.prompt('Name fuer neue Kombination:', def)
const name = window.prompt('Name für neue Kombination:', def)
if (!name || !name.trim()) return
const t = name.trim()
if (existing.includes(t) && !window.confirm(`Kombination "${t}" ueberschreiben?`)) return
if (existing.includes(t) && !window.confirm(`Kombination "${t}" überschreiben?`)) return
savePreset(t)
setSelectedPreset(t)
}}
@@ -527,7 +527,7 @@ export default function OverridesApp() {
className="btn-outlined"
style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}
title={selectedPreset
? `Aenderungen in "${selectedPreset}" speichern`
? `Änderungen in "${selectedPreset}" speichern`
: 'Aktuelle Regeln als neue Kombination speichern'}
>
<Icon name="save" size={14} />
+6 -6
View File
@@ -24,9 +24,9 @@ const TOOLS = {
['flip', 'Mirror', '_Mirror', 'Spiegeln'],
['padding', 'Offset', '_Offset', 'Parallelversatz'],
['content_cut', 'Trim', '_Trim', 'Stutzen'],
['swipe_right_alt', 'Extend', '_Extend', 'Verlaengern'],
['swipe_right_alt', 'Extend', '_Extend', 'Verlängern'],
['link', 'Join', '_Join', 'Verbinden'],
['scatter_plot', 'Explode', '_Explode', 'Aufloesen'],
['scatter_plot', 'Explode', '_Explode', 'Auflösen'],
['rounded_corner', 'Fillet', '_Fillet', 'Verrunden (Ecke abrunden)'],
['apps', 'Array', '_ArrayPolar','Polar-Array'],
],
@@ -41,11 +41,11 @@ const TOOLS = {
['unfold_more', 'Loft', '_Loft', 'Loft (Kurven verbinden)'],
],
'Auswahl': [
['add_link', 'Chain', '_SelChain', 'Tangentiale Kurvenkette waehlen'],
['filter_alt', 'Dup', '_SelDup', 'Doppelte Objekte waehlen'],
['loop', 'Closed', '_SelClosedCrv', 'Geschlossene Kurven waehlen'],
['add_link', 'Chain', '_SelChain', 'Tangentiale Kurvenkette wählen'],
['filter_alt', 'Dup', '_SelDup', 'Doppelte Objekte wählen'],
['loop', 'Closed', '_SelClosedCrv', 'Geschlossene Kurven wählen'],
['compare_arrows', 'Invert', '_Invert', 'Auswahl invertieren'],
['select_all', 'All', '_SelAll', 'Alle auswaehlen'],
['select_all', 'All', '_SelAll', 'Alle auswählen'],
['deselect', 'None', '_SelNone', 'Auswahl aufheben'],
],
}
+176 -4
View File
@@ -36,13 +36,29 @@ export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'],
color: null,
scale: 1.0,
rotation: 0,
lw: null, // Stiftstaerke der Hatch in mm; null = wie Stift der Ebene
lw: null,
...(ebene.fill || {}),
},
// Section Style: was Rhino bei Clipping-Plane-Schnitten anzeigt.
// Spiegelt das native Section-Style-Dialog (Options → Layer → Section Style).
section: {
hatchPattern: 'None', // None / Solid / <name>
hatchColor: null, // null = ByObject (Layer-Farbe)
hatchRotation: 0,
hatchScale: 1.0,
background: 'viewport', // viewport / object
boundaryShow: true,
boundaryLinetype: 'byLayer',
boundaryWidthScale: 1.0,
boundaryColor: null, // null = ByObject
sectionOpenObjects: true,
...(ebene.section || {}),
},
})
const set = (patch) => setDraft({ ...draft, ...patch })
const setFill = (patch) => setDraft({ ...draft, fill: { ...draft.fill, ...patch } })
const set = (patch) => setDraft({ ...draft, ...patch })
const setFill = (patch) => setDraft({ ...draft, fill: { ...draft.fill, ...patch } })
const setSection = (patch) => setDraft({ ...draft, section: { ...draft.section, ...patch } })
const fill = draft.fill
const isFilled = fill.pattern !== 'None'
@@ -50,6 +66,12 @@ export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'],
const fillFromLayer = fill.source === 'layer'
const previewColor = (fillFromLayer || !fill.color) ? draft.color : fill.color
const sec = draft.section
const secHatched = sec.hatchPattern !== 'None'
const secHatchPat = secHatched && sec.hatchPattern !== 'Solid'
const secHatchColor = sec.hatchColor || draft.color
const secBoundColor = sec.boundaryColor || draft.color
// Pattern-Optionen: None + Solid + Patterns
const patternOptions = [
'None', 'Solid',
@@ -208,7 +230,7 @@ export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'],
<Field
label="STIFTSTÄRKE (mm)"
hint="Leer = wie Stift der Ebene. Eigener Wert ueberschreibt die Strichstaerke der Hatch-Linien."
hint="Leer = wie Stift der Ebene. Eigener Wert überschreibt die Strichstärke der Hatch-Linien."
>
<input
type="number" step="0.01" min="0"
@@ -233,6 +255,156 @@ export default function EbenenSettingsDialog({ ebene, hatchPatterns = ['Solid'],
</Field>
</>
)}
{/* === SECTION STYLE (Clipping-Plane-Schnitt) === */}
<SectionLabel>Section Style (Clipping-Schnitt)</SectionLabel>
<span style={{ fontSize: 9, color: 'var(--text-muted)', lineHeight: 1.4, display: 'block', marginBottom: 4 }}>
Wird gezeigt wenn ein Objekt auf dieser Ebene von einer Clipping-Plane geschnitten wird.
</span>
<Field label="HATCH PATTERN">
<select
value={sec.hatchPattern}
onChange={(ev) => setSection({ hatchPattern: ev.target.value })}
style={{ flex: 1, fontSize: 11, minWidth: 0 }}
>
{patternOptions.map(p => (
<option key={p} value={p}>{p}</option>
))}
</select>
</Field>
{secHatched && (
<>
<Field label="HATCH FARBE" hint="Leer = Stift der Ebene (By Object)">
<input
type="color"
value={secHatchColor}
onChange={(ev) => setSection({ hatchColor: ev.target.value })}
style={{
width: 32, height: 22, padding: 0,
border: '1px solid var(--border)', borderRadius: 'var(--r)',
cursor: 'pointer', background: 'transparent',
}}
/>
<span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', flex: 1 }}>
{sec.hatchColor || 'By Object'}
</span>
{sec.hatchColor && (
<button
className="btn-text"
style={{ fontSize: 10, padding: '2px 6px' }}
onClick={() => setSection({ hatchColor: null })}
>×</button>
)}
</Field>
{secHatchPat && (
<>
<Field label="HATCH SKALIERUNG">
<input
type="number" step="0.05" min="0.001"
value={sec.hatchScale}
onChange={(ev) => setSection({ hatchScale: parseFloat(ev.target.value) || 1.0 })}
style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }}
/>
</Field>
<Field label="HATCH DREHUNG (°)">
<input
type="number" step="5"
value={sec.hatchRotation}
onChange={(ev) => setSection({ hatchRotation: parseFloat(ev.target.value) || 0 })}
style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }}
/>
</Field>
</>
)}
<Field label="HINTERGRUND">
<select
value={sec.background}
onChange={(ev) => setSection({ background: ev.target.value })}
style={{ flex: 1, fontSize: 11, minWidth: 0 }}
>
<option value="viewport">Viewport (transparent)</option>
<option value="object">By Object (Layer-Farbe)</option>
</select>
</Field>
</>
)}
{/* --- Boundary --- */}
<div style={{ marginTop: 8, padding: '4px 0',
borderTop: '1px dashed var(--border-light)' }}>
<span style={{ fontSize: 9, color: 'var(--text-muted)',
letterSpacing: 0.4, textTransform: 'uppercase' }}>
Boundary (Schnittkante)
</span>
</div>
<Field label="ZEIGE BOUNDARY">
<input
type="checkbox"
checked={sec.boundaryShow}
onChange={(ev) => setSection({ boundaryShow: ev.target.checked })}
style={{ marginRight: 6 }}
/>
<span style={{ fontSize: 10, color: 'var(--text-muted)', flex: 1 }}>
{sec.boundaryShow ? 'Schnittkante wird gezeichnet' : 'Schnittkante unsichtbar'}
</span>
</Field>
{sec.boundaryShow && (
<>
<Field label="BOUNDARY FARBE" hint="Leer = Stift der Ebene (By Object)">
<input
type="color"
value={secBoundColor}
onChange={(ev) => setSection({ boundaryColor: ev.target.value })}
style={{
width: 32, height: 22, padding: 0,
border: '1px solid var(--border)', borderRadius: 'var(--r)',
cursor: 'pointer', background: 'transparent',
}}
/>
<span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', flex: 1 }}>
{sec.boundaryColor || 'By Object'}
</span>
{sec.boundaryColor && (
<button
className="btn-text"
style={{ fontSize: 10, padding: '2px 6px' }}
onClick={() => setSection({ boundaryColor: null })}
>×</button>
)}
</Field>
<Field
label="BOUNDARY WIDTH SCALE"
hint="Multiplikator auf die Ebenen-Stiftstärke. 1 = wie Ebene, 3 = 3× dicker."
>
<input
type="number" step="0.5" min="0.1" max="20"
value={sec.boundaryWidthScale}
onChange={(ev) => setSection({ boundaryWidthScale: parseFloat(ev.target.value) || 1.0 })}
style={{ flex: 1, fontSize: 11, textAlign: 'right', minWidth: 0 }}
/>
</Field>
</>
)}
<Field label="OFFENE OBJEKTE">
<input
type="checkbox"
checked={sec.sectionOpenObjects}
onChange={(ev) => setSection({ sectionOpenObjects: ev.target.checked })}
style={{ marginRight: 6 }}
/>
<span style={{ fontSize: 10, color: 'var(--text-muted)', flex: 1 }}>
Auch nicht-geschlossene Geometrie schneiden
</span>
</Field>
</div>
{/* Footer */}
+11 -1
View File
@@ -142,7 +142,17 @@ export default function GeschossSettingsDialog({ geschoss, onSave, onClose }) {
}}>
<div style={{ flex: 1 }} />
<button className="btn-text" onClick={onClose}>Abbrechen</button>
<button className="btn-contained" onClick={() => onSave(draft)}>Übernehmen</button>
<button className="btn-contained" onClick={() => {
// Numerische Felder NIEMALS als undefined/null rausgehen lassen —
// sonst crasht der Plugin spaeter beim float()-Cast. Defaults
// entsprechen den Werten die das UI auch ohne User-Input zeigt.
const out = { ...draft }
if (out.isGeschoss) {
if (out.hoehe == null) out.hoehe = 3.0
if (out.schnitthoehe == null) out.schnitthoehe = 1.0
}
onSave(out)
}}>Übernehmen</button>
</div>
</div>
</div>
+12
View File
@@ -172,6 +172,13 @@ export function runCommand(cmd) { send('RUN_COMMAND', { cmd }) }
export function sendKeys(text, enter) { send('SEND_KEYS', { text, enter: enter !== false }) }
export function cancelCommand() { send('CANCEL_COMMAND', {}) }
export function toggleRhinoCmdLine() { send('TOGGLE_RHINO_CMD_LINE', {}) }
export function getDossierSettings() { send('GET_SETTINGS', {}) }
export function openDossierSettings() { send('OPEN_SETTINGS', {}) }
export function applyWindowLayout(name) { send('APPLY_LAYOUT', { name }) }
export function saveLayoutPref(name, autoApply) {
send('SAVE_LAYOUT_PREF',
{ defaultLayout: name || '', autoApplyLayout: !!autoApply })
}
// --- Overrides-Panel ---
export function setOverridesEnabled(on) { send('SET_ENABLED', { enabled: !!on }) }
@@ -234,7 +241,12 @@ export function createDecke(p) { send('CREATE_DECKE', p || {}) }
export function createDach(p) { send('CREATE_DACH', p || {}) }
export function createFenster(p) { send('CREATE_FENSTER', p || {}) }
export function createTuer(p) { send('CREATE_TUER', p || {}) }
export function createAussparung(p) { send('CREATE_AUSSPARUNG', p || {}) }
export function createTreppe(p) { send('CREATE_TREPPE', p || {}) }
export function createStuetze(p) { send('CREATE_STUETZE', p || {}) }
export function createTraeger(p) { send('CREATE_TRAEGER', p || {}) }
export function createRaum(p) { send('CREATE_RAUM', p || {}) }
export function exportRaeume() { send('EXPORT_RAEUME', {}) }
export function updateElement(id, patch) { send('UPDATE_ELEMENT', { id, ...(patch || {}) }) }
export function deleteElement(id) { send('DELETE_ELEMENT', { id }) }
// Backwards-Compat-Aliases