Files
DOSSIER/rhino/text_create.py
T
karim 6fce00343c Text-Block: Bug-Fix + Vectorworks-Style + Edit-Selected
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>
2026-05-21 00:32:16 +02:00

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)