Zeichnungsmanager Master-Controls + Scheren + Startup-Perf + Oeffnung-Preview

UI:
- GeschossManager: Master-Eye + Master-Lock im Header (analog EbenenManager).
  Scheren-Button pro Geschoss togglet hasClipping. Auge ganz links wie bei
  Ebenen. Eye-Logik klar 4-Wege: aktive Z immer hell+on, in 'active'/'all_force'
  fuer non-active gedimmt, sonst spiegelt Flag direkt. Schrift wird NIE gegrayt.
  Neuer Mode 'all_force' = "Alle anzeigen" (ignoriert Eye), 'all' jetzt mit
  Label "Ausgewaehlte" (respektiert Eye). Klick aufs Auge in 'active'/'all_force'
  wechselt automatisch in "Ausgewaehlte" damit Aktion sofort wirkt.

- layer_builder.apply_visibility: neuer z_mode 'all_force' vor visible-Check —
  zeigt jede Z auch wenn Eye=false war.

- elemente._cmd_create_oeffnung: gruene Live-Preview (vertikales Oeffnungs-
  Rechteck + Breiten-Marker + Diagonale) waehrend Fenster/Tuer-Platzierung
  entlang der Wand-Achse. Brueest-Offset aus Geschoss-UK korrekt im Z.

Performance Cold-Start:
- panel_base: Inlined-HTML als Modul-Cache (1x build, n-mal mount). Pro
  Panel-Mount nur noch str.replace + LoadHtml. Spart bei 10 Panels 9x
  ~395 KB Disk-Read + Regex-Pass. Cache-Key = mtime von dist/index.html.

- Timing-Instrumentierung: _t_mark + print_startup_summary. Hook in startup.py
  feuert 3s nach Plugin-Load + listet Wall-time, Top-10, Aggregat pro Phase.

- OberleisteBridge: Command-Enumeration (~1000 Commands) jetzt lazy via
  Rhino.RhinoApp.Idle statt synchron im __init__. Cold-Start nicht blockiert,
  Autocomplete kommt ~1 Frame spaeter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 04:36:56 +02:00
parent 95031ee2c0
commit 222b00c113
6 changed files with 325 additions and 48 deletions
+103 -22
View File
@@ -9,10 +9,38 @@ function GeschossBadge({ name }) {
function ZeichnungsebeneRow({
z, active, mode, onClick, onContextMenu,
onToggleVisible, onToggleLock, onDelete,
onToggleVisible, onToggleLock, onToggleClipping, onDelete,
}) {
const eyeShown = mode !== 'active'
const isGeschoss = !!z.isGeschoss
// Eye-Logik: die aktive Z ist IMMER sichtbar (Backend forciert das), also
// zeigen wir ihr Auge immer als "an" — ohne Ruecksicht aufs visible-Flag.
// Nicht-aktive: in 'all_force' ist visible-Flag ueberschrieben (alle an),
// in 'active' ueberschrieben (alle aus) — Auge dimmt. Sonst (Ausgewaehlte/
// grey) reflektiert es das Flag direkt.
let eyeIcon, eyeOn, eyeOpacity, eyeTitle
if (active) {
eyeIcon = 'visibility'
eyeOn = true
eyeOpacity = 1
eyeTitle = z.visible !== false
? 'Sichtbar (aktive Zeichnungsebene)'
: 'Normalerweise ausgeblendet — wird gezeigt weil aktiv'
} else if (mode === 'all_force') {
eyeIcon = 'visibility'
eyeOn = true
eyeOpacity = 0.35
eyeTitle = 'Im „Alle anzeigen"-Mode immer sichtbar — Klick wechselt in „Ausgewählte"'
} else if (mode === 'active') {
eyeIcon = z.visible !== false ? 'visibility' : 'visibility_off'
eyeOn = false
eyeOpacity = 0.35
eyeTitle = 'Im „Nur aktive"-Mode ausgeblendet — Klick wechselt in „Ausgewählte"'
} else {
eyeIcon = z.visible !== false ? 'visibility' : 'visibility_off'
eyeOn = z.visible !== false
eyeOpacity = 1
eyeTitle = z.visible !== false ? 'Ausblenden' : 'Einblenden'
}
return (
<div
onClick={onClick}
@@ -21,18 +49,22 @@ function ZeichnungsebeneRow({
display: 'flex', alignItems: 'center', gap: 6,
padding: '4px 12px',
margin: active ? '1px 6px' : '0',
background: active ? 'var(--active-dim)'
: (z.visible !== false) ? 'var(--bg-item)'
: 'var(--bg-panel)',
background: active ? 'var(--active-dim)' : 'var(--bg-item)',
borderRadius: active ? 999 : 0,
borderLeft: active ? 'none' : '3px solid transparent',
borderBottom: active ? 'none' : '1px solid var(--border-light)',
boxShadow: active ? 'inset 0 0 0 1px var(--active-light)' : 'none',
cursor: 'pointer',
userSelect: 'none',
opacity: (!active && z.visible === false && mode !== 'all') ? 0.45 : 1,
}}
>
<button
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
title={eyeTitle}
style={{ opacity: eyeOpacity }}
><Icon name={eyeIcon} size={14} /></button>
<span style={{
fontWeight: active ? 700 : 500,
fontSize: 12,
@@ -49,22 +81,15 @@ function ZeichnungsebeneRow({
{isGeschoss && <GeschossBadge name={z.name} />}
{isGeschoss && z.hasClipping && (
<Icon name="content_cut" size={12} style={{ color: 'var(--accent)', flexShrink: 0 }} title="Clipping Plane aktiv" />
)}
{eyeShown ? (
{isGeschoss ? (
<button
className={`btn-icon-sm ${z.visible !== false ? 'is-on' : ''}`}
onClick={(ev) => { ev.stopPropagation(); onToggleVisible() }}
title={
active
? (z.visible !== false
? 'Normalerweise sichtbar (aktive Zeichnungsebene wird trotzdem gezeigt)'
: 'Normalerweise ausgeblendet — wird nur sichtbar weil aktiv')
: (z.visible !== false ? 'Ausblenden' : 'Einblenden')
}
><Icon name={z.visible !== false ? 'visibility' : 'visibility_off'} size={14} /></button>
className={`btn-icon-xs ${z.hasClipping ? 'is-on' : ''}`}
onClick={(ev) => { ev.stopPropagation(); onToggleClipping() }}
title={z.hasClipping
? 'Clipping Plane ausschalten'
: 'Clipping Plane einschalten (Schnitt auf Schnitthöhe)'}
style={{ color: z.hasClipping ? 'var(--accent)' : undefined }}
><Icon name="content_cut" size={12} /></button>
) : (
<span style={{ width: 18, flexShrink: 0 }} />
)}
@@ -86,7 +111,8 @@ function ZeichnungsebeneRow({
}
const MODES = [
{ value: 'all', label: 'Alle anzeigen' },
{ value: 'all_force', label: 'Alle anzeigen' },
{ value: 'all', label: 'Ausgewählte' },
{ value: 'active', label: 'Nur aktive' },
{ value: 'grey', label: 'Andere grau' },
{ value: 'grey_locked', label: 'Andere grau & gesperrt' },
@@ -119,12 +145,20 @@ export default function GeschossManager({
const toggleVisible = (id) => {
onChange(zeichnungsebenen.map(z => z.id === id ? { ...z, visible: !(z.visible !== false) } : z))
// In "active" / "all_force" greift visible-Flag nicht — wer aufs Auge
// klickt will offensichtlich Sichtbarkeit kontrollieren, also direkt
// in den "Ausgewählte"-Mode wechseln damit die Aktion wirkt.
if (mode === 'active' || mode === 'all_force') onModeChange('all')
}
const toggleLock = (id) => {
onChange(zeichnungsebenen.map(z => z.id === id ? { ...z, locked: !z.locked } : z))
}
const toggleClipping = (id) => {
onChange(zeichnungsebenen.map(z => z.id === id ? { ...z, hasClipping: !z.hasClipping } : z))
}
const duplicate = (id) => {
const src = zeichnungsebenen.find(z => z.id === id)
if (!src) return
@@ -211,6 +245,52 @@ export default function GeschossManager({
</span>
</div>
{/* Master-Row: Master-Eye links + Master-Lock rechts (analog
EbenenManager). */}
<div style={{
display: 'flex', alignItems: 'center', gap: 5,
padding: '2px 14px',
background: 'var(--bg-section)',
borderBottom: '1px solid var(--border)',
}}>
<button
className="btn-icon-xs"
onClick={() => {
const anyVisible = zeichnungsebenen.some(z => z.visible !== false)
onChange(zeichnungsebenen.map(z => ({ ...z, visible: !anyVisible })))
if (mode === 'active' || mode === 'all_force') onModeChange('all')
}}
title={zeichnungsebenen.every(z => z.visible !== false)
? 'Alle Zeichnungsebenen ausblenden'
: 'Alle Zeichnungsebenen einblenden'}
style={{ width: 18, height: 18,
opacity: (mode === 'active' || mode === 'all_force') ? 0.5 : 1 }}
>
<Icon
name={zeichnungsebenen.every(z => z.visible !== false) ? 'visibility' : 'visibility_off'}
size={12}
/>
</button>
<span style={{ flex: 1 }} />
<button
className="btn-icon-xs"
onClick={() => {
const anyLocked = zeichnungsebenen.some(z => z.locked === true)
onChange(zeichnungsebenen.map(z => ({ ...z, locked: !anyLocked })))
}}
title={zeichnungsebenen.every(z => z.locked === true)
? 'Alle Zeichnungsebenen entsperren'
: 'Alle Zeichnungsebenen sperren'}
style={{ width: 18, height: 18 }}
>
<Icon
name={zeichnungsebenen.every(z => z.locked === true) ? 'lock' : 'lock_open'}
size={11}
/>
</button>
<div style={{ width: 18 }} />
</div>
<div>
{sorted.map(z => (
<ZeichnungsebeneRow
@@ -222,6 +302,7 @@ export default function GeschossManager({
onContextMenu={(ev) => openContextMenu(ev, z.id)}
onToggleVisible={() => toggleVisible(z.id)}
onToggleLock={() => toggleLock(z.id)}
onToggleClipping={() => toggleClipping(z.id)}
onDelete={() => remove(z.id)}
/>
))}