9eece87cc4
User: Frame wird gezeichnet aber Editor erscheint nicht — kann nicht schreiben. Eto.Form mit WindowStyle.None + Form.Show() funktioniert auf Mac WebKit/Rhino-Build nicht zuverlaessig (kein Render oder hinter Rhino- Window). Fix: Eto.Dialog mit ShowModal — laeuft proven auf Mac+Windows. Dialog hat normale Chrome (Title-Bar, OK/Cancel-Buttons) aber wird neben dem gepickten Frame positioniert (via vp.WorldToScreen + view.ScreenRectangle → Dialog.Location 20px rechts vom Frame). Tradeoff zu "inline im Viewport": Dialog hat Rahmen, aber ist sichtbar und funktional. Workflow: pick Frame → Dialog poppt neben Frame mit TextArea + OK/Cancel + Cmd/Ctrl+Enter Shortcut + Esc-Abbruch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
567 lines
19 KiB
Python
567 lines
19 KiB
Python
#! 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"
|
|
_STYLES_KEY = "dossier_text_styles"
|
|
_STYLE_ACTIVE_KEY = "dossier_text_style_active"
|
|
|
|
_ALIGNS = ("left", "center", "right")
|
|
|
|
_DEFAULTS = {
|
|
"font": "Helvetica",
|
|
"size": 0.20, # in Model-Units (m bei m-Doc, mm bei mm-Doc)
|
|
"bold": False,
|
|
"italic": False,
|
|
"underline": False,
|
|
"align": "left",
|
|
}
|
|
|
|
|
|
def _normalize(s):
|
|
out = dict(_DEFAULTS)
|
|
if isinstance(s, dict):
|
|
for k, v in s.items():
|
|
if k not in _DEFAULTS: continue
|
|
if k == "align" and v not in _ALIGNS: continue
|
|
out[k] = v
|
|
return out
|
|
|
|
|
|
def load_settings(doc):
|
|
if doc is None: return dict(_DEFAULTS)
|
|
try:
|
|
raw = doc.Strings.GetValue(_SETTINGS_KEY)
|
|
if not raw: return dict(_DEFAULTS)
|
|
return _normalize(json.loads(raw))
|
|
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)
|
|
for k, v in partial.items():
|
|
if k not in _DEFAULTS: continue
|
|
if k == "align" and v not in _ALIGNS: continue
|
|
cur[k] = v
|
|
try:
|
|
doc.Strings.SetString(_SETTINGS_KEY, json.dumps(cur))
|
|
except Exception as ex:
|
|
print("[TEXT] save settings:", ex)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Text-Stile (Presets, analog mass_style): doc.Strings JSON-Liste mit
|
|
# benannten Settings + aktiver Style-ID.
|
|
|
|
def list_styles(doc):
|
|
if doc is None: return []
|
|
try:
|
|
raw = doc.Strings.GetValue(_STYLES_KEY)
|
|
if not raw: return []
|
|
items = json.loads(raw)
|
|
if not isinstance(items, list): return []
|
|
out = []
|
|
for it in items:
|
|
if not isinstance(it, dict): continue
|
|
norm = _normalize(it)
|
|
norm["id"] = it.get("id") or ("ts_" + uuid.uuid4().hex[:8])
|
|
norm["name"] = it.get("name") or "Stil"
|
|
out.append(norm)
|
|
return out
|
|
except Exception as ex:
|
|
print("[TEXT] list_styles:", ex)
|
|
return []
|
|
|
|
|
|
def get_active_style_id(doc):
|
|
if doc is None: return None
|
|
try:
|
|
return doc.Strings.GetValue(_STYLE_ACTIVE_KEY) or None
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def set_active_style_id(doc, sid):
|
|
if doc is None: return
|
|
try:
|
|
doc.Strings.SetString(_STYLE_ACTIVE_KEY, sid or "")
|
|
except Exception: pass
|
|
|
|
|
|
def save_style(doc, name, settings=None):
|
|
"""Speichert aktuelle Settings als benannten Style. Returns die ID."""
|
|
if doc is None or not name: return None
|
|
items = list_styles(doc)
|
|
# Existing same-name → update
|
|
sid = None
|
|
for it in items:
|
|
if it["name"] == name:
|
|
sid = it["id"]; break
|
|
norm = _normalize(settings if settings is not None else load_settings(doc))
|
|
norm["id"] = sid or ("ts_" + uuid.uuid4().hex[:8])
|
|
norm["name"] = name
|
|
if sid:
|
|
items = [norm if it["id"] == sid else it for it in items]
|
|
else:
|
|
items.append(norm)
|
|
try:
|
|
doc.Strings.SetString(_STYLES_KEY, json.dumps(items))
|
|
except Exception as ex:
|
|
print("[TEXT] save_style:", ex)
|
|
return norm["id"]
|
|
|
|
|
|
def delete_style(doc, sid):
|
|
if doc is None or not sid: return
|
|
items = [it for it in list_styles(doc) if it.get("id") != sid]
|
|
try:
|
|
doc.Strings.SetString(_STYLES_KEY, json.dumps(items))
|
|
except Exception: pass
|
|
if get_active_style_id(doc) == sid:
|
|
set_active_style_id(doc, "")
|
|
|
|
|
|
def apply_style(doc, sid):
|
|
"""Wendet einen Style als aktive Defaults an + (wenn Selektion) auch
|
|
auf die selektierten TextEntities."""
|
|
if doc is None or not sid: return
|
|
items = list_styles(doc)
|
|
style = next((it for it in items if it["id"] == sid), None)
|
|
if not style: return
|
|
set_active_style_id(doc, sid)
|
|
# Defaults aus Style schreiben (ohne id/name)
|
|
patch = {k: style[k] for k in style if k in _DEFAULTS}
|
|
save_settings(doc, patch)
|
|
apply_settings_to_selection(doc, patch)
|
|
|
|
|
|
_FONT_FALLBACK = [
|
|
"Helvetica", "Helvetica Neue", "Arial", "Arial Narrow",
|
|
"Times New Roman", "Times", "Courier", "Courier New",
|
|
"Menlo", "Monaco", "Verdana", "Geneva", "Lucida Grande",
|
|
"Avenir", "Avenir Next", "Optima", "Palatino",
|
|
]
|
|
|
|
|
|
def available_fonts():
|
|
"""Sortierte Liste verfuegbarer System-Fonts. Mit Fallback auf
|
|
haeufige Mac-Fonts falls die Rhino-API nichts liefert."""
|
|
try:
|
|
names = Rhino.DocObjects.Font.AvailableFontFaceNames()
|
|
out = sorted({str(n) for n in names if n})
|
|
if out: return out
|
|
except Exception as ex:
|
|
print("[TEXT] available_fonts:", ex)
|
|
return list(_FONT_FALLBACK)
|
|
|
|
|
|
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 _inline_editor(p1, p2, initial=""):
|
|
"""Editor-Dialog mit Multi-Line-TextArea, positioniert NEBEN dem
|
|
gepickten Frame (statt zentriert). Eto.Dialog.ShowModal — reliable
|
|
auf Mac/Win. Returns Text-String oder None."""
|
|
import Eto.Forms as forms
|
|
import Eto.Drawing as drawing
|
|
|
|
# Frame-Position im Screen ermitteln (fuer Dialog-Positionierung)
|
|
sx = sy = None
|
|
sw = max(360, 280)
|
|
sh = max(180, 150)
|
|
try:
|
|
view = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
|
|
vp = view.ActiveViewport
|
|
ok1, x1, y1 = vp.WorldToScreen(p1)
|
|
ok2, x2, y2 = vp.WorldToScreen(p2)
|
|
if ok1 and ok2:
|
|
view_rect = view.ScreenRectangle
|
|
fx = view_rect.X + min(x1, x2)
|
|
fy = view_rect.Y + min(y1, y2)
|
|
fw = abs(x2 - x1); fh = abs(y2 - y1)
|
|
# Dialog rechts neben dem Frame platzieren (oder darunter wenn
|
|
# rechts kein Platz). Falls Frame klein, Dialog hat eigene Min-
|
|
# Groesse.
|
|
sx = int(fx + fw + 20)
|
|
sy = int(fy)
|
|
sw = max(360, fw)
|
|
sh = max(180, fh)
|
|
except Exception as ex:
|
|
print("[TEXT] viewport-coords:", ex)
|
|
|
|
dlg = forms.Dialog()
|
|
dlg.Title = "Text einfuegen"
|
|
dlg.Resizable = True
|
|
dlg.Padding = drawing.Padding(8)
|
|
try: dlg.MinimumSize = drawing.Size(360, 180)
|
|
except Exception: pass
|
|
try: dlg.ClientSize = drawing.Size(int(sw), int(sh))
|
|
except Exception: pass
|
|
# Position neben dem Frame (falls verfuegbar)
|
|
if sx is not None and sy is not None:
|
|
try: dlg.Location = drawing.Point(sx, sy)
|
|
except Exception: pass
|
|
|
|
ta = forms.TextArea()
|
|
ta.AcceptsReturn = True
|
|
ta.AcceptsTab = False
|
|
ta.Wrap = True
|
|
ta.Text = initial or ""
|
|
try: ta.Font = drawing.Font("Helvetica", 13)
|
|
except Exception: pass
|
|
|
|
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
|
|
|
|
# Cmd/Ctrl+Enter Shortcut im TextArea
|
|
def on_keydown(sender, e):
|
|
try:
|
|
is_cmd = (e.Modifiers == forms.Keys.Application or
|
|
e.Modifiers == forms.Keys.Control)
|
|
except Exception:
|
|
is_cmd = False
|
|
if e.Key == forms.Keys.Enter and is_cmd:
|
|
on_ok(sender, e); e.Handled = True
|
|
elif e.Key == forms.Keys.Escape:
|
|
on_cancel(sender, e); e.Handled = True
|
|
ta.KeyDown += on_keydown
|
|
|
|
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] dialog show:", 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 mit Live-Rechteck-Vorschau
|
|
(wie Rhinos _Rectangle). Returns (p1, p2, origin, width, height)
|
|
oder None bei Abbruch."""
|
|
try:
|
|
import System
|
|
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
|
|
|
|
# Live Rechteck-Preview via DynamicDraw
|
|
rect_color = System.Drawing.Color.FromArgb(180, 95, 168, 150) # petrol
|
|
def _draw_rect(sender, e):
|
|
try:
|
|
pt = e.CurrentPoint
|
|
z = p1.Z
|
|
corners = [
|
|
p1,
|
|
Rhino.Geometry.Point3d(pt.X, p1.Y, z),
|
|
Rhino.Geometry.Point3d(pt.X, pt.Y, z),
|
|
Rhino.Geometry.Point3d(p1.X, pt.Y, z),
|
|
p1,
|
|
]
|
|
e.Display.DrawDottedPolyline(corners, rect_color, False)
|
|
except Exception: pass
|
|
gp2.DynamicDraw += _draw_rect
|
|
|
|
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 = rg.Point3d(min_x, max_y, p1.Z)
|
|
return (p1, p2, origin, width, height)
|
|
except Exception as ex:
|
|
print("[TEXT] pick frame:", ex)
|
|
return None
|
|
|
|
|
|
def _apply_font(te, face, bold, italic, underline=False):
|
|
"""Setzt Font auf TextEntity. Mehrere Konstruktor-Pfade fuer
|
|
verschiedene RhinoCommon-Versionen:
|
|
- 5-arg Font(face,bold,italic,underline,strike) — unterstuetzt underline
|
|
- FromQuartetProperties — keine underline
|
|
- FontTable.FindOrCreate — keine underline
|
|
|
|
face wird normalisiert (Bold/Italic-Suffixe entfernen) damit
|
|
QuartetNames wie "Helvetica-Bold" nicht den Quartet-Lookup blockieren."""
|
|
face = str(face or "Helvetica").strip()
|
|
bold = bool(bold)
|
|
italic = bool(italic)
|
|
underline = bool(underline)
|
|
for suffix in ("-BoldItalic", "-BoldOblique", "-Bold", "-Italic",
|
|
"-Oblique", " Bold Italic", " Bold Oblique",
|
|
" Bold", " Italic", " Oblique"):
|
|
if face.endswith(suffix):
|
|
face = face[:-len(suffix)].strip(); break
|
|
print("[TEXT] _apply_font face={!r} bold={} italic={} underline={}".format(
|
|
face, bold, italic, underline))
|
|
# Pfad 1: 5-arg Font-Konstruktor (mit underline+strikethrough)
|
|
try:
|
|
font = Rhino.DocObjects.Font(face, bold, italic, underline, False)
|
|
if font is not None:
|
|
te.Font = font
|
|
return True
|
|
except Exception as ex:
|
|
if underline: print("[TEXT] Font(5-arg) fail:", ex)
|
|
# Pfad 2: 3-arg Font-Konstruktor
|
|
try:
|
|
font = Rhino.DocObjects.Font(face, bold, italic)
|
|
if font is not None:
|
|
te.Font = font
|
|
return True
|
|
except Exception: pass
|
|
# Pfad 3: FromQuartetProperties
|
|
try:
|
|
font = Rhino.DocObjects.Font.FromQuartetProperties(face, bold, italic)
|
|
if font is not None:
|
|
te.Font = font
|
|
return True
|
|
except Exception as ex:
|
|
print("[TEXT] FromQuartet:", ex)
|
|
# Pfad 4: doc.Fonts.FindOrCreate
|
|
try:
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
font_idx = doc.Fonts.FindOrCreate(face, bold, 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] FindOrCreate:", 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_align(te, align):
|
|
"""Setzt TextHorizontalAlignment."""
|
|
try:
|
|
m = {
|
|
"left": Rhino.DocObjects.TextHorizontalAlignment.Left,
|
|
"center": Rhino.DocObjects.TextHorizontalAlignment.Center,
|
|
"right": Rhino.DocObjects.TextHorizontalAlignment.Right,
|
|
}
|
|
if align in m:
|
|
te.TextHorizontalAlignment = m[align]
|
|
return True
|
|
except Exception as ex:
|
|
print("[TEXT] apply align:", ex)
|
|
return False
|
|
|
|
|
|
def apply_settings_to_selection(doc, patch):
|
|
"""Wendet font/size/bold/italic/align 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. Vorher PlainText-
|
|
# Reset damit eventuelle RichText-Formatierungs-Runs nicht
|
|
# das neue te.Font ueberschreiben.
|
|
if any(k in patch for k in ("font", "bold", "italic", "underline")):
|
|
try:
|
|
plain = te.PlainText
|
|
te.PlainText = plain # reset zu plain-mode
|
|
except Exception: pass
|
|
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
|
|
try: cur_underline = bool(cur.Underlined) if cur else False
|
|
except Exception: cur_underline = 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
|
|
underline = patch["underline"] if "underline" in patch else cur_underline
|
|
_apply_font(te, face, bool(bold), bool(italic), bool(underline))
|
|
if "align" in patch and patch["align"] in _ALIGNS:
|
|
_apply_align(te, patch["align"])
|
|
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."""
|
|
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
|
|
try: underline = bool(font.Underlined) if font else False
|
|
except Exception: underline = False
|
|
align = "left"
|
|
try:
|
|
h = te.TextHorizontalAlignment
|
|
if h == Rhino.DocObjects.TextHorizontalAlignment.Center: align = "center"
|
|
elif h == Rhino.DocObjects.TextHorizontalAlignment.Right: align = "right"
|
|
except Exception: pass
|
|
return {
|
|
"font": face,
|
|
"size": float(te.TextHeight),
|
|
"bold": bold,
|
|
"italic": italic,
|
|
"underline": underline,
|
|
"align": align,
|
|
}
|
|
except Exception as ex:
|
|
print("[TEXT] read selection:", ex)
|
|
return None
|
|
|
|
|
|
def create_text():
|
|
"""InDesign-Stil: User zieht Frame (Live-Rechteck-Vorschau) → Inline-
|
|
Editor poppt direkt ueber dem Frame im Viewport → tippen → Cmd+Enter
|
|
fuegt TextEntity ein. Esc bricht ab."""
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
if doc is None: return
|
|
settings = load_settings(doc)
|
|
|
|
frame = _pick_text_frame()
|
|
if frame is None: return
|
|
p1, p2, origin, width, height = frame
|
|
|
|
text = _inline_editor(p1, p2)
|
|
if not text: return
|
|
|
|
try:
|
|
te = rg.TextEntity()
|
|
te.Plane = rg.Plane(origin, 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"),
|
|
settings.get("underline"))
|
|
_apply_align(te, settings.get("align") or "left")
|
|
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:
|
|
print("[TEXT] create:", ex)
|