Text-Block: Bug-Fix + Vectorworks-Style + Edit-Selected
User-Probleme: 1. Floating Eto-Dialog erschien nicht nach GetPoint (Mac-Bug) 2. UI war zu klobig, sollte vectorworks-mässig kompakt sein, "+" als kleines Icon-Symbol 3. Selektierten Text aendern war nicht moeglich Fix 1 — Bug: _floating_input geloescht, ersetzt durch _prompt_for_text() das Rhino.UI.Dialogs.ShowEditBox benutzt. Nativer cross-platform Dialog ohne Eto-Modal-Bug. Workflow: GetPoint → ShowEditBox → AddText. Funktioniert auf Mac. Fix 2 — UI: Text-Block kompakt umgebaut. Reihe 1: [Font ▼] | [Size m] (130 + 60 = 196px) Reihe 2: [B][I][+] segmented pill (gleiche Breite wie Reihe 1) "+" ist jetzt kleines add-icon (size 13), kein "+ Text" Label mehr. Fix 3 — Edit-Selection: neue Funktionen in text_create.py: - _selected_text_objects(doc) → Liste der selektierten TextEntities - read_selection_settings(doc) → Settings der ersten Selektion - apply_settings_to_selection(doc, patch) → wendet font/size/bold/italic auf alle selektierten TextEntities an oberleiste.SET_TEXT_SETTINGS handler appliziert die Aenderung jetzt ZUSAETZLICH auf die Selection (wenn vorhanden) — UND speichert als Default. State enthaelt textSelectionSettings, UI nutzt diese als Anzeige-Werte wenn vorhanden (Werte spiegeln Selektion live). Visual: Border-Color der Size/Segmented-Pill wird accent wenn Selection aktiv ist (Hinweis dass Aenderungen auf Selektion wirken). Sig-Update: textSelectionSettings + lastSetView in last_state_sig damit State neu gepusht wird wenn sich Selection/View aendert. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+83
-60
@@ -72,69 +72,20 @@ def available_fonts():
|
||||
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)
|
||||
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:
|
||||
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()
|
||||
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] dialog show:", ex)
|
||||
print("[TEXT] ShowEditBox:", 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
|
||||
@@ -162,6 +113,78 @@ def _apply_font(te, face, bold, italic):
|
||||
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
|
||||
@@ -173,7 +196,7 @@ def create_text():
|
||||
if rc != Rhino.Commands.Result.Success: return
|
||||
if pt is None: return
|
||||
|
||||
text = _floating_input()
|
||||
text = _prompt_for_text()
|
||||
if not text: return
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user