diff --git a/rhino/text_create.py b/rhino/text_create.py index dbab208..8877a81 100644 --- a/rhino/text_create.py +++ b/rhino/text_create.py @@ -197,6 +197,110 @@ def _prompt_for_text(default=""): return None +def _frame_editor_dialog(initial=""): + """InDesign-Stil Editor: multi-line TextArea in Eto-Dialog. User tippt + rein, OK → Text-String zurueck. Esc/Cancel → None.""" + import Eto.Forms as forms + import Eto.Drawing as drawing + dlg = forms.Dialog() + dlg.Title = "Text einfuegen" + dlg.Resizable = True + dlg.MinimumSize = drawing.Size(360, 180) + dlg.Padding = drawing.Padding(10) + + ta = forms.TextArea() + ta.AcceptsReturn = True + ta.AcceptsTab = False + ta.Wrap = True + ta.Text = initial or "" + ta.Font = drawing.Font("Helvetica", 13) + ta.Width = 380 + ta.Height = 160 + + result = {"text": None, "committed": False} + + ok_btn = forms.Button() + ok_btn.Text = "Einfuegen" + cancel_btn = forms.Button() + cancel_btn.Text = "Abbrechen" + + def on_ok(s, e): + result["text"] = ta.Text or "" + result["committed"] = True + try: dlg.Close() + except Exception: pass + def on_cancel(s, e): + try: dlg.Close() + except Exception: pass + + ok_btn.Click += on_ok + cancel_btn.Click += on_cancel + + # Layout: TextArea ueber volle Breite, Buttons rechtsbuendig unten + layout = forms.DynamicLayout() + layout.Spacing = drawing.Size(6, 6) + layout.BeginVertical() + layout.AddRow(ta) + layout.EndVertical() + layout.BeginVertical() + layout.BeginHorizontal() + layout.Add(None, True, False) + layout.Add(cancel_btn) + layout.Add(ok_btn) + layout.EndHorizontal() + layout.EndVertical() + dlg.Content = layout + try: + dlg.DefaultButton = ok_btn + dlg.AbortButton = cancel_btn + except Exception: pass + + try: + parent = Rhino.UI.RhinoEtoApp.MainWindow + if parent is not None: dlg.ShowModal(parent) + else: dlg.ShowModal() + except Exception as ex: + print("[TEXT] frame editor:", ex) + return None + + if not result["committed"]: return None + return (result["text"] or "").strip() or None + + +def _pick_text_frame(): + """Picked 2 Ecken eines Text-Feldes (Rechteck in der XY-Ebene des + ersten Punkts). Returns (origin_pt, width, height) oder None.""" + try: + gp = Rhino.Input.Custom.GetPoint() + gp.SetCommandPrompt("Erste Ecke des Text-Feldes") + gp.Get() + if gp.CommandResult() != Rhino.Commands.Result.Success: return None + p1 = gp.Point() + + gp2 = Rhino.Input.Custom.GetPoint() + gp2.SetCommandPrompt("Gegenueberliegende Ecke") + try: gp2.SetBasePoint(p1, True) + except Exception: pass + try: gp2.DrawLineFromPoint(p1, True) + except Exception: pass + gp2.Get() + if gp2.CommandResult() != Rhino.Commands.Result.Success: return None + p2 = gp2.Point() + + min_x = min(p1.X, p2.X); max_x = max(p1.X, p2.X) + min_y = min(p1.Y, p2.Y); max_y = max(p1.Y, p2.Y) + width = max_x - min_x + height = max_y - min_y + if width < 1e-6 or height < 1e-6: + print("[TEXT] frame zu klein"); return None + # Origin = obere linke Ecke (Text laeuft von dort nach unten) + origin = rg.Point3d(min_x, max_y, p1.Z) + return (origin, width, height) + except Exception as ex: + print("[TEXT] pick frame:", ex) + return None + + def _apply_font(te, face, bold, italic): """Setzt Font auf TextEntity. FromQuartetProperties zuerst (sauberer — konstruiert Font-Objekt direkt mit (face, bold, italic), unabhaengig @@ -339,22 +443,23 @@ def read_selection_settings(doc): def create_text(): - """Triggered von der Oberleiste. Vollstaendiger Workflow.""" + """InDesign-Stil: User picked 2 Ecken (Frame) → Multi-Line-Editor- + Dialog → TextEntity wird im Frame mit Text-Wrap erstellt. Frame-Breite + bestimmt den Word-Wrap.""" 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 + frame = _pick_text_frame() + if frame is None: return + origin, width, height = frame - text = _prompt_for_text() + text = _frame_editor_dialog() if not text: return try: te = rg.TextEntity() - te.Plane = rg.Plane(pt, rg.Vector3d.ZAxis) + te.Plane = rg.Plane(origin, rg.Vector3d.ZAxis) te.PlainText = text try: te.TextHeight = float(settings.get("size", 0.20)) @@ -362,6 +467,16 @@ def create_text(): _apply_font(te, settings.get("font") or "Helvetica", settings.get("bold"), settings.get("italic")) _apply_align(te, settings.get("align") or "left") + # Text-Wrap an der Frame-Breite (Property-Namen variieren je nach + # RhinoCommon-Version — best-effort, schluckt Fehler). + for attr in ("FormatWidth", "TextWidth", "MaskWidth"): + try: + setattr(te, attr, width); break + except Exception: pass + try: te.TextIsWrapped = True + except Exception: + try: te.TextWrap = True + except Exception: pass doc.Objects.AddText(te) doc.Views.Redraw() except Exception as ex: