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
+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 */}