#! 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)