6fce00343c
User-Probleme: 1. Floating Eto-Dialog erschien nicht nach GetPoint (Mac-Bug) 2. UI war zu klobig, sollte vectorworks-mässig kompakt sein, "+" als kleines Icon-Symbol 3. Selektierten Text aendern war nicht moeglich Fix 1 — Bug: _floating_input geloescht, ersetzt durch _prompt_for_text() das Rhino.UI.Dialogs.ShowEditBox benutzt. Nativer cross-platform Dialog ohne Eto-Modal-Bug. Workflow: GetPoint → ShowEditBox → AddText. Funktioniert auf Mac. Fix 2 — UI: Text-Block kompakt umgebaut. Reihe 1: [Font ▼] | [Size m] (130 + 60 = 196px) Reihe 2: [B][I][+] segmented pill (gleiche Breite wie Reihe 1) "+" ist jetzt kleines add-icon (size 13), kein "+ Text" Label mehr. Fix 3 — Edit-Selection: neue Funktionen in text_create.py: - _selected_text_objects(doc) → Liste der selektierten TextEntities - read_selection_settings(doc) → Settings der ersten Selektion - apply_settings_to_selection(doc, patch) → wendet font/size/bold/italic auf alle selektierten TextEntities an oberleiste.SET_TEXT_SETTINGS handler appliziert die Aenderung jetzt ZUSAETZLICH auf die Selection (wenn vorhanden) — UND speichert als Default. State enthaelt textSelectionSettings, UI nutzt diese als Anzeige-Werte wenn vorhanden (Werte spiegeln Selektion live). Visual: Border-Color der Size/Segmented-Pill wird accent wenn Selection aktiv ist (Hinweis dass Aenderungen auf Selektion wirken). Sig-Update: textSelectionSettings + lastSetView in last_state_sig damit State neu gepusht wird wenn sich Selection/View aendert. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
215 lines
6.9 KiB
Python
215 lines
6.9 KiB
Python
#! python 3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
text_create.py
|
|
Text-Erstellungs-Workflow mit Floating-Input-Box statt Rhino-Dialog.
|
|
|
|
Flow:
|
|
1. User klickt Topbar-Button "Text einfuegen"
|
|
2. RhinoGet.GetPoint(): User picked Position im Viewport
|
|
3. Eto.Dialog mit TextBox erscheint neben Maus-Cursor
|
|
4. User tippt, Enter = commit, Escape = abbrechen
|
|
5. TextEntity wird mit Topbar-Settings (Font/Size/Bold/Italic)
|
|
am gepickten Punkt erstellt
|
|
|
|
Settings werden pro Dokument in doc.Strings["dossier_text_settings"]
|
|
persistiert (JSON: font/size/bold/italic).
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import Rhino
|
|
import Rhino.Geometry as rg
|
|
import scriptcontext as sc
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
if _HERE not in sys.path:
|
|
sys.path.insert(0, _HERE)
|
|
|
|
_SETTINGS_KEY = "dossier_text_settings"
|
|
|
|
_DEFAULTS = {
|
|
"font": "Helvetica",
|
|
"size": 0.20, # in Model-Units (m bei m-Doc, mm bei mm-Doc)
|
|
"bold": False,
|
|
"italic": False,
|
|
}
|
|
|
|
|
|
def load_settings(doc):
|
|
if doc is None: return dict(_DEFAULTS)
|
|
try:
|
|
raw = doc.Strings.GetValue(_SETTINGS_KEY)
|
|
if not raw: return dict(_DEFAULTS)
|
|
s = json.loads(raw)
|
|
if not isinstance(s, dict): return dict(_DEFAULTS)
|
|
out = dict(_DEFAULTS)
|
|
out.update({k: s[k] for k in s if k in _DEFAULTS})
|
|
return out
|
|
except Exception:
|
|
return dict(_DEFAULTS)
|
|
|
|
|
|
def save_settings(doc, partial):
|
|
"""Merged partial-Updates rein und persistiert."""
|
|
if doc is None or not isinstance(partial, dict): return
|
|
cur = load_settings(doc)
|
|
cur.update({k: partial[k] for k in partial if k in _DEFAULTS})
|
|
try:
|
|
doc.Strings.SetString(_SETTINGS_KEY, json.dumps(cur))
|
|
except Exception as ex:
|
|
print("[TEXT] save settings:", ex)
|
|
|
|
|
|
def available_fonts():
|
|
"""Liefert sortierte Liste verfuegbarer System-Fonts (Face-Names)."""
|
|
try:
|
|
names = Rhino.DocObjects.Font.AvailableFontFaceNames()
|
|
out = sorted({str(n) for n in names if n})
|
|
return out
|
|
except Exception as ex:
|
|
print("[TEXT] available_fonts:", ex)
|
|
return ["Helvetica", "Arial", "Times New Roman", "Courier New"]
|
|
|
|
|
|
def _prompt_for_text(default=""):
|
|
"""Nativer Rhino-Dialog fuer Text-Eingabe via ShowEditBox. Wirkt
|
|
cross-platform (Mac+Windows), kein Eto-Modal-Bug. Returns text oder
|
|
None bei Abbruch/leer."""
|
|
try:
|
|
rc, text = Rhino.UI.Dialogs.ShowEditBox(
|
|
"Text", "Text:", default or "", False)
|
|
if not rc: return None
|
|
text = (text or "").strip()
|
|
return text or None
|
|
except Exception as ex:
|
|
print("[TEXT] ShowEditBox:", ex)
|
|
return None
|
|
|
|
|
|
def _apply_font(te, face, bold, italic):
|
|
"""Setzt Font auf TextEntity. Mehrere Fallback-Pfade fuer
|
|
verschiedene RhinoCommon-Versionen."""
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
# Pfad 1: FindOrCreate im FontTable + zuweisen
|
|
try:
|
|
font_idx = doc.Fonts.FindOrCreate(face, bool(bold), bool(italic))
|
|
if font_idx >= 0:
|
|
font = doc.Fonts[font_idx]
|
|
if font is not None:
|
|
te.Font = font
|
|
return True
|
|
except Exception as ex:
|
|
print("[TEXT] font path 1:", ex)
|
|
# Pfad 2: statische FromQuartetProperties
|
|
try:
|
|
font = Rhino.DocObjects.Font.FromQuartetProperties(
|
|
face, bool(bold), bool(italic))
|
|
if font is not None:
|
|
te.Font = font
|
|
return True
|
|
except Exception as ex:
|
|
print("[TEXT] font path 2:", ex)
|
|
return False
|
|
|
|
|
|
def _selected_text_objects(doc):
|
|
"""Liefert Liste der selektierten TextEntity-Objekte."""
|
|
out = []
|
|
if doc is None: return out
|
|
try:
|
|
for obj in doc.Objects.GetSelectedObjects(False, False):
|
|
try:
|
|
if isinstance(obj.Geometry, rg.TextEntity):
|
|
out.append(obj)
|
|
except Exception: pass
|
|
except Exception: pass
|
|
return out
|
|
|
|
|
|
def apply_settings_to_selection(doc, patch):
|
|
"""Wendet font/size/bold/italic auf alle selektierten TextEntities an.
|
|
Returns Anzahl der geaenderten Objekte."""
|
|
if doc is None or not isinstance(patch, dict): return 0
|
|
selected = _selected_text_objects(doc)
|
|
if not selected: return 0
|
|
n = 0
|
|
for obj in selected:
|
|
try:
|
|
te = obj.Geometry.Duplicate()
|
|
if "size" in patch:
|
|
try: te.TextHeight = float(patch["size"])
|
|
except Exception: pass
|
|
# Font: bei jeder Aenderung neu setzen (face+bold+italic kombiniert)
|
|
if any(k in patch for k in ("font", "bold", "italic")):
|
|
cur = te.Font
|
|
try: cur_face = cur.QuartetName if cur else "Helvetica"
|
|
except Exception: cur_face = "Helvetica"
|
|
try: cur_bold = bool(cur.Bold) if cur else False
|
|
except Exception: cur_bold = False
|
|
try: cur_italic = bool(cur.Italic) if cur else False
|
|
except Exception: cur_italic = False
|
|
face = patch.get("font") or cur_face
|
|
bold = patch["bold"] if "bold" in patch else cur_bold
|
|
italic = patch["italic"] if "italic" in patch else cur_italic
|
|
_apply_font(te, face, bool(bold), bool(italic))
|
|
doc.Objects.Replace(obj.Id, te)
|
|
n += 1
|
|
except Exception as ex:
|
|
print("[TEXT] apply selection:", ex)
|
|
if n > 0:
|
|
try: doc.Views.Redraw()
|
|
except Exception: pass
|
|
return n
|
|
|
|
|
|
def read_selection_settings(doc):
|
|
"""Wenn TextEntities selektiert: liefert die Settings des ersten als
|
|
dict (font/size/bold/italic). Sonst None."""
|
|
sel = _selected_text_objects(doc)
|
|
if not sel: return None
|
|
try:
|
|
te = sel[0].Geometry
|
|
font = te.Font
|
|
face = font.QuartetName if font else "Helvetica"
|
|
bold = bool(font.Bold) if font else False
|
|
italic = bool(font.Italic) if font else False
|
|
return {
|
|
"font": face,
|
|
"size": float(te.TextHeight),
|
|
"bold": bold,
|
|
"italic": italic,
|
|
}
|
|
except Exception as ex:
|
|
print("[TEXT] read selection:", ex)
|
|
return None
|
|
|
|
|
|
def create_text():
|
|
"""Triggered von der Oberleiste. Vollstaendiger Workflow."""
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
if doc is None: return
|
|
settings = load_settings(doc)
|
|
|
|
rc, pt = Rhino.Input.RhinoGet.GetPoint(
|
|
"Position fuer Text", False)
|
|
if rc != Rhino.Commands.Result.Success: return
|
|
if pt is None: return
|
|
|
|
text = _prompt_for_text()
|
|
if not text: return
|
|
|
|
try:
|
|
te = rg.TextEntity()
|
|
te.Plane = rg.Plane(pt, rg.Vector3d.ZAxis)
|
|
te.PlainText = text
|
|
try:
|
|
te.TextHeight = float(settings.get("size", 0.20))
|
|
except Exception: pass
|
|
_apply_font(te, settings.get("font") or "Helvetica",
|
|
settings.get("bold"), settings.get("italic"))
|
|
doc.Objects.AddText(te)
|
|
doc.Views.Redraw()
|
|
except Exception as ex:
|
|
print("[TEXT] create:", ex)
|