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:
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# clean-rhino.sh — setzt DOSSIER in Rhino zurueck auf "frisch installiert" Zustand.
|
||||
# Damit kann das Setup im Launcher (Settings → Setup tab) jederzeit von Null
|
||||
# durchgespielt werden.
|
||||
#
|
||||
# Aufgaben:
|
||||
# 1. yak uninstall dossier (Plugin raus)
|
||||
# 2. Window-Layout-Datei loeschen (workspaces/<guid>.xml)
|
||||
# 3. StartupCommands-XML-Eintrag entfernen (Python-Bootstrap-Trigger)
|
||||
#
|
||||
# Bleibt unangetastet:
|
||||
# - dossier_settings.json (User-Praeferenzen, Tags, etc.)
|
||||
# - launcher recent.json
|
||||
# - alles ausserhalb DOSSIER
|
||||
|
||||
set -e
|
||||
|
||||
RHINO_APP="/Applications/Rhino 8.app"
|
||||
YAK="$RHINO_APP/Contents/Resources/bin/yak"
|
||||
SETTINGS_XML="$HOME/Library/Application Support/McNeel/Rhinoceros/8.0/settings/settings-Scheme__Default.xml"
|
||||
WORKSPACES_DIR="$HOME/Library/Application Support/McNeel/Rhinoceros/8.0/settings/Scheme__Default/workspaces"
|
||||
LAYOUT_GUID="b6b68c03-3031-4899-bca2-fe6e425146fc"
|
||||
|
||||
# --- Safety: Rhino muss zu sein ---
|
||||
if pgrep -f "Rhino 8.app/Contents/MacOS/Rhinoceros$" >/dev/null; then
|
||||
echo "FEHLER: Rhino laeuft. Bitte beenden und nochmal."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 1. Yak uninstall (idempotent — meldet 'package not installed' wenn schon weg) ---
|
||||
echo "==> 1. Yak uninstall dossier"
|
||||
if [ -x "$YAK" ]; then
|
||||
"$YAK" uninstall dossier 2>&1 | sed 's/^/ /' || true
|
||||
else
|
||||
echo " WARN: yak nicht gefunden — skip"
|
||||
fi
|
||||
|
||||
# --- 2. Window-Layout-Datei loeschen ---
|
||||
echo "==> 2. Window-Layout-Datei loeschen"
|
||||
LAYOUT_FILE="$WORKSPACES_DIR/$LAYOUT_GUID.xml"
|
||||
if [ -f "$LAYOUT_FILE" ]; then
|
||||
rm -v "$LAYOUT_FILE" | sed 's/^/ /'
|
||||
else
|
||||
echo " schon weg"
|
||||
fi
|
||||
|
||||
# --- 3. StartupCommands-Eintrag aus XML entfernen ---
|
||||
echo "==> 3. StartupCommands-Eintrag entfernen"
|
||||
if [ -f "$SETTINGS_XML" ]; then
|
||||
# sed: matche genau unsere DOSSIER-Zeile und loesche
|
||||
# (egal welcher Pfad — solange startup.py drin steht)
|
||||
if grep -q 'StartupCommands.*startup.py' "$SETTINGS_XML"; then
|
||||
# macOS sed braucht leeres Backup-Suffix
|
||||
sed -i '' '/<entry key="StartupCommands">.*startup\.py.*<\/entry>/d' "$SETTINGS_XML"
|
||||
echo " entfernt"
|
||||
else
|
||||
echo " schon weg"
|
||||
fi
|
||||
else
|
||||
echo " WARN: Rhino-settings-XML nicht gefunden"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Clean fertig. Naechster Schritt:"
|
||||
echo " → Launcher → Settings → Setup → 'Setup starten'"
|
||||
@@ -303,9 +303,17 @@ fn splash_owner_marker_path() -> PathBuf {
|
||||
|
||||
fn open_rhino_internal(app: &tauri::AppHandle, path3dm: &str) -> Result<(), String> {
|
||||
let settings = load_settings();
|
||||
// XML-Edit nur sinnvoll wenn Rhino nicht laeuft (sonst ueberschreibt's
|
||||
// beim Beenden) UND der Eintrag fuer den naechsten Start eh schon greift.
|
||||
// Setup-Schritte nur wenn Rhino NICHT laeuft — sonst ueberschreibt Rhino
|
||||
// unsere XML-Edits beim Beenden, und yak install kann die in-use .rhp
|
||||
// nicht ersetzen.
|
||||
if settings.auto_load_plugin && !is_rhino_running() {
|
||||
// Schritt 1: .rhp via yak installieren/aktualisieren. Soft-Fail wenn
|
||||
// .yak nicht gebundelt ist (Dev-Setup ohne build.sh ausgefuehrt) —
|
||||
// Bootstrap via XML reicht alleine, Commands fehlen nur.
|
||||
if let Err(e) = ensure_rhino_plugin_installed() {
|
||||
eprintln!("[DOSSIER] Plugin-Install skip: {e}");
|
||||
}
|
||||
// Schritt 2: StartupCommands-XML fuer Python-Bootstrap setzen.
|
||||
let startup_path = settings.plugin_startup_path
|
||||
.clone()
|
||||
.unwrap_or_else(default_plugin_startup_path);
|
||||
@@ -466,11 +474,264 @@ fn ensure_rhino_startup_command(startup_path: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
// ===== Rhino-Plugin Install via Yak =====
|
||||
// Mac Rhino lehnt Auto-Load fuer dritte Plugins ab (egal welche Methode).
|
||||
// Wir installieren `.yak` aber trotzdem in den User-Plugin-Pfad — beim ersten
|
||||
// dWall/dDoor/... laedt Rhino das Plugin on-demand und cached es danach.
|
||||
// Der Python-Bootstrap (Panels/Aliases) laeuft parallel via StartupCommands-
|
||||
// XML (ensure_rhino_startup_command). Beide Pfade zusammen = full setup.
|
||||
|
||||
fn yak_binary_path() -> PathBuf {
|
||||
PathBuf::from("/Applications/Rhino 8.app/Contents/Resources/bin/yak")
|
||||
}
|
||||
|
||||
fn rhino_packages_dir() -> PathBuf {
|
||||
let home = std::env::var("HOME").map(PathBuf::from).unwrap_or_default();
|
||||
home.join("Library/Application Support/McNeel/Rhinoceros/packages/8.0")
|
||||
}
|
||||
|
||||
fn installed_dossier_version() -> Option<String> {
|
||||
let manifest = rhino_packages_dir().join("DOSSIER/manifest.txt");
|
||||
fs::read_to_string(&manifest).ok().map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
// .yak + version.txt im App-Bundle (Production) oder Repo-dist/ (Dev).
|
||||
fn bundled_plugin_paths() -> (PathBuf, PathBuf) {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(contents_dir) = exe.parent().and_then(|p| p.parent()) {
|
||||
let yak = contents_dir.join("Resources/plugin/dossier.yak");
|
||||
let ver = contents_dir.join("Resources/plugin/dossier-version.txt");
|
||||
if yak.is_file() && ver.is_file() {
|
||||
return (yak, ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dev-Fallback: Repo-Pfad
|
||||
let repo = PathBuf::from("/Users/karim/STUDIO/DOSSIER/csharp/DOSSIER/dist");
|
||||
(repo.join("dossier.yak"), repo.join("dossier-version.txt"))
|
||||
}
|
||||
|
||||
fn bundled_plugin_version() -> Option<String> {
|
||||
let (_, ver_path) = bundled_plugin_paths();
|
||||
fs::read_to_string(&ver_path).ok().map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
// Installiert/aktualisiert das DOSSIER-Plugin via yak. Idempotent — skip wenn
|
||||
// installierte Version == gebundelte Version. Soft-Fail wenn .yak fehlt oder
|
||||
// yak-CLI nicht vorhanden (Logging, kein Error — Bootstrap via XML laeuft eh).
|
||||
fn ensure_rhino_plugin_installed() -> Result<(), String> {
|
||||
let yak = yak_binary_path();
|
||||
if !yak.is_file() {
|
||||
return Err(format!("yak CLI nicht gefunden: {}", yak.display()));
|
||||
}
|
||||
let (yak_pkg, _) = bundled_plugin_paths();
|
||||
if !yak_pkg.is_file() {
|
||||
return Err(format!(
|
||||
"DOSSIER .yak-Paket nicht gefunden: {} (build.sh in csharp/DOSSIER ausfuehren)",
|
||||
yak_pkg.display()
|
||||
));
|
||||
}
|
||||
let bundled_ver = bundled_plugin_version();
|
||||
let installed_ver = installed_dossier_version();
|
||||
if let (Some(b), Some(i)) = (&bundled_ver, &installed_ver) {
|
||||
if b == i {
|
||||
return Ok(()); // schon aktuell — kein Re-Install
|
||||
}
|
||||
}
|
||||
if is_rhino_running() {
|
||||
return Err(
|
||||
"Rhino laeuft — Plugin-Update kann erst nach Rhino-Quit installiert werden."
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let pkg_dir = yak_pkg.parent().ok_or_else(|| "Plugin-Pfad ohne Parent".to_string())?;
|
||||
let output = Command::new(&yak)
|
||||
.arg("install")
|
||||
.arg("dossier")
|
||||
.arg("--source")
|
||||
.arg(pkg_dir)
|
||||
.output()
|
||||
.map_err(|e| format!("yak install: {e}"))?;
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"yak install fehlgeschlagen: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
eprintln!(
|
||||
"[DOSSIER] Plugin via yak installiert: {} → {}",
|
||||
installed_ver.unwrap_or_else(|| "(nicht installiert)".into()),
|
||||
bundled_ver.unwrap_or_else(|| "(unbekannt)".into())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
fn open_rhino(app: tauri::AppHandle, path3dm: String) -> Result<(), String> {
|
||||
open_rhino_internal(&app, &path3dm)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn install_rhino_plugin() -> Result<String, String> {
|
||||
ensure_rhino_plugin_installed()?;
|
||||
Ok(installed_dossier_version().unwrap_or_else(|| "(version unbekannt)".into()))
|
||||
}
|
||||
|
||||
|
||||
// ===== Window-Layout Installation =====
|
||||
// Rhino-Workspaces sind XML-Dateien im User-Pfad benannt nach Layout-GUID.
|
||||
// Wir bundlen DOSSIERs Master-Layout(s) im Repo unter rhino/workspaces/ und
|
||||
// kopieren sie beim Init in Rhinos Workspaces-Folder, damit startup.py das
|
||||
// Layout per `_-WindowLayout "<name>" _Enter` direkt anwenden kann.
|
||||
|
||||
fn rhino_workspaces_dir() -> PathBuf {
|
||||
let home = std::env::var("HOME").map(PathBuf::from).unwrap_or_default();
|
||||
home.join("Library/Application Support/McNeel/Rhinoceros/8.0/settings/Scheme__Default/workspaces")
|
||||
}
|
||||
|
||||
fn bundled_workspaces_dir() -> PathBuf {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(contents_dir) = exe.parent().and_then(|p| p.parent()) {
|
||||
let bundled = contents_dir.join("Resources/rhino/workspaces");
|
||||
if bundled.is_dir() {
|
||||
return bundled;
|
||||
}
|
||||
}
|
||||
}
|
||||
PathBuf::from("/Users/karim/STUDIO/DOSSIER/rhino/workspaces")
|
||||
}
|
||||
|
||||
// Kopiert alle Workspace-XMLs aus dem bundle in Rhinos Workspaces-Folder.
|
||||
// Vorhandene Files werden ueberschrieben (User-Customizations gehen verloren —
|
||||
// dafuer ist's reproduzierbar). Returns Anzahl kopierter Files.
|
||||
fn ensure_window_layout_installed() -> Result<usize, String> {
|
||||
let src = bundled_workspaces_dir();
|
||||
if !src.is_dir() {
|
||||
return Err(format!("Workspace-Quelle fehlt: {}", src.display()));
|
||||
}
|
||||
let dst = rhino_workspaces_dir();
|
||||
fs::create_dir_all(&dst)
|
||||
.map_err(|e| format!("Workspace-Zielordner erstellen: {e}"))?;
|
||||
if is_rhino_running() {
|
||||
return Err("Rhino laeuft — bitte erst beenden (Layout-Datei wird sonst ueberschrieben).".into());
|
||||
}
|
||||
let mut count = 0;
|
||||
for entry in fs::read_dir(&src).map_err(|e| format!("Workspace-Quelle lesen: {e}"))? {
|
||||
let entry = entry.map_err(|e| format!("DirEntry: {e}"))?;
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|s| s.to_str()).map(|s| s.eq_ignore_ascii_case("xml")).unwrap_or(false) {
|
||||
let file_name = path.file_name().ok_or("FileName")?;
|
||||
let dst_path = dst.join(file_name);
|
||||
fs::copy(&path, &dst_path)
|
||||
.map_err(|e| format!("Workspace kopieren ({}): {e}", file_name.to_string_lossy()))?;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
|
||||
// ===== DOSSIER INIT =====
|
||||
// Komplettes Setup auf einem neuen PC: Plugin via Yak + StartupCommands-XML +
|
||||
// Window-Layout-Files. Returns Status pro Schritt fuer das Frontend-Dialog.
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
struct InitStep {
|
||||
id: String,
|
||||
label: String,
|
||||
status: String, // "ok" | "error" | "skipped"
|
||||
detail: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
struct InitResult {
|
||||
steps: Vec<InitStep>,
|
||||
overall_ok: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
struct InitStatus {
|
||||
plugin_installed: bool,
|
||||
startup_cmd_set: bool,
|
||||
layout_installed: bool,
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn check_dossier_initialized() -> InitStatus {
|
||||
let plugin_installed = installed_dossier_version().is_some();
|
||||
let startup_cmd_set = fs::read_to_string(rhino_settings_xml_path())
|
||||
.map(|s| s.contains("StartupCommands") && s.contains("startup.py"))
|
||||
.unwrap_or(false);
|
||||
let layout_installed = rhino_workspaces_dir()
|
||||
.join("b6b68c03-3031-4899-bca2-fe6e425146fc.xml")
|
||||
.is_file();
|
||||
InitStatus {
|
||||
plugin_installed,
|
||||
startup_cmd_set,
|
||||
layout_installed,
|
||||
// initialized = ALLE drei vorhanden. So zeigt der Dialog auch nach
|
||||
// teilweisem Clean (z.B. nur layout geloescht) noch an.
|
||||
initialized: plugin_installed && startup_cmd_set && layout_installed,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn dossier_init() -> Result<InitResult, String> {
|
||||
if is_rhino_running() {
|
||||
return Err("Rhino laeuft — bitte erst beenden, dann Init nochmal starten.".into());
|
||||
}
|
||||
|
||||
let mut steps = Vec::new();
|
||||
let mut overall_ok = true;
|
||||
|
||||
// Schritt 1: Plugin via Yak installieren/aktualisieren
|
||||
let (status, detail) = match ensure_rhino_plugin_installed() {
|
||||
Ok(()) => {
|
||||
let v = installed_dossier_version().unwrap_or_else(|| "(?)".into());
|
||||
("ok".into(), format!("Version {v}"))
|
||||
}
|
||||
Err(e) => { overall_ok = false; ("error".into(), e) }
|
||||
};
|
||||
steps.push(InitStep {
|
||||
id: "plugin".into(),
|
||||
label: "DOSSIER-Plugin via Yak installieren".into(),
|
||||
status, detail,
|
||||
});
|
||||
|
||||
// Schritt 2: StartupCommands-XML eintragen (Python-Bootstrap)
|
||||
let startup_path = default_plugin_startup_path();
|
||||
let (status, detail) = if !Path::new(&startup_path).is_file() {
|
||||
overall_ok = false;
|
||||
("error".into(), format!("startup.py nicht gefunden: {startup_path}"))
|
||||
} else {
|
||||
match ensure_rhino_startup_command(&startup_path) {
|
||||
Ok(()) => ("ok".into(), startup_path.clone()),
|
||||
Err(e) => { overall_ok = false; ("error".into(), e) }
|
||||
}
|
||||
};
|
||||
steps.push(InitStep {
|
||||
id: "startup".into(),
|
||||
label: "Python-Bootstrap (StartupCommands-XML)".into(),
|
||||
status, detail,
|
||||
});
|
||||
|
||||
// Schritt 3: Window-Layout-Files in Rhinos Workspaces-Folder kopieren
|
||||
let (status, detail) = match ensure_window_layout_installed() {
|
||||
Ok(n) => ("ok".into(), format!("{n} Layout-Datei(en) installiert")),
|
||||
Err(e) => { overall_ok = false; ("error".into(), e) }
|
||||
};
|
||||
steps.push(InitStep {
|
||||
id: "layout".into(),
|
||||
label: "Window-Layout in Rhino kopieren".into(),
|
||||
status, detail,
|
||||
});
|
||||
|
||||
Ok(InitResult { steps, overall_ok })
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn trigger_plugin_load_now() -> Result<(), String> {
|
||||
// Schreibt den `_RunPythonScript <pfad>` Eintrag in Rhinos Startup-Command-
|
||||
@@ -924,6 +1185,9 @@ pub fn run() {
|
||||
read_project_config,
|
||||
open_rhino,
|
||||
trigger_plugin_load_now,
|
||||
install_rhino_plugin,
|
||||
dossier_init,
|
||||
check_dossier_initialized,
|
||||
get_default_plugin_startup_path,
|
||||
show_in_finder,
|
||||
is_rhino_running,
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
},
|
||||
"resources": {
|
||||
"../../dist": "dist",
|
||||
"../../rhino": "rhino"
|
||||
"../../rhino": "rhino",
|
||||
"../../csharp/DOSSIER/dist/dossier.yak": "plugin/dossier.yak",
|
||||
"../../csharp/DOSSIER/dist/dossier-version.txt": "plugin/dossier-version.txt"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user