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