Files
DOSSIER/rhino/text_create.py
T
karim 2252ffd2f9 Text-Erstellung mit Floating-Input-Box (Variante B)
Neuer Workflow: Klick "+ Text" in Topbar → Punkt im Viewport picken →
Floating Eto-Dialog erscheint neben dem Mauszeiger → User tippt → Enter
fuegt TextEntity mit Topbar-Settings ein. Esc bricht ab.

Backend (rhino/text_create.py):
- load_settings/save_settings — persistiert font/size/bold/italic in
  doc.Strings["dossier_text_settings"] (JSON)
- available_fonts() — System-Font-Namen via
  Rhino.DocObjects.Font.AvailableFontFaceNames
- _floating_input() — Eto.Dialog mit TextBox, ShowModal mit Rhino-
  MainWindow als Parent, positioniert bei Mouse.Position
- create_text() — RhinoGet.GetPoint → _floating_input → TextEntity
  mit Font/Size/Bold/Italic erstellen + AddText
- _apply_font() mit 2 Fallback-Pfaden (FontTable.FindOrCreate +
  Font.FromQuartetProperties) fuer RhinoCommon-Kompatibilitaet

Backend (oberleiste.py):
- CREATE_TEXT handler → text_create.create_text()
- SET_TEXT_SETTINGS handler → text_create.save_settings (merge partial)
- State payload: textSettings (immer) + textFonts (einmalig initial,
  via _fonts_sent Flag — Liste aendert sich nicht zur Laufzeit)

Frontend (OberleisteApp + rhinoBridge):
- createText() + setTextSettings() Bridge-Funktionen
- Text-Block 2x2 Grid analog Massstab:
  R1: Font-Dropdown (BarCombo mit text_fields icon) | Size-Input mit "m" suffix
  R2: B/I-Toggles (segmented pill mit accent-Fill bei active) | "+ Text" Button
- Hover-Logik analog View-Toggle (bg → bg-item-hover, color → accent-light)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 23:52:00 +02:00

192 lines
5.6 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 _floating_input():
"""Modal Eto-Dialog mit TextBox bei der Maus-Position. Returns
eingegebenen Text oder None (Escape/leer)."""
import Eto.Forms as forms
import Eto.Drawing as drawing
dlg = forms.Dialog()
dlg.Title = "Text"
dlg.Resizable = False
dlg.MinimumSize = drawing.Size(280, 0)
dlg.Padding = drawing.Padding(6)
tb = forms.TextBox()
tb.PlaceholderText = "Text eingeben — Enter = einfuegen, Esc = abbrechen"
tb.Font = drawing.Font("Helvetica", 13)
tb.Width = 280
result = {"text": None, "committed": False}
def on_keydown(sender, e):
if e.Key == forms.Keys.Enter:
result["text"] = tb.Text or ""
result["committed"] = True
try: dlg.Close()
except Exception: pass
e.Handled = True
elif e.Key == forms.Keys.Escape:
try: dlg.Close()
except Exception: pass
e.Handled = True
tb.KeyDown += on_keydown
layout = forms.StackLayout()
layout.Padding = drawing.Padding(0)
layout.Items.Add(tb)
dlg.Content = layout
# Bei Maus-Cursor positionieren (Position wo User gerade geklickt hat)
try:
m = forms.Mouse.Position
dlg.Location = drawing.Point(int(m.X) + 10, int(m.Y) + 12)
except Exception: pass
# ShowModal blockiert bis Close. Parent = Rhinos Main-Window damit
# der Dialog ueber dem Viewport rendert (Mac).
try:
parent = Rhino.UI.RhinoEtoApp.MainWindow
except Exception:
parent = None
try:
if parent is not None:
dlg.ShowModal(parent)
else:
dlg.ShowModal()
except Exception as ex:
print("[TEXT] dialog show:", ex)
return None
if not result["committed"]: return None
txt = (result["text"] or "").strip()
return txt or 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 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 = _floating_input()
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)