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:
+26
-6
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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'}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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'],
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user