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
+7
View File
@@ -0,0 +1,7 @@
bin/
obj/
dist/
*.user
*.suo
.vs/
.idea/
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
//
// BIM-Commands: jeweils Wrapper auf das Python-Script in rhino/aliases/cmd/.
// Naming-Convention: d-Prefix + englischer BIM-Begriff (VisualARQ-Stil).
// Klassen-Guids sind frei generiert (uuidgen) — wichtig nur dass sie
// stabil bleiben, damit Rhino sie ueber Sessions wiedererkennt.
using System.Runtime.InteropServices;
namespace DOSSIER.Cmd;
[Guid("9A87B609-719F-468B-AF2A-6E59A9B61062")]
public class DWall : DossierPythonCommand
{
public override string EnglishName => "dWall";
protected override string ScriptRelativePath => "cmd/wand.py";
}
[Guid("80278984-16B8-485B-8876-3D63806BCA58")]
public class DDoor : DossierPythonCommand
{
public override string EnglishName => "dDoor";
protected override string ScriptRelativePath => "cmd/tuer.py";
}
[Guid("20D22047-03FA-4CF3-ACF3-3424A109BD91")]
public class DWindow : DossierPythonCommand
{
public override string EnglishName => "dWindow";
protected override string ScriptRelativePath => "cmd/fenster.py";
}
[Guid("536641ED-93D4-4D49-A028-9F2C4EEE2A24")]
public class DSlab : DossierPythonCommand
{
public override string EnglishName => "dSlab";
protected override string ScriptRelativePath => "cmd/decke.py";
}
[Guid("CF196C6A-EEAE-478C-8EB0-C69B6F7B9942")]
public class DStair : DossierPythonCommand
{
public override string EnglishName => "dStair";
protected override string ScriptRelativePath => "cmd/treppe.py";
}
[Guid("B4A19B8B-4056-428B-BA2E-DF69A2A8DA9A")]
public class DColumn : DossierPythonCommand
{
public override string EnglishName => "dColumn";
protected override string ScriptRelativePath => "cmd/stuetze.py";
}
[Guid("CCDF2D03-1FBD-4BC3-A06E-6D3FEEE575AB")]
public class DBeam : DossierPythonCommand
{
public override string EnglishName => "dBeam";
protected override string ScriptRelativePath => "cmd/traeger.py";
}
[Guid("6ADF4344-0C05-48D1-BB2A-B330E3057CE4")]
public class DRoom : DossierPythonCommand
{
public override string EnglishName => "dRoom";
protected override string ScriptRelativePath => "cmd/raum.py";
}
[Guid("A9D471FD-CB75-4C4E-8236-3C8B9A491266")]
public class DSymbol : DossierPythonCommand
{
public override string EnglishName => "dSymbol";
protected override string ScriptRelativePath => "cmd/symbol.py";
}
[Guid("5388E3A7-B40E-40CE-B958-4A294B1E9F4F")]
public class DTag : DossierPythonCommand
{
public override string EnglishName => "dTag";
protected override string ScriptRelativePath => "cmd/stempel.py";
}
[Guid("F0A5E3B0-F77E-4316-B521-294979F1E9CA")]
public class DRoof : DossierPythonCommand
{
public override string EnglishName => "dRoof";
protected override string ScriptRelativePath => "cmd/dach.py";
}
[Guid("404E4389-F8BF-4BAE-A972-60EADB33941C")]
public class DVoid : DossierPythonCommand
{
public override string EnglishName => "dVoid";
protected override string ScriptRelativePath => "cmd/aussparung.py";
}
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using System.Runtime.InteropServices;
namespace DOSSIER.Cmd;
[Guid("07F23908-EF40-4A98-A550-C8D8A1F80A7F")]
public class DJoin : DossierPythonCommand
{
public override string EnglishName => "dJoin";
protected override string ScriptRelativePath => "cmd/smart_join.py";
}
[Guid("69DBE84C-5E44-4155-84CB-D67329B64830")]
public class DSplit : DossierPythonCommand
{
public override string EnglishName => "dSplit";
protected override string ScriptRelativePath => "cmd/smart_split.py";
}
[Guid("38E80D26-5270-45C6-B5F3-2E2179545C47")]
public class DPipette : DossierPythonCommand
{
public override string EnglishName => "dPipette";
protected override string ScriptRelativePath => "cmd/pipette.py";
}
[Guid("F2C8B5A1-9D4E-4F73-B2C6-1A8E7D3F5C42")]
public class DSection : DossierPythonCommand
{
public override string EnglishName => "dSection";
protected override string ScriptRelativePath => "cmd/section.py";
}
[Guid("66647D04-F324-459F-82B9-0FD82307FA93")]
public class DKeys : DossierPythonCommand
{
public override string EnglishName => "dKeys";
protected override string ScriptRelativePath => "cmd/dkeys.py";
}
[Guid("93406D93-E9AC-424D-BFBD-3B7A542A85A7")]
public class DWelcome : DossierPythonCommand
{
public override string EnglishName => "dWelcome";
protected override string ScriptRelativePath => "cmd/dwelcome.py";
}
+40
View File
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using System.Runtime.InteropServices;
namespace DOSSIER.Cmd;
[Guid("4498B184-E064-4049-8B43-873721ECEE71")]
public class DPlan : DossierPythonCommand
{
public override string EnglishName => "dPlan";
protected override string ScriptRelativePath => "view/plan.py";
}
[Guid("D6089B7C-C513-4A39-A62B-5A5E91764A18")]
public class D3D : DossierPythonCommand
{
public override string EnglishName => "d3D";
protected override string ScriptRelativePath => "view/persp3d.py";
}
[Guid("BA89B2DE-2301-4E0D-8542-3BDF393BF7A7")]
public class DMaterial : DossierPythonCommand
{
public override string EnglishName => "dMaterial";
protected override string ScriptRelativePath => "view/material.py";
}
[Guid("A802824C-BC9B-405B-88A4-77125AA7D5A9")]
public class DLevelUp : DossierPythonCommand
{
public override string EnglishName => "dLevelUp";
protected override string ScriptRelativePath => "view/geschoss_up.py";
}
[Guid("A034FF6F-0BCC-48D7-9AC9-8447D5718D32")]
public class DLevelDown : DossierPythonCommand
{
public override string EnglishName => "dLevelDown";
protected override string ScriptRelativePath => "view/geschoss_down.py";
}
+65
View File
@@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<AssemblyName>DOSSIER</AssemblyName>
<RootNamespace>DOSSIER</RootNamespace>
<Version>0.2.0</Version>
<Title>DOSSIER</Title>
<Company>Karim Gabriele Varano</Company>
<Description>DOSSIER — Architektur-Studio-Plugin fuer Rhino 8. Bootstrappt beim Plugin-Load die Python-Module (Panels, Aliases, View-Modes, Welcome) und registriert native Commands (dWall, dDoor, dStair, ...) als saubere Wrapper auf die jeweiligen Python-Scripts.</Description>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<!-- Rhino-Plugin-Output: .rhp statt .dll -->
<TargetExt>.rhp</TargetExt>
<NoWarn>NU1701;NETSDK1086</NoWarn>
<EnableDynamicLoading>true</EnableDynamicLoading>
<!-- Kein Konflikt mit Rhinos Eto/WPF -->
<UseWindowsForms>false</UseWindowsForms>
<UseWpf>false</UseWpf>
<!-- Plugin-Metadaten (sichtbar im _PluginManager) -->
<AssemblyTitle>DOSSIER</AssemblyTitle>
<Copyright>Copyright (C) 2026 Karim Gabriele Varano. AGPL-3.0-or-later.</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RhinoCommon" Version="8.0.23304.9001" IncludeAssets="compile;build" />
<!-- Rhino-CPython3-Runtime — direkt aus dem App-Bundle linken.
Erlaubt RhinoCode-API ohne den Umweg ueber _-RunPythonScript-Command. -->
<Reference Include="Rhino.Runtime.Code">
<HintPath>/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Versions/A/Resources/Rhino.Runtime.Code.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<!-- Plugin-Guid als Assembly-Attribut (Rhino registriert Plugin via dieser ID) -->
<ItemGroup>
<AssemblyAttribute Include="Rhino.PlugIns.PlugInDescriptionAttribute">
<_Parameter1>Rhino.PlugIns.DescriptionType.Address</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
<_Parameter2>-</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="Rhino.PlugIns.PlugInDescriptionAttribute">
<_Parameter1>Rhino.PlugIns.DescriptionType.Email</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
<_Parameter2>karim@gabrielevarano.ch</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="Rhino.PlugIns.PlugInDescriptionAttribute">
<_Parameter1>Rhino.PlugIns.DescriptionType.Organization</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
<_Parameter2>Karim Gabriele Varano</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="Rhino.PlugIns.PlugInDescriptionAttribute">
<_Parameter1>Rhino.PlugIns.DescriptionType.WebSite</_Parameter1>
<_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
<_Parameter2>https://github.com/karimgvarano/DOSSIER</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.InteropServices.GuidAttribute">
<_Parameter1>e8a4d2c1-6b3f-4e89-9c5a-1d2e3f4a5b6c</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using System;
using System.IO;
namespace DOSSIER;
/// <summary>
/// Locator-Pfade fuer das DOSSIER-Repo. Reihenfolge:
/// 1. Env-Var DOSSIER_HOME
/// 2. File ~/.dossier_home (eine Zeile mit dem Pfad)
/// 3. Hardcoded Fallback /Users/karim/STUDIO/DOSSIER (Dev-Setup)
/// </summary>
internal static class DossierPaths
{
private const string FallbackRoot = "/Users/karim/STUDIO/DOSSIER";
private const string MarkerFile = ".dossier_home";
private static string? _cachedRoot;
public static string? Root
{
get
{
if (_cachedRoot is not null) return _cachedRoot;
_cachedRoot = ResolveRoot();
return _cachedRoot;
}
}
public static string RhinoDir => Path.Combine(Root ?? FallbackRoot, "rhino");
public static string AliasDir => Path.Combine(RhinoDir, "aliases");
public static string CmdDir => Path.Combine(AliasDir, "cmd");
public static string ViewDir => Path.Combine(AliasDir, "view");
public static string StartupPy => Path.Combine(RhinoDir, "startup.py");
private static string? ResolveRoot()
{
var env = Environment.GetEnvironmentVariable("DOSSIER_HOME");
if (!string.IsNullOrEmpty(env) && Directory.Exists(env)) return env;
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var marker = Path.Combine(home, MarkerFile);
if (File.Exists(marker))
{
try
{
var p = File.ReadAllText(marker).Trim();
if (!string.IsNullOrEmpty(p) && Directory.Exists(p)) return p;
}
catch { /* ignore */ }
}
if (Directory.Exists(FallbackRoot)) return FallbackRoot;
return null;
}
}
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using Rhino;
using Rhino.PlugIns;
namespace DOSSIER;
/// <summary>
/// DOSSIER-Plugin. Drei Aufgaben:
/// 1. Bootstrappt beim Plugin-Load die Python-Module: Panels, Aliases,
/// View-Modes, BeginCommand-Hook, Welcome-Screen (alles ueber rhino/startup.py).
/// 2. Registriert native Rhino-Commands (dWall, dDoor, dStair, ...) die
/// jeweils das passende Python-Script in rhino/aliases/cmd/ ausfuehren.
/// 3. Loest das Echo-/Autocomplete-Problem der frueheren Keyboard-Macros
/// (jetzt zeigt die History "dWall" statt "_-RunPythonScript ...").
///
/// Installation: Plugin via _PluginManager → Install... registrieren. Beim
/// naechsten Rhino-Start laeuft DOSSIER automatisch. Kein zusaetzlicher
/// StartupCommands-XML-Eintrag noetig.
/// </summary>
public class DossierPlugin : PlugIn
{
public DossierPlugin() { Instance = this; }
public static DossierPlugin Instance { get; private set; } = null!;
/// <summary>Plugin bei jedem Rhino-Start automatisch laden — default ist
/// "WhenNeeded" (erst beim ersten Command-Aufruf). Wir brauchen aber
/// AtStartup, damit OnLoad → startup.py-Bootstrap immer feuert.</summary>
public override PlugInLoadTime LoadTime => PlugInLoadTime.AtStartup;
protected override LoadReturnCode OnLoad(ref string errorMessage)
{
var root = DossierPaths.Root;
if (root is null)
{
errorMessage =
"DOSSIER Root nicht gefunden. Setze Env-Var DOSSIER_HOME " +
"auf den DOSSIER-Repo-Ordner (z.B. /Users/karim/STUDIO/DOSSIER) " +
"oder leg ein File ~/.dossier_home an.";
return LoadReturnCode.ErrorShowDialog;
}
RhinoApp.WriteLine($"[DOSSIER] Plugin geladen (root={root})");
// Python-Bootstrap deferred auf Idle — OnLoad feuert vor Eto-UI-Init,
// Panels brauchen aber MainWindow + Idle-Event. PythonRunner.RunDeferred
// wartet auf naechstes Idle und ruft dann startup.py auf.
PythonRunner.RunDeferred(DossierPaths.StartupPy, "startup");
return LoadReturnCode.Success;
}
}
+36
View File
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using System.IO;
using Rhino;
using Rhino.Commands;
namespace DOSSIER;
/// <summary>
/// Abstrakte Basis fuer alle DOSSIER-Commands die ein Python-Script
/// ausfuehren. Subklasse setzt nur EnglishName + ScriptRelativePath.
///
/// Mechanik: Rhino erlaubt kein synchrones Command-in-Command-Nesting fuer
/// _-RunPythonScript. PythonRunner.RunDeferred wartet auf das naechste Idle-
/// Event und versucht dann zuerst die RhinoCode-API (keine Echo) und faellt
/// auf _-RunPythonScript zurueck. Der Outer-Command beendet sauber mit Success,
/// das Python-Script laeuft direkt danach. Ergebnis fuer den User: sauberer
/// Command-Name in der History (z.B. "dWall") statt "_-RunPythonScript ...".
/// </summary>
public abstract class DossierPythonCommand : Command
{
/// <summary>z.B. "cmd/wand.py" oder "view/plan.py" — relativ zu rhino/aliases/.</summary>
protected abstract string ScriptRelativePath { get; }
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
var scriptPath = Path.Combine(DossierPaths.AliasDir, ScriptRelativePath);
if (!File.Exists(scriptPath))
{
RhinoApp.WriteLine($"[DOSSIER] FEHLER: Script nicht gefunden: {scriptPath}");
return Result.Failure;
}
PythonRunner.RunDeferred(scriptPath, EnglishName);
return Result.Success;
}
}
+79
View File
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Karim Gabriele Varano
using System;
using System.IO;
using Rhino;
namespace DOSSIER;
/// <summary>
/// Geteilter Python-Script-Runner fuer Plugin-Startup (startup.py) und
/// Commands (cmd/*.py). Primaer ueber Rhino.Runtime.Code-API (CPython3),
/// Fallback ueber _-RunPythonScript-Command.
/// </summary>
internal static class PythonRunner
{
/// <summary>Fuehrt das Script aus. Versucht RhinoCode-API zuerst,
/// faellt auf _-RunPythonScript zurueck. Labels nur fuer Logs.</summary>
public static bool Run(string scriptPath, string label)
{
if (!File.Exists(scriptPath))
{
RhinoApp.WriteLine($"[DOSSIER] {label}: Script nicht gefunden: {scriptPath}");
return false;
}
if (TryRunViaRhinoCode(scriptPath, label)) return true;
try
{
RhinoApp.RunScript($"_-RunPythonScript \"{scriptPath}\"", echo: false);
return true;
}
catch (Exception ex)
{
RhinoApp.WriteLine($"[DOSSIER] {label} RunScript: {ex.Message}");
return false;
}
}
/// <summary>Wie Run, aber defern auf das naechste Idle-Event.
/// Erlaubt safe Invocation aus Plugin-OnLoad oder Command-RunCommand
/// (Rhino mag kein direktes _-RunPythonScript aus diesen Kontexten).</summary>
public static void RunDeferred(string scriptPath, string label)
{
EventHandler? handler = null;
handler = (sender, e) =>
{
RhinoApp.Idle -= handler;
Run(scriptPath, label);
};
RhinoApp.Idle += handler;
}
private static bool TryRunViaRhinoCode(string scriptPath, string label)
{
try
{
var spec = new Rhino.Runtime.Code.Languages.LanguageSpec("*.*.python", "3.*");
var lang = Rhino.Runtime.Code.RhinoCode.Languages.QueryLatest(spec);
if (lang == null) return false;
// RhinoCode.CreateCode(text) setzt __file__/sys.path NICHT automatisch
// — die DOSSIER-Scripts erwarten beides. Injizieren vorne rein.
var pathLit = scriptPath.Replace("\\", "/");
var preamble =
"import sys, os\n" +
$"__file__ = r'{pathLit}'\n" +
"sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n" +
"sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n";
var code = lang.CreateCode(preamble + File.ReadAllText(scriptPath));
var ctx = new Rhino.Runtime.Code.Execution.RunContext();
code.Run(ctx);
return true;
}
catch (Exception ex)
{
RhinoApp.WriteLine($"[DOSSIER] {label} RhinoCode: {ex.Message}");
return false;
}
}
}
+144
View File
@@ -0,0 +1,144 @@
#!/bin/bash
# DOSSIER — Build-Skript
# Baut das C#-Plugin (.rhp) das beim Rhino-Start die Python-Module
# bootstrappt (Panels, Aliases, Welcome) und Native-Commands registriert
# (dWall, dDoor, dStair, dSlab, ...).
#
# === Voraussetzungen ===
# 1. .NET 7 SDK installiert. Auf Mac:
# brew install dotnet@7
# Oder direkt von Microsoft:
# https://dotnet.microsoft.com/download/dotnet/7.0
#
# 2. RhinoCommon NuGet-Package wird beim ersten Build automatisch geladen.
#
# === Build ===
# ./build.sh — Release-Build, output in bin/Release/net7.0/
# ./build.sh debug — Debug-Build mit Symbols
# ./build.sh clean — bin/obj loeschen
# ./build.sh install — Build + ins Rhino Plug-In-Verzeichnis kopieren
#
# === Installation in Rhino (einmalig, auf Mac) ===
# WICHTIG: Mac Rhino 8 unterstuetzt KEIN Drag-Drop fuer .rhp-Plugins
# (der Drag landet im Datei-Oeffnen-Handler, nicht im Plugin-Loader).
#
# Richtiger Weg:
# 1. Rhino 8 oeffnen
# 2. Command-Prompt: PluginManager
# (oder Tools-Menue → Options → Plug-Ins)
# 3. Button "Install..." → browse zur .rhp
# bin/Release/net7.0/DOSSIER.rhp
# 4. Open → Rhino registriert das Plugin
# 5. Rhino restart — DOSSIER bootstrappt (Panels/Aliases/Welcome) +
# Commands dWall/dDoor/... sind verfuegbar
#
# Pfad bleibt in Rhinos settings-XML registriert. Bei spaeteren Builds
# einfach in den gleichen Output-Pfad bauen — Rhino laedt den neuen Stand
# automatisch beim naechsten Start.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# --- Param-Parsing ---
MODE="${1:-release}"
case "$MODE" in
debug|Debug)
CONFIG="Debug"
;;
release|Release|"")
CONFIG="Release"
;;
clean)
echo "==> Loesche bin/ + obj/"
rm -rf bin obj
exit 0
;;
install)
CONFIG="Release"
DO_INSTALL=1
;;
*)
echo "Usage: $0 [release|debug|clean|install]"
exit 1
;;
esac
# --- dotnet-Check ---
if ! command -v dotnet &>/dev/null; then
echo "FEHLER: dotnet nicht installiert."
echo "Install: brew install dotnet@7"
echo " oder https://dotnet.microsoft.com/download/dotnet/7.0"
exit 1
fi
# --- Build ---
echo "==> Build: $CONFIG"
dotnet build -c "$CONFIG"
OUTPUT="$SCRIPT_DIR/bin/$CONFIG/net7.0/DOSSIER.rhp"
if [ ! -f "$OUTPUT" ]; then
echo "FEHLER: Build-Output nicht gefunden: $OUTPUT"
exit 1
fi
echo "==> .rhp Output: $OUTPUT"
# --- Yak-Paket bauen ---
# yak (Rhinos Package Manager) ist im Rhino-App-Bundle dabei. Wir packen .rhp +
# manifest.yml in ein .yak-Archiv das der Launcher bundlet + via "yak install"
# in den User-Plugin-Pfad legt. Dort wird's von Rhino aus dem trusted Yak-
# Verzeichnis geladen.
YAK="/Applications/Rhino 8.app/Contents/Resources/bin/yak"
DIST_DIR="$SCRIPT_DIR/dist"
mkdir -p "$DIST_DIR"
if [ -x "$YAK" ]; then
BUILD_DIR="$SCRIPT_DIR/bin/$CONFIG/net7.0"
pushd "$BUILD_DIR" >/dev/null
# yak spec failed mit Exit-1 wenn manifest.yml schon existiert — kein Fehler
"$YAK" spec --input DOSSIER.rhp >/dev/null 2>&1 || true
rm -f dossier-*.yak
YAK_OUT=$("$YAK" build 2>&1 | grep -oE '/.*\.yak$' | head -1)
popd >/dev/null
if [ -n "$YAK_OUT" ] && [ -f "$YAK_OUT" ]; then
# Versionierter Filename rein damit Launcher die Version vom Filename ablesen kann
YAK_NAME=$(basename "$YAK_OUT")
# Alte .yak im dist/ wegraeumen
rm -f "$DIST_DIR"/dossier-*.yak
cp -v "$YAK_OUT" "$DIST_DIR/$YAK_NAME"
# Stabilen Symlink fuer Launcher (immer 'dossier.yak') zusaetzlich
ln -sf "$YAK_NAME" "$DIST_DIR/dossier.yak"
# Version separat als Textdatei (extrahiert aus manifest.yml im .yak)
VERSION=$(grep '^version:' "$BUILD_DIR/manifest.yml" | awk '{print $2}')
echo -n "$VERSION" > "$DIST_DIR/dossier-version.txt"
echo "==> .yak Output: $DIST_DIR/$YAK_NAME (version=$VERSION)"
else
echo "WARN: yak build hat keinen Output produziert"
fi
else
echo "WARN: yak CLI nicht gefunden ($YAK) — kein .yak-Paket gebaut"
fi
# --- Install: lokales Test-Install via yak ---
# Fuer Dev-Iteration: installiert das frische .yak direkt in den Rhino-User-
# Plugin-Pfad (~/Library/Application Support/McNeel/Rhinoceros/packages/8.0/).
# In Production macht der Launcher das automatisch beim ersten Rhino-Start.
if [ -n "$DO_INSTALL" ]; then
# Alte Manuell-Install-Standorte aufraeumen
OLD_MANUAL="$HOME/Library/Application Support/Dossier/Plugin"
for old in "/Applications/Rhino 8.app/Contents/PlugIns/DOSSIER.rhp" \
"/Applications/Rhino 8.app/Contents/PlugIns/DossierCommands.rhp" \
"$OLD_MANUAL/DOSSIER.rhp"; do
if [ -f "$old" ]; then rm -v "$old"; fi
done
if [ -x "$YAK" ] && [ -f "$DIST_DIR/dossier.yak" ]; then
# yak install nimmt Quelle als Verzeichnis (treats local dir as source server)
"$YAK" install dossier --source "$DIST_DIR" 2>&1 | sed 's/^/ /'
echo "==> Yak-Install fertig. Rhino restart noetig (Plugin laedt on-demand beim ersten Command)."
echo "==> StartupCommands-XML-Eintrag wird vom Launcher gesetzt — fuer Dev manuell pruefen:"
echo " Options → General → Run these commands every time a model is opened"
echo " soll enthalten: _-RunPythonScript \"$SCRIPT_DIR/../../rhino/startup.py\""
fi
fi
echo "OK."