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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user