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:
@@ -0,0 +1,7 @@
|
||||
bin/
|
||||
obj/
|
||||
dist/
|
||||
*.user
|
||||
*.suo
|
||||
.vs/
|
||||
.idea/
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+144
@@ -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."
|
||||
Reference in New Issue
Block a user