Linientypen + Schraffuren-Tabs in Project-Settings + Datei-Import

Project-Settings hat jetzt 4 Tabs:
- Voreinstellungen (kompakte InlineNumberField, gruppiert in Sections)
- Materialien (List/Detail, ohne Hatch)
- Linientypen (List/Detail mit SVG-Strich-Vorschau)
- Schraffuren (List/Detail mit echtem HatchLine-Renderer)

Backend (rhinopanel.py):
- _list_linetypes_full liefert Segmente {length, type: Line/Space/Dot}
  (Mac Rhino 8 GetSegment returnt (length, isLine: bool))
- _list_hatch_patterns_full liefert HatchLines mit angle/base/offset/dashes
  (hl.Dashes optional ueber 3 API-Variants)
- CRUD: RENAME / DELETE / LOAD_DEFAULTS
- File-Import: IMPORT_LINETYPE_FILE (.lin), IMPORT_HATCH_FILE (.pat)
  via Eto.OpenFileDialog → Linetypes.Load / HatchPatterns.LoadFromFile

Frontend (ProjectSettingsDialog.jsx):
- LinetypePreview: SVG mit tile-fenster (4 Repetitions), Line als <line>,
  Dot als <circle>, currentColor fuer Renderer-Robustheit
- HatchPreview: rendert pro HatchLine alle parallelen Linien mit Angle,
  Offset (Spacing + Stagger), Dashes als stroke-dasharray
- TABLES_UPDATED Message vom Backend re-rendert Listen
- Import-Pills im List-Footer

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:14:28 +02:00
parent 8d3b3af882
commit a597b58c93
3 changed files with 764 additions and 1 deletions
+309
View File
@@ -49,6 +49,154 @@ def _hatch_pattern_names(doc):
return out
def _list_hatch_patterns_full(doc):
"""Vollstaendiges Hatch-Pattern-Listing fuer die Verwaltungs-UI.
Inkludiert die HatchLines (angle, base, offset, dashes) damit
Frontend echte Pattern-Previews rendern kann statt Platzhalter."""
out = []
try:
for i in range(doc.HatchPatterns.Count):
try:
hp = doc.HatchPatterns[i]
if hp is None: continue
try:
if bool(hp.IsDeleted): continue
except Exception: pass
ftype = "Lines"
try:
ft = hp.FillType
ftype = str(ft).split(".")[-1]
except Exception: pass
ref = 0
try: ref = int(hp.Reference)
except Exception: pass
desc = ""
try: desc = str(hp.Description) or ""
except Exception: pass
# HatchLines extrahieren (nur Lines-Typ)
hlines = []
if ftype.lower() == "lines":
try:
# Probiere zuerst die Property, dann GetHatchLines()
lines = None
try: lines = hp.HatchLines
except Exception: pass
if lines is None:
try: lines = hp.GetHatchLines()
except Exception: pass
if lines is None:
print("[EBENEN] hp[{}] '{}' HatchLines=None".format(
i, hp.Name))
else:
try: cnt = len(lines)
except Exception:
try: cnt = lines.Count
except Exception: cnt = -1
print("[EBENEN] hp[{}] '{}' HatchLines type={} count={}".format(
i, hp.Name, type(lines).__name__, cnt))
for hl in lines:
try:
bp = hl.BasePoint
off = hl.Offset
# Dashes optional — Property heisst auf
# manchen Rhino-Versionen GetDashes() oder
# DashCount/GetDash(i)
dashes = []
try:
dr = hl.Dashes
if dr is not None:
for d in dr: dashes.append(float(d))
except Exception:
# Versuche GetDashes()
try:
dr = hl.GetDashes()
if dr is not None:
for d in dr: dashes.append(float(d))
except Exception:
# Versuche DashCount + GetDash(i)
try:
dc = int(hl.DashCount)
for k in range(dc):
dashes.append(float(hl.GetDash(k)))
except Exception: pass
entry = {
"angle": float(hl.Angle),
"baseX": float(bp.X),
"baseY": float(bp.Y),
"offX": float(off.X),
"offY": float(off.Y),
"dashes": dashes,
}
hlines.append(entry)
except Exception as ex:
print("[EBENEN] hp[{}] hl FAIL:".format(i), ex)
except Exception as ex:
print("[EBENEN] hp[{}] HatchLines outer FAIL:".format(i), ex)
out.append({
"index": i,
"name": hp.Name or "",
"fillType": ftype,
"description": desc,
"isReference": (ref > 0),
"hatchLines": hlines,
})
except Exception:
continue
except Exception as ex:
print("[EBENEN] _list_hatch_patterns_full:", ex)
return out
def _list_linetypes_full(doc):
"""Vollstaendiges Linetype-Listing fuer die Verwaltungs-UI.
Mac Rhino 8 GetSegment(i) returnt (length: float, isLine: bool):
True = Line-Segment, False = Space (Gap). Bei Dot ist length=0.0
+ isLine=True (Punkt = unendlich kurzer Strich)."""
out = []
try:
for i in range(doc.Linetypes.Count):
try:
lt = doc.Linetypes[i]
if lt is None or lt.IsDeleted: continue
segs = []
sc_cnt = 0
try: sc_cnt = int(lt.SegmentCount)
except Exception: pass
for s in range(sc_cnt):
try:
seg = lt.GetSegment(s)
if seg is None: continue
length = float(seg[0])
# seg[1]: True = Line/Dot, False = Space/Gap
is_line = bool(seg[1])
if is_line and length == 0.0:
stype = "Dot"
elif is_line:
stype = "Line"
else:
stype = "Space"
segs.append({"length": length, "type": stype})
except Exception as ex:
print("[EBENEN] lt[{}] GetSegment({}) FAIL: {}".format(
i, s, ex))
ref = 0
try: ref = int(lt.Reference)
except Exception: pass
out.append({
"index": i,
"name": lt.Name or "",
"segments": segs,
"isContinuous": (len(segs) == 0),
"isReference": (ref > 0),
})
except Exception as ex:
print("[EBENEN] linetype outer FAIL:", ex)
continue
except Exception as ex:
print("[EBENEN] _list_linetypes_full:", ex)
return out
def _read_launcher_schema():
"""Liest das Default-Layer-Schema aus dossier_settings.json (Launcher-Pfad).
Liefert eine Liste {code, name, color, lw} oder None wenn nicht gesetzt."""
@@ -692,6 +840,8 @@ class EbenenBridge(panel_base.BaseBridge):
"materials": current.get("materials", []),
"builtinMaterials": built_in,
"hatchPatterns": _hatch_pattern_names(doc),
"hatchPatternsFull": _list_hatch_patterns_full(doc),
"linetypes": _list_linetypes_full(doc),
}
def on_save(updated):
doc2 = Rhino.RhinoDoc.ActiveDoc
@@ -774,6 +924,20 @@ class EbenenBridge(panel_base.BaseBridge):
except Exception: pass
elif t == "PICK_TEXTURE_FILE":
self._pick_texture(p)
elif t == "RENAME_LINETYPE":
self._rename_linetype(p)
elif t == "DELETE_LINETYPE":
self._delete_linetype(p)
elif t == "LOAD_LINETYPE_DEFAULTS":
self._load_linetype_defaults()
elif t == "IMPORT_LINETYPE_FILE":
self._import_linetype_file()
elif t == "RENAME_HATCH":
self._rename_hatch(p)
elif t == "DELETE_HATCH":
self._delete_hatch(p)
elif t == "IMPORT_HATCH_FILE":
self._import_hatch_file()
def _pick_texture(self, payload):
slot = payload.get("slot") or "diffuse"
try:
@@ -794,6 +958,151 @@ class EbenenBridge(panel_base.BaseBridge):
except Exception as ex:
print("[PROJECT-SETTINGS] pick_texture:", ex)
self.send("TEXTURE_PICKED", {"slot": slot, "path": None})
# ---- Linetype CRUD ----
def _rename_linetype(self, payload):
idx = payload.get("index")
new_name = (payload.get("name") or "").strip()
if idx is None or not new_name: return
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
lt = d.Linetypes[int(idx)]
if lt is None or lt.IsDeleted: return
lt.Name = new_name
d.Linetypes.Modify(lt, int(idx), True)
except Exception as ex:
print("[PROJECT-SETTINGS] rename_linetype:", ex)
self._send_tables()
def _delete_linetype(self, payload):
idx = payload.get("index")
if idx is None: return
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
lt = d.Linetypes[int(idx)]
if lt is None: return
# Default-Linetypes (Continuous, ByLayer) sollten nicht
# geloescht werden — Rhino's Delete macht es uns aber
# einfach: gibt False zurueck wenn nicht erlaubt.
d.Linetypes.Delete(int(idx), True)
except Exception as ex:
print("[PROJECT-SETTINGS] delete_linetype:", ex)
self._send_tables()
def _load_linetype_defaults(self):
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
d.Linetypes.LoadDefaultLinetypes(True)
except Exception as ex:
print("[PROJECT-SETTINGS] load_linetype_defaults:", ex)
self._send_tables()
def _import_linetype_file(self):
"""Datei-Picker fuer .lin (AutoCAD-Linetype) → Linetypes.Load."""
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
import Eto.Forms as forms
dlg = forms.OpenFileDialog()
dlg.Title = "Linientyp-Datei waehlen (.lin)"
dlg.MultiSelect = False
dlg.Filters.Add(forms.FileFilter("AutoCAD Linetypes", ".lin"))
dlg.Filters.Add(forms.FileFilter("Alle", ".*"))
parent = bridge_holder.get("form")
res = dlg.ShowDialog(parent) if parent else dlg.ShowDialog(None)
if str(res) != "Ok": return
path = dlg.FileName or ""
if not path: return
cnt_before = d.Linetypes.Count
# Rhino-API: Linetypes.Load(filename) — Achtung in
# manchen Builds heisst es LoadLinetypeFile(...).
ok = False
try:
ok = bool(d.Linetypes.Load(path))
except Exception:
try: ok = bool(d.Linetypes.LoadLinetypeFile(path))
except Exception as ex:
print("[PROJECT-SETTINGS] Linetypes.Load:", ex)
cnt_after = d.Linetypes.Count
print("[PROJECT-SETTINGS] linetype import: ok={} {} -> {} ({} neu)".format(
ok, cnt_before, cnt_after, cnt_after - cnt_before))
except Exception as ex:
print("[PROJECT-SETTINGS] import_linetype_file:", ex)
self._send_tables()
def _import_hatch_file(self):
"""Datei-Picker fuer .pat (AutoCAD-Hatch) →
HatchPatterns.LoadFromFile."""
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
import Eto.Forms as forms
dlg = forms.OpenFileDialog()
dlg.Title = "Schraffur-Datei waehlen (.pat)"
dlg.MultiSelect = False
dlg.Filters.Add(forms.FileFilter("AutoCAD Hatches", ".pat"))
dlg.Filters.Add(forms.FileFilter("Alle", ".*"))
parent = bridge_holder.get("form")
res = dlg.ShowDialog(parent) if parent else dlg.ShowDialog(None)
if str(res) != "Ok": return
path = dlg.FileName or ""
if not path: return
cnt_before = d.HatchPatterns.Count
cnt_imported = 0
try:
# LoadFromFile(file, replaceExisting) — returnt int
cnt_imported = int(d.HatchPatterns.LoadFromFile(path, False))
except Exception:
try:
cnt_imported = int(d.HatchPatterns.Load(path))
except Exception as ex:
print("[PROJECT-SETTINGS] HatchPatterns.Load:", ex)
cnt_after = d.HatchPatterns.Count
print("[PROJECT-SETTINGS] hatch import: {} (Tabelle {} -> {})".format(
cnt_imported, cnt_before, cnt_after))
except Exception as ex:
print("[PROJECT-SETTINGS] import_hatch_file:", ex)
self._send_tables()
# ---- Hatch-Pattern CRUD ----
def _rename_hatch(self, payload):
idx = payload.get("index")
new_name = (payload.get("name") or "").strip()
if idx is None or not new_name: return
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
hp = d.HatchPatterns[int(idx)]
if hp is None or hp.IsDeleted: return
hp.Name = new_name
d.HatchPatterns.Modify(hp, int(idx), True)
except Exception as ex:
print("[PROJECT-SETTINGS] rename_hatch:", ex)
self._send_tables()
def _delete_hatch(self, payload):
idx = payload.get("index")
if idx is None: return
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
try:
d.HatchPatterns.Delete(int(idx), True)
except Exception as ex:
print("[PROJECT-SETTINGS] delete_hatch:", ex)
self._send_tables()
def _send_tables(self):
"""Sendet aktuelle Linetype + Hatch-Tabellen ans Frontend
(TABLES_UPDATED-Message). Frontend re-rendert die Listen."""
d = Rhino.RhinoDoc.ActiveDoc
if d is None: return
self.send("TABLES_UPDATED", {
"linetypes": _list_linetypes_full(d),
"hatchPatternsFull": _list_hatch_patterns_full(d),
})
b = _ProjectSettingsBridge()
bridge_holder["form"] = panel_base.open_satellite_window(
"project_settings",