Styles: default pen for new objects + csharp build doc
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
+130
-1
@@ -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:
|
||||
|
||||
+36
-10
@@ -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 (
|
||||
<div style={{
|
||||
padding: '60px 24px', textAlign: 'center',
|
||||
color: 'var(--text-muted)', fontSize: 11,
|
||||
display: 'flex', flexDirection: 'column', gap: 14, alignItems: 'center',
|
||||
}}>
|
||||
<Icon name="touch_app" size={36} style={{ color: 'var(--text-muted)' }} />
|
||||
<div>Waehle ein oder mehrere Objekte in Rhino aus.</div>
|
||||
</div>
|
||||
<>
|
||||
<div style={{ padding: '12px 14px 4px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Icon name="draw" size={16} style={{ color: 'var(--accent)' }} />
|
||||
<span style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-primary)' }}>
|
||||
Stift für neue Objekte
|
||||
</span>
|
||||
{sel.active && (
|
||||
<span style={{
|
||||
fontSize: 9, fontWeight: 600, letterSpacing: '0.06em',
|
||||
textTransform: 'uppercase', color: 'var(--accent)',
|
||||
border: '1px solid var(--accent)', borderRadius: 999,
|
||||
padding: '1px 7px',
|
||||
}}>aktiv</span>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ fontSize: 10, color: 'var(--text-muted)', lineHeight: 1.4 }}>
|
||||
{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.'}
|
||||
</div>
|
||||
</div>
|
||||
<PenBlock sel={sel} />
|
||||
<div style={{ padding: '2px 14px 14px', fontSize: 9, color: 'var(--text-muted)', fontStyle: 'italic' }}>
|
||||
Oder wähle ein Objekt in Rhino, um dessen Stift zu ändern.
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -538,7 +564,7 @@ export default function GestaltungApp() {
|
||||
position: 'relative',
|
||||
}}>
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||
{empty ? <EmptyState /> : (
|
||||
{empty ? <NewObjectPen pen={sel.newObjectPen} /> : (
|
||||
<>
|
||||
{showFill && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user