From d558fac2c301ef21c848f4f126af6d366a796ab8 Mon Sep 17 00:00:00 2001 From: karim Date: Sun, 7 Jun 2026 02:00:47 +0200 Subject: [PATCH] Styles: default pen for new objects + csharp build doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gestaltung panel now shows pen controls (color, lineweight, linetype) when nothing is selected. Settings persist in sticky and are stamped onto every newly drawn object (curves, text, hatch, dims — not 3D solids or DOSSIER element geometry) via the existing AddRhinoObject listener. Active state shown with badge; resets by switching all back to "Nach Ebene". Also adds csharp/BUILD.md with full build + post-reinstall checklist. Co-Authored-By: Claude Sonnet 4.6 --- csharp/BUILD.md | 77 +++++++++++++++++++++++++++ rhino/styles.py | 131 +++++++++++++++++++++++++++++++++++++++++++++- src/StylesApp.jsx | 46 ++++++++++++---- 3 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 csharp/BUILD.md diff --git a/csharp/BUILD.md b/csharp/BUILD.md new file mode 100644 index 0000000..c0aa7f8 --- /dev/null +++ b/csharp/BUILD.md @@ -0,0 +1,77 @@ +# DOSSIER C# Plugin — Build & Install + +Das Plugin (`.rhp`) bootstrappt beim Rhino-Start die Python-Module und +registriert native Commands (`dWall`, `dDoor`, `dSlab`, …). + +## Voraussetzungen + +```bash +brew install dotnet@7 +``` + +Oder direkt von Microsoft: https://dotnet.microsoft.com/download/dotnet/7.0 + +RhinoCommon wird beim ersten Build automatisch via NuGet geladen. + +## Repo-Pfad setzen (nach Neuinstallation wichtig) + +Das Plugin sucht das Repo in dieser Reihenfolge: + +1. Env-Var `DOSSIER_HOME` +2. Datei `~/.dossier_home` (eine Zeile: absoluter Pfad zum Repo-Root) +3. Hardcoded Fallback `/Users/karim/STUDIO/DOSSIER` + +Einfachste Variante — einmalig nach dem Klonen: + +```bash +echo "/Users/karim/PROJECTS/DOSSIER" > ~/.dossier_home +``` + +Ohne das findet das Plugin `rhino/startup.py` nicht und bootet nicht. + +## Build + +```bash +cd csharp/DOSSIER +./build.sh # Release → bin/Release/net7.0/DOSSIER.rhp +./build.sh debug # Debug-Build mit Symbols +./build.sh clean # bin/ + obj/ löschen +./build.sh install # Build + yak install in Rhino-User-Plugin-Pfad +``` + +## Installation in Rhino (einmalig nach Build) + +Mac Rhino 8 unterstützt kein Drag & Drop für `.rhp`-Dateien. + +1. Rhino 8 öffnen +2. Befehl: `PluginManager` +3. Button **Install…** → `csharp/DOSSIER/bin/Release/net7.0/DOSSIER.rhp` +4. Rhino neu starten + +Der Pfad bleibt in Rhinos Settings-XML registriert. Bei späteren Builds +einfach wieder in denselben Output-Pfad bauen — Rhino lädt den neuen Stand +automatisch beim nächsten Start. + +## Startup-Eintrag (Python-Bootstrap) + +Der Launcher trägt den Startup-Eintrag automatisch ein. Für manuelle +Dev-Setups ohne Launcher: + +Rhino → Options → General → „Run these commands every time a model is opened": + +``` +_-RunPythonScript "/Users/karim/PROJECTS/DOSSIER/rhino/startup.py" +``` + +(Mit Dash, mit Quotes, voller Pfad — siehe CLAUDE.md für Details warum.) + +## Nach Neuinstallation Mac — Checkliste + +- [ ] Repo klonen: `git clone https://git.kgva.ch/karim/DOSSIER.git` +- [ ] `echo "/Users/karim/PROJECTS/DOSSIER" > ~/.dossier_home` +- [ ] `brew install dotnet@7 node` +- [ ] `cd DOSSIER && npm install && npm run build` +- [ ] `cd csharp/DOSSIER && ./build.sh install` +- [ ] Rhino öffnen → PluginManager → Install → `.rhp` registrieren +- [ ] Rhino neu starten → DOSSIER bootstrappt (Panels + Commands) +- [ ] `~/Library/Application Support/RhinoPanel/override_presets.json` von Backup zurückkopieren diff --git a/rhino/styles.py b/rhino/styles.py index 1ba5465..25c46fb 100644 --- a/rhino/styles.py +++ b/rhino/styles.py @@ -189,6 +189,94 @@ def _hex_to_color(h): return Drawing.Color.FromArgb(int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)) +# Default-Pen ("Stift fuer neue Objekte"): wird in on_add auf frisch +# gezeichnete Kurven gestempelt, solange aktiv. Persistiert in Sticky und +# gilt bis der User wieder auf "Nach Ebene" zurueckstellt. Wird vom selben +# UI-Block gesetzt wie der Selektions-Pen — nur ohne Auswahl (siehe Setter). +_DEFAULT_PEN_KEY = "_dossier_default_pen" + + +def _default_pen(): + p = sc.sticky.get(_DEFAULT_PEN_KEY) + if not isinstance(p, dict): + p = {} + return { + "colorSource": p.get("colorSource", "layer"), + "color": p.get("color"), + "lwSource": p.get("lwSource", "layer"), + "lw": p.get("lw"), + "linetypeSource": p.get("linetypeSource", "layer"), + "linetype": p.get("linetype"), + } + + +def _set_default_pen(**changes): + p = _default_pen() + p.update(changes) + sc.sticky[_DEFAULT_PEN_KEY] = p + + +def _default_pen_active(pen=None): + p = pen or _default_pen() + return (p["colorSource"] == "object" or p["lwSource"] == "object" + or p["linetypeSource"] == "object") + + +def _has_selection(doc): + return bool(list(doc.Objects.GetSelectedObjects(False, False))) + + +def _apply_pen_to_attrs(doc, a, pen): + """Stempelt den aktiven Default-Pen auf eine Attribut-Kopie (in-place).""" + if pen["colorSource"] == "object": + a.ColorSource = _FROM_OBJECT + if pen["color"]: + a.ObjectColor = _hex_to_color(pen["color"]) + _sync_plot_color_to_display(a) + if pen["lwSource"] == "object" and pen["lw"] is not None: + a.PlotWeightSource = _LW_FROM_OBJECT + try: + import massstab + massstab.write_plotweight(doc, a, float(pen["lw"])) + except Exception: + a.PlotWeight = float(pen["lw"]) + if pen["linetypeSource"] == "object" and pen["linetype"]: + idx = -1 + try: idx = doc.Linetypes.Find(pen["linetype"], True) + except Exception: idx = -1 + if idx >= 0: + a.LinetypeSource = _LT_FROM_OBJECT + a.LinetypeIndex = idx + + +def _new_object_pen_summary(doc, pen): + """PenBlock-kompatibles Objekt fuer den leeren Panel-Zustand: zeigt den + aktiven Default-Pen, "Nach Ebene"-Vorschau aus der aktuellen Ebene.""" + cur = doc.Layers.CurrentLayer + layer_color = _color_to_hex(cur.Color) + try: + import massstab as _ms + layer_lw = round(_ms.read_plotweight(cur), 4) + except Exception: + layer_lw = round(cur.PlotWeight, 4) + layer_lt = _linetype_name(doc, cur.LinetypeIndex) + return { + "colorSource": pen["colorSource"], + "color": pen["color"] or layer_color, + "layerColor": layer_color, + "lwSource": pen["lwSource"], + "lw": pen["lw"] if pen["lw"] is not None else layer_lw, + "layerLw": layer_lw, + "linetypeSource": pen["linetypeSource"], + "linetype": pen["linetype"] or layer_lt, + "layerLinetype": layer_lt, + "linetypes": _all_linetypes(doc), + "layerName": _safe_layer_label(doc, cur, doc.Layers.CurrentLayerIndex), + "geometryKind": "curveOpen", + "active": _default_pen_active(pen), + } + + def _force_load_linetypes(doc): """Rhinos Linetype-Tabelle wird lazy initialisiert — wir triggern es.""" # 1) Eingebaute Methode (falls present) @@ -840,6 +928,7 @@ def _selection_summary(doc): objs = list(doc.Objects.GetSelectedObjects(False, False)) base = {"count": 0, "linetypes": _all_linetypes(doc), "hatchPatterns": _all_hatch_patterns(doc)} if not objs: + base["newObjectPen"] = _new_object_pen_summary(doc, _default_pen()) return base color_sources, colors = set(), set() @@ -1238,6 +1327,12 @@ class GestaltungBridge(panel_base.BaseBridge): self._send_selection() def _set_color_source(self, source, color_hex): + doc = Rhino.RhinoDoc.ActiveDoc + if not _has_selection(doc): + _set_default_pen(colorSource=source, + color=(color_hex if source == "object" else None)) + self._send_selection() + return col = _hex_to_color(color_hex) if (source == "object" and color_hex) else None def m(a, _obj): if source == "layer": @@ -1255,11 +1350,16 @@ class GestaltungBridge(panel_base.BaseBridge): # Print-Mode-aware: bei aktivem Print-View werden PlotWeights skaliert. # write_plotweight() kuemmert sich um beides (Original-Speicherung + # Skalierungs-Multiplier). + doc = Rhino.RhinoDoc.ActiveDoc + if not _has_selection(doc): + _set_default_pen(lwSource=source, + lw=(lw if source == "object" else None)) + self._send_selection() + return try: import massstab except Exception: massstab = None - doc = Rhino.RhinoDoc.ActiveDoc def m(a, _obj): if source == "layer": a.PlotWeightSource = _LW_FROM_LAYER @@ -1316,6 +1416,11 @@ class GestaltungBridge(panel_base.BaseBridge): def _set_linetype_source(self, source, name): doc = Rhino.RhinoDoc.ActiveDoc + if not _has_selection(doc): + _set_default_pen(linetypeSource=source, + linetype=(name if source == "object" else None)) + self._send_selection() + return idx = -1 if source == "object" and name: try: @@ -2012,6 +2117,30 @@ def _install_selection_listener(bridge): # Waehrend Move/Rotate werden Sub-Volumen erzeugt die kein Auto-Fill # brauchen, und elemente uebernimmt die Coupling. if sc.sticky.get("_dossier_user_transform_active"): return + + # 2b) Default-Pen ("Stift fuer neue Objekte") auf frisch gezeichnete + # 2D-Objekte stempeln (Kurven, Text, Hatch, Bemassung ...), solange + # aktiv. Ausgeschlossen: 3D-Volumen (Pen ist ein 2D-Begriff) und + # DOSSIER-Element-Geometrie (dossier_element_type), die elemente.py setzt. + pen = _default_pen() + g = obj.Geometry + is_3d = isinstance(g, (rg.Brep, rg.Extrusion, rg.Mesh, rg.SubD)) + if _default_pen_active(pen) and g is not None and not is_3d: + try: dossier_type = obj.Attributes.GetUserString("dossier_element_type") or "" + except Exception: dossier_type = "" + if not dossier_type: + try: + a = obj.Attributes.Duplicate() + _apply_pen_to_attrs(doc, a, pen) + _processing.add(obj.Id) + try: + doc.Objects.ModifyAttributes(obj, a, True) + finally: + _processing.discard(obj.Id) + doc.Views.Redraw() + except Exception as ex: + print("[STYLES] on_add default-pen:", ex) + try: ok = _apply_ebene_fill(doc, obj) except Exception as ex: diff --git a/src/StylesApp.jsx b/src/StylesApp.jsx index 4a63de1..f905858 100644 --- a/src/StylesApp.jsx +++ b/src/StylesApp.jsx @@ -493,16 +493,42 @@ function SectionBlock({ sel }) { } -function EmptyState() { +// Leerer Zustand = "Stift fuer neue Objekte". Dieselben Pen-Controls wie bei +// einer Selektion (PenBlock), aber ohne Auswahl routet das Backend die Setter +// auf den Default-Pen, der dann auf frisch gezeichnete Kurven gestempelt wird. +function NewObjectPen({ pen }) { + const sel = pen || { + linetypes: [], geometryKind: 'curveOpen', + colorSource: 'layer', lwSource: 'layer', linetypeSource: 'layer', + } return ( -
- -
Waehle ein oder mehrere Objekte in Rhino aus.
-
+ <> +
+
+ + + Stift für neue Objekte + + {sel.active && ( + aktiv + )} +
+
+ {sel.active + ? 'Neu gezeichnete Linien & Kurven bekommen diesen Stift. Überall „Nach Ebene" = aus.' + : 'Farbe, Stärke oder Linientyp wählen — gilt für alle neu gezeichneten Objekte, bis du wieder auf „Nach Ebene" stellst.'} +
+
+ +
+ Oder wähle ein Objekt in Rhino, um dessen Stift zu ändern. +
+ ) } @@ -538,7 +564,7 @@ export default function GestaltungApp() { position: 'relative', }}>
- {empty ? : ( + {empty ? : ( <> {showFill && ( <>