DOSSIER Multi-Phase: C#-Plugin + Yak + Wandstile + UX-Polish

- C#-Plugin "DOSSIER" mit 23 nativen Commands (dWall, dDoor, ..., dSection)
  - Native Command-Namen + Autocomplete + saubere History
  - Idle-Defer + RhinoCode-API → kein _-RunPythonScript-Echo
  - Yak-Paket via build.sh, Install in ~/Library/.../packages/8.0/
- Launcher (Tauri):
  - dossier_init Tauri-Command + Setup-Tab in Settings
  - Yak-Install + StartupCommands-XML + Window-Layout in einem Schritt
  - clean-rhino.sh fuer reproduzierbare Resets
  - check_dossier_initialized triggert Auto-Open-Setup beim ersten Start
- Wand-Architektur:
  - Chain-Logik DEAKTIVIERT → jede Wand baut eigenes Volume (individuell
    anwaehlbar, einzeln loeschbar)
  - Polyline-Wand: jedes Segment = eigene Wand
  - Smart-Split fuer wand_axis/decke/dach/raum/aussparung/traeger
  - Auto-Group axis+volume → kein ChooseOne-Dialog, Delete loescht beides
  - Stale-Mitre-Fix: Joint-Cache wird vor jedem Wand-Regen invalidiert
  - T-Junction-Tolerance auf 1mm (war 1cm, lieferte falsche T-Mitres)
- Wand-Stile:
  - Schema in dossier_project_settings.wand_styles (Material + Prio +
    Default-Dicke + Referenz, oder Layered mit Schichten)
  - dWall-Command Stil-Picker
  - ProjectSettingsDialog: Sidebar-Layout (Pill-Selection) +
    Wandstile-Tab mit Liste/Editor
  - _wand_chain_compat benutzt style_id
  - Prio-Dominanz: hoehere Prio gewinnt Eckverbindung, niedrigere wird
    T-mitered (siehe _resolve_corner_miter)
- Cmd+G fuer Group (Geschoss-Up auf Alias 'gu')
- Welcome + Cheatsheet borderless mit X/Back-Buttons
- BeginCommand-Hook fuer Gestaltung-Panel-Auto-Open
- panel_base: Python.NET-Enum-Fix fuer Material-Render
This commit is contained in:
2026-05-30 12:46:53 +02:00
parent 7930705d01
commit 18d6d98e07
54 changed files with 5575 additions and 398 deletions
+151
View File
@@ -126,6 +126,13 @@ export default function App() {
invoke('list_window_layouts').then(setLayouts).catch(() => {})
invoke('read_dossier_settings').then(ds => setActiveLayout(ds?.windowLayout || '')).catch(() => {})
invoke('read_settings').then(s => setTags(s?.tags || [])).catch(() => {})
// Auto-Open Setup-Dialog wenn DOSSIER nicht initialisiert ist (z.B. nach
// clean-rhino.sh oder auf einem neuen Mac).
invoke('check_dossier_initialized')
.then(st => {
if (!st?.initialized) { setSettingsTab('setup'); setSettingsOpen(true) }
})
.catch(() => {})
}, [])
// File-Meta laden sobald recent sich aendert
@@ -750,6 +757,7 @@ function SettingsDialog({ initialTab = 'rhino', onClose }) {
<header style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<span>Einstellungen</span>
<div style={{ display: 'flex', gap: 4, marginLeft: 'auto', flexWrap: 'wrap' }}>
<TabBtn active={tab === 'setup'} onClick={() => setTab('setup')}>Setup</TabBtn>
<TabBtn active={tab === 'rhino'} onClick={() => setTab('rhino')}>Rhino</TabBtn>
<TabBtn active={tab === 'view'} onClick={() => setTab('view')}>View</TabBtn>
<TabBtn active={tab === 'ebenen'} onClick={() => setTab('ebenen')}>Ebenen</TabBtn>
@@ -759,6 +767,7 @@ function SettingsDialog({ initialTab = 'rhino', onClose }) {
</div>
</header>
<div className="body">
{tab === 'setup' && <SetupSettings />}
{tab === 'rhino' && <RhinoSettings />}
{tab === 'view' && <ViewSettings />}
{tab === 'ebenen' && <EbenenSchemaSettings />}
@@ -774,6 +783,148 @@ function SettingsDialog({ initialTab = 'rhino', onClose }) {
)
}
function SetupSettings() {
const [running, setRunning] = useState(false)
const [result, setResult] = useState(null) // { steps, overall_ok }
const [error, setError] = useState(null)
const [rhinoBusy, setRhinoBusy] = useState(false)
const [status, setStatus] = useState(null) // { plugin_installed, startup_cmd_set, layout_installed, initialized }
const [rhinoApp, setRhinoApp] = useState('')
const [startupPath, setStartupPath] = useState('')
// Live-Check: ob Rhino laeuft (Init kann nicht laufen wenn ja)
useEffect(() => {
let cancelled = false
const tick = () => {
invoke('is_rhino_running')
.then(v => { if (!cancelled) setRhinoBusy(!!v) })
.catch(() => {})
}
tick()
const id = setInterval(tick, 2000)
return () => { cancelled = true; clearInterval(id) }
}, [])
// Initialer State-Check + erkannte Rhino-Konfig
const refreshStatus = useCallback(() => {
invoke('check_dossier_initialized').then(setStatus).catch(() => {})
}, [])
useEffect(() => {
refreshStatus()
invoke('read_settings').then(s => setRhinoApp(s?.rhinoApp || 'Rhinoceros 8')).catch(() => {})
invoke('get_default_plugin_startup_path').then(setStartupPath).catch(() => {})
}, [refreshStatus])
const runInit = async () => {
setRunning(true); setError(null); setResult(null)
try {
const r = await invoke('dossier_init')
setResult(r)
refreshStatus()
} catch (e) {
setError(typeof e === 'string' ? e : (e?.message || String(e)))
} finally {
setRunning(false)
}
}
const dot = (ok) => (
<span style={{
display: 'inline-block', width: 8, height: 8, borderRadius: 4,
background: ok ? 'var(--accent)' : '#e87b6b', marginRight: 8,
}} />
)
return (
<div>
<h3 style={{ marginTop: 0 }}>DOSSIER einrichten</h3>
<p style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
Setzt DOSSIER auf einem frischen Mac komplett auf: installiert das C#-Plugin in Rhino via Yak,
traegt den Python-Bootstrap als Startup-Command ein, und kopiert das DOSSIER-Window-Layout in Rhinos
Workspaces-Folder. Idempotent kann mehrfach ausgefuehrt werden.
</p>
{/* Erkannte Konfiguration */}
<div style={{
marginTop: 14, padding: 10, background: 'rgba(255,255,255,0.04)',
border: '1px solid var(--border)', borderRadius: 6, fontSize: 11,
}}>
<div style={{ color: 'var(--text-muted)', marginBottom: 6 }}>Erkannte Konfiguration:</div>
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 4 }}>
<span style={{ color: 'var(--text-muted)' }}>Rhino-App:</span>
<span>{rhinoApp || '(nicht gesetzt)'}</span>
<span style={{ color: 'var(--text-muted)' }}>startup.py:</span>
<span style={{ wordBreak: 'break-all' }}>{startupPath || '(nicht gefunden)'}</span>
</div>
<div style={{ marginTop: 6, color: 'var(--text-muted)', fontSize: 10 }}>
(Aendern unter Settings Rhino)
</div>
</div>
{/* Aktueller Install-Status (live) */}
{status && (
<div style={{ marginTop: 12, fontSize: 11 }}>
<div style={{ color: 'var(--text-muted)', marginBottom: 4 }}>Status:</div>
<div>{dot(status.plugin_installed)}DOSSIER-Plugin (.rhp) installiert</div>
<div>{dot(status.startup_cmd_set)}Python-Bootstrap in Rhino-StartupCommands</div>
<div>{dot(status.layout_installed)}Window-Layout in Rhino-Workspaces</div>
</div>
)}
<p style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 14 }}>
Hinweis: Rhino muss waehrend des Setups <strong>geschlossen</strong> sein.
</p>
<div style={{ marginTop: 16, display: 'flex', alignItems: 'center', gap: 12 }}>
<button
className="primary pill"
onClick={runInit}
disabled={running || rhinoBusy}
title={rhinoBusy ? 'Rhino laeuft — bitte beenden' : 'Setup starten'}
>
{running ? 'Setup laeuft…' : 'Setup starten'}
</button>
{rhinoBusy && (
<span style={{ fontSize: 11, color: '#e87b6b' }}>
Rhino laeuft bitte beenden.
</span>
)}
</div>
{result && (
<ul style={{ listStyle: 'none', padding: 0, marginTop: 20, borderTop: '1px solid var(--border)' }}>
{result.steps.map(s => (
<li key={s.id} style={{ display: 'flex', alignItems: 'flex-start', gap: 10,
padding: '10px 0', borderBottom: '1px solid var(--border)' }}>
<span style={{ width: 16, fontSize: 14,
color: s.status === 'ok' ? 'var(--accent)' : '#e87b6b' }}>
{s.status === 'ok' ? '✓' : '✗'}
</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12 }}>{s.label}</div>
<div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2,
wordBreak: 'break-all' }}>
{s.detail}
</div>
</div>
</li>
))}
<li style={{ padding: '10px 0', fontSize: 11,
color: result.overall_ok ? 'var(--accent)' : '#e87b6b' }}>
{result.overall_ok
? '✓ Alle Schritte erfolgreich. Rhino oeffnen — Plugin laedt bei erstem dWall/dDoor/...-Aufruf, startup.py bootstrappt automatisch.'
: '✗ Mindestens ein Schritt ist fehlgeschlagen. Details oben.'}
</li>
</ul>
)}
{error && (
<p style={{ color: '#e87b6b', marginTop: 12, fontSize: 12 }}>{error}</p>
)}
</div>
)
}
function TabBtn({ active, onClick, children }) {
return (
<button