Initial commit — Dossier Rhino 8 Plugin

OpenStudio-Suite Architektur-Plugin fuer Rhino 8 (Mac):
- Smart-Elemente: Wand, Decke, Dach (Pult/Sattel/Walm/Mansarde),
  Oeffnungen (Fenster/Tueren mit Rahmen + Sims + Glas + Fluegel),
  Treppen (gerade · L · Wendel mit Schrittmass-Validierung)
- Live-Previews mit Step-Lines + Soll-Range-Clamping
- Bidirektionale Selection-Sync zwischen Source-Linie und Volume
- Geschoss-/Ebenen-Verwaltung mit OKFF-Persistenz
- Layouts mit PDF-Export
- Ausschnitte / Massstab / Override-Regeln
- Petrol-Gruen Theme (Rapport-konform)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 04:27:41 +02:00
commit 9dc191be4f
145 changed files with 32629 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
// Verhindert ein extra Konsolen-Fenster auf Windows im Release-Build.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
// Statisches Modul-Manifest — in die Binary einkompiliert, sodass die App
// keine externe Datei zur Laufzeit braucht. Wer Module aendert: modules.json
// im launcher-Root bearbeiten, dann neu bauen.
const MODULES_JSON: &str = include_str!("../../modules.json");
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Project {
name: String,
path: String,
modules: Vec<String>,
#[serde(rename = "lastOpened")]
last_opened: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct ProjectConfig {
name: String,
modules: Vec<String>,
#[serde(rename = "dossierVersion")]
dossier_version: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct Settings {
// App-Name oder absoluter Pfad zur Rhino-App. `open -a` akzeptiert beides:
// "Rhinoceros 8" -> sucht in /Applications
// "/Applications/Rhino 8.app" oder ein Beta-Build irgendwo anders.
#[serde(rename = "rhinoApp")]
rhino_app: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
rhino_app: "Rhinoceros 8".into(),
}
}
}
fn dossier_dir() -> PathBuf {
// ~/Library/Application Support/Dossier auf macOS,
// entsprechend Plattform-Pendants sonst.
let dir = directories::ProjectDirs::from("ch", "gabrielevarano", "Dossier")
.map(|p| p.data_dir().to_path_buf())
.unwrap_or_else(|| PathBuf::from("."));
fs::create_dir_all(&dir).ok();
dir
}
fn recent_path() -> PathBuf {
dossier_dir().join("recent.json")
}
fn settings_path() -> PathBuf {
dossier_dir().join("settings.json")
}
fn load_settings() -> Settings {
let p = settings_path();
if !p.exists() {
return Settings::default();
}
fs::read_to_string(&p)
.ok()
.and_then(|raw| serde_json::from_str(&raw).ok())
.unwrap_or_default()
}
#[tauri::command]
fn list_recent() -> Vec<Project> {
let p = recent_path();
if !p.exists() {
return vec![];
}
let raw = fs::read_to_string(&p).unwrap_or_default();
serde_json::from_str(&raw).unwrap_or_default()
}
#[tauri::command]
fn save_recent(projects: Vec<Project>) -> Result<(), String> {
let p = recent_path();
let raw = serde_json::to_string_pretty(&projects).map_err(|e| e.to_string())?;
fs::write(&p, raw).map_err(|e| format!("recent.json schreiben: {e}"))
}
#[tauri::command]
fn write_project_config(
path3dm: String,
name: String,
modules: Vec<String>,
) -> Result<(), String> {
let p = Path::new(&path3dm);
let dir = p
.parent()
.ok_or_else(|| format!("Kein gueltiger Ordner aus Pfad: {path3dm}"))?;
let config_path = dir.join("dossier.project.json");
let config = ProjectConfig {
name,
modules,
dossier_version: env!("CARGO_PKG_VERSION").to_string(),
};
let raw = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
fs::write(&config_path, raw).map_err(|e| format!("project-config schreiben: {e}"))
}
#[tauri::command]
fn read_project_config(path3dm: String) -> Result<Option<ProjectConfig>, String> {
let p = Path::new(&path3dm);
let dir = p.parent().ok_or_else(|| "Pfad ungueltig".to_string())?;
let config_path = dir.join("dossier.project.json");
if !config_path.exists() {
return Ok(None);
}
let raw = fs::read_to_string(&config_path).map_err(|e| e.to_string())?;
let cfg: ProjectConfig = serde_json::from_str(&raw).map_err(|e| e.to_string())?;
Ok(Some(cfg))
}
#[tauri::command]
fn open_rhino(path3dm: String) -> Result<(), String> {
// macOS: `open -a <app> <file>`. `<app>` kann ein App-Name (in /Applications
// gesucht) oder ein absoluter Pfad zur .app sein. Aus den Settings — Default
// "Rhinoceros 8", falls der User nichts angepasst hat.
let settings = load_settings();
Command::new("open")
.args(["-a", &settings.rhino_app, &path3dm])
.spawn()
.map_err(|e| format!(
"open-Befehl fehlgeschlagen ({}): {e}", settings.rhino_app
))?;
Ok(())
}
#[tauri::command]
fn read_settings() -> Settings {
load_settings()
}
#[tauri::command]
fn save_settings(settings: Settings) -> Result<(), String> {
let raw = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
fs::write(settings_path(), raw).map_err(|e| format!("settings.json schreiben: {e}"))
}
#[tauri::command]
fn read_modules_manifest() -> Result<serde_json::Value, String> {
serde_json::from_str(MODULES_JSON).map_err(|e| e.to_string())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
list_recent,
save_recent,
write_project_config,
read_project_config,
open_rhino,
read_modules_manifest,
read_settings,
save_settings
])
.run(tauri::generate_context!())
.expect("Fehler beim Starten der Tauri-App");
}