Text-Create: InDesign-Stil Frame + Multi-Line-Editor (Phase 1)
User-Wunsch: wie InDesign — Text-Feld aufziehen und reinschreiben statt Single-Point + ein-Zeilen-Prompt. Phase 1 — Frame + Multi-Line-Eingabe: - _pick_text_frame(): User picked 2 Ecken (mit Live-Vorschaulinie ab Erstpunkt), liefert (origin, width, height) - _frame_editor_dialog(initial): Eto.Dialog mit forms.TextArea (multi-line, wrap, AcceptsReturn) + OK/Abbrechen-Buttons. Default = OK-Button (Enter im TextArea aber legt Newline → OK per Maus-Klick oder DefaultButton-Enter-Sequenz) - create_text(): pickt Frame → oeffnet Editor → erstellt TextEntity an origin (= obere linke Ecke), Plane mit Z-Axis, Text-Wrap auf Frame- Breite (best-effort: FormatWidth/TextWidth/MaskWidth/TextIsWrapped je nach RhinoCommon-Version vorhanden) Phase 2 (noch nicht): verschiedene Fonts INNERHALB eines Texts (Rich- Text-Runs). Braucht Mapping Eto.RichTextArea → Rhino's RTF-Format. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+122
-7
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user