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:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user