DOSSIER Custom Text-Editor + 3 Eto-Bugfixes + Tagging
User-Wunsch: eigener Editor-Dialog mit Toolbar (Font, Size, Farbe,
B/I/U/Align), Sonderzeichen-Palette, Sub/Superscript. Eigenes "Symbol"
(= unser Topbar-+-Button) triggert ihn.
Neuer _dossier_text_editor(p1, p2, settings, fonts):
- Eto.Dialog "Dossier Text", positioniert neben Frame
- Row 1 (Format): Font-Dropdown | Größe (NumericStepper) | ColorPicker
+ Layer-Reset-Button
- Row 2 (Style): B / I / U Checkboxes + L/C/R Align-RadioButtons +
x² (Hochstellen) / x₂ (Tiefstellen) — konvertieren markierte Ziffern
via Unicode-Map (⁰¹²³... bzw. ₀₁₂...)
- Symbol-Palette: 42 Sonderzeichen in 12er-Reihen, Click inserted am
Cursor: ∅ Ø ⌀ ° ± × ÷ ² ³ ½ ¼ ¾ ⅓ ⅔ ≤ ≥ ≠ ≈ ∞ √ ∆ π µ ← → ↑ ↓ ↔ ↕
• · ▪ ◆ ★ ☆ ✓ ✗ § ¶ © ® ™
- Multi-Line TextArea (560×240, wrap, AcceptsReturn)
- Cmd/Ctrl+Enter = OK, Esc = Cancel
- Returns dict {text, font, size, bold, italic, underline, align, color}
create_text rewired: ruft _dossier_text_editor statt _inline_editor.
- Save settings (ohne color) als neue Defaults
- TextEntity mit allen Properties + Wrap im Frame
- ObjectAttributes.SetUserString("dossier_text", "1") fuer spaeteren
Double-Click-Hook (Phase 2: Doppelklick auf getaggten Text re-oeffnet
unseren Editor statt Rhinos Standard)
- Farbe als ColorFromObject wenn explizit gesetzt, sonst Layer-Farbe
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+356
-13
@@ -197,6 +197,320 @@ def _prompt_for_text(default=""):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _dossier_text_editor(p1, p2, settings, fonts, initial=""):
|
||||||
|
"""DOSSIER Custom Text-Editor: eigene Toolbar (Font, Size, Farbe,
|
||||||
|
B/I/U, Align), Symbol-Palette mit Sonderzeichen, Multi-Line-TextArea.
|
||||||
|
|
||||||
|
Returns dict {text, font, size, bold, italic, underline, align, color}
|
||||||
|
oder None bei Abbruch. color ist (r,g,b)-Tuple oder None (= Layer-Farbe).
|
||||||
|
"""
|
||||||
|
import Eto.Forms as forms
|
||||||
|
import Eto.Drawing as drawing
|
||||||
|
import System
|
||||||
|
|
||||||
|
# Frame-Position fuer Dialog-Positionierung (rechts neben dem Frame)
|
||||||
|
sx = sy = None
|
||||||
|
try:
|
||||||
|
view = Rhino.RhinoDoc.ActiveDoc.Views.ActiveView
|
||||||
|
vp = view.ActiveViewport
|
||||||
|
c1 = vp.WorldToClient(p1)
|
||||||
|
c2 = vp.WorldToClient(p2)
|
||||||
|
view_rect = view.ScreenRectangle
|
||||||
|
fx = view_rect.X + int(min(c1.X, c2.X))
|
||||||
|
fy = view_rect.Y + int(min(c1.Y, c2.Y))
|
||||||
|
fw = abs(int(c2.X - c1.X))
|
||||||
|
sx = int(fx + fw + 20)
|
||||||
|
sy = int(fy)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[TEXT] viewport-coords:", ex)
|
||||||
|
|
||||||
|
# Editor-State (mutable durch Closures)
|
||||||
|
st = {
|
||||||
|
"text": initial or "",
|
||||||
|
"font": settings.get("font") or "Helvetica",
|
||||||
|
"size": float(settings.get("size") or 0.2),
|
||||||
|
"bold": bool(settings.get("bold")),
|
||||||
|
"italic": bool(settings.get("italic")),
|
||||||
|
"underline": bool(settings.get("underline")),
|
||||||
|
"align": settings.get("align") or "left",
|
||||||
|
"color": None, # None = Layer-Farbe
|
||||||
|
}
|
||||||
|
result = {"committed": False, "state": None}
|
||||||
|
|
||||||
|
dlg = forms.Dialog()
|
||||||
|
dlg.Title = "Dossier Text"
|
||||||
|
dlg.Resizable = True
|
||||||
|
dlg.Padding = drawing.Padding(10)
|
||||||
|
try: dlg.MinimumSize = drawing.Size(580, 440)
|
||||||
|
except Exception: pass
|
||||||
|
if sx is not None and sy is not None:
|
||||||
|
try: dlg.Location = drawing.Point(sx, sy)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
# === TextArea zuerst (wird von Symbol-Inserts angesprochen) ===
|
||||||
|
ta = forms.TextArea()
|
||||||
|
ta.AcceptsReturn = True
|
||||||
|
ta.AcceptsTab = False
|
||||||
|
ta.Wrap = True
|
||||||
|
ta.Text = st["text"]
|
||||||
|
try: ta.Font = drawing.Font(st["font"], 14)
|
||||||
|
except Exception: pass
|
||||||
|
try: ta.Size = drawing.Size(560, 240)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
def _update_ta_font():
|
||||||
|
try: ta.Font = drawing.Font(st["font"], 14)
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
# === Toolbar Row 1: Font / Size / Color ===
|
||||||
|
font_dd = forms.DropDown()
|
||||||
|
for f in fonts: font_dd.Items.Add(f)
|
||||||
|
try:
|
||||||
|
for i, f in enumerate(fonts):
|
||||||
|
if f == st["font"]: font_dd.SelectedIndex = i; break
|
||||||
|
except Exception: pass
|
||||||
|
def on_font_change(s, e):
|
||||||
|
try:
|
||||||
|
idx = font_dd.SelectedIndex
|
||||||
|
if idx >= 0 and idx < len(fonts):
|
||||||
|
st["font"] = fonts[idx]
|
||||||
|
_update_ta_font()
|
||||||
|
except Exception: pass
|
||||||
|
font_dd.SelectedIndexChanged += on_font_change
|
||||||
|
|
||||||
|
size_input = forms.NumericStepper()
|
||||||
|
size_input.MinValue = 0.001
|
||||||
|
size_input.MaxValue = 100.0
|
||||||
|
size_input.Increment = 0.05
|
||||||
|
size_input.DecimalPlaces = 3
|
||||||
|
size_input.Value = st["size"]
|
||||||
|
def on_size_change(s, e):
|
||||||
|
try: st["size"] = float(size_input.Value)
|
||||||
|
except Exception: pass
|
||||||
|
size_input.ValueChanged += on_size_change
|
||||||
|
|
||||||
|
color_picker = forms.ColorPicker()
|
||||||
|
try: color_picker.AllowAlpha = False
|
||||||
|
except Exception: pass
|
||||||
|
try: color_picker.Value = drawing.Colors.Black
|
||||||
|
except Exception: pass
|
||||||
|
def on_color_change(s, e):
|
||||||
|
try:
|
||||||
|
c = color_picker.Value
|
||||||
|
st["color"] = (int(c.Rb), int(c.Gb), int(c.Bb))
|
||||||
|
except Exception: pass
|
||||||
|
color_picker.ValueChanged += on_color_change
|
||||||
|
color_reset = forms.Button()
|
||||||
|
color_reset.Text = "Layer"
|
||||||
|
color_reset.ToolTip = "Farbe der Ebene benutzen"
|
||||||
|
def on_color_reset(s, e):
|
||||||
|
st["color"] = None
|
||||||
|
try: color_picker.Value = drawing.Colors.Black
|
||||||
|
except Exception: pass
|
||||||
|
color_reset.Click += on_color_reset
|
||||||
|
|
||||||
|
# === Toolbar Row 2: B I U + Align ===
|
||||||
|
bold_check = forms.CheckBox()
|
||||||
|
bold_check.Text = "B"
|
||||||
|
bold_check.Checked = st["bold"]
|
||||||
|
bold_check.CheckedChanged += lambda s, e: st.update(bold=bool(bold_check.Checked))
|
||||||
|
|
||||||
|
italic_check = forms.CheckBox()
|
||||||
|
italic_check.Text = "I"
|
||||||
|
italic_check.Checked = st["italic"]
|
||||||
|
italic_check.CheckedChanged += lambda s, e: st.update(italic=bool(italic_check.Checked))
|
||||||
|
|
||||||
|
underline_check = forms.CheckBox()
|
||||||
|
underline_check.Text = "U"
|
||||||
|
underline_check.Checked = st["underline"]
|
||||||
|
underline_check.CheckedChanged += lambda s, e: st.update(underline=bool(underline_check.Checked))
|
||||||
|
|
||||||
|
align_left = forms.RadioButton()
|
||||||
|
align_left.Text = "L"
|
||||||
|
align_center = forms.RadioButton(align_left)
|
||||||
|
align_center.Text = "C"
|
||||||
|
align_right = forms.RadioButton(align_left)
|
||||||
|
align_right.Text = "R"
|
||||||
|
if st["align"] == "center": align_center.Checked = True
|
||||||
|
elif st["align"] == "right": align_right.Checked = True
|
||||||
|
else: align_left.Checked = True
|
||||||
|
align_left.CheckedChanged += lambda s, e: (st.update(align="left") if align_left.Checked else None)
|
||||||
|
align_center.CheckedChanged += lambda s, e: (st.update(align="center") if align_center.Checked else None)
|
||||||
|
align_right.CheckedChanged += lambda s, e: (st.update(align="right") if align_right.Checked else None)
|
||||||
|
|
||||||
|
# === Symbol-Palette (Unicode-Buttons, inserten am Cursor) ===
|
||||||
|
# Architektur, Mathe, Pfeile, Auszeichnungen
|
||||||
|
SYMBOLS = [
|
||||||
|
'∅', 'Ø', '⌀', '°', '±', '×', '÷',
|
||||||
|
'²', '³', '½', '¼', '¾', '⅓', '⅔',
|
||||||
|
'≤', '≥', '≠', '≈', '∞', '√', '∆', 'π', 'µ',
|
||||||
|
'←', '→', '↑', '↓', '↔', '↕',
|
||||||
|
'•', '·', '▪', '◆', '★', '☆',
|
||||||
|
'✓', '✗', '§', '¶', '©', '®', '™',
|
||||||
|
]
|
||||||
|
|
||||||
|
def insert_at_cursor(s_char):
|
||||||
|
try:
|
||||||
|
pos = ta.CaretIndex
|
||||||
|
txt = ta.Text or ""
|
||||||
|
ta.Text = txt[:pos] + s_char + txt[pos:]
|
||||||
|
try: ta.CaretIndex = pos + len(s_char)
|
||||||
|
except Exception: pass
|
||||||
|
try: ta.Focus()
|
||||||
|
except Exception: pass
|
||||||
|
except Exception as ex:
|
||||||
|
print("[TEXT] insert:", ex)
|
||||||
|
|
||||||
|
def make_sym_handler(s_char):
|
||||||
|
return lambda sender, e: insert_at_cursor(s_char)
|
||||||
|
|
||||||
|
sym_panel = forms.DynamicLayout()
|
||||||
|
sym_panel.Spacing = drawing.Size(2, 2)
|
||||||
|
# 7 Symbole pro Reihe
|
||||||
|
PER_ROW = 12
|
||||||
|
for row_start in range(0, len(SYMBOLS), PER_ROW):
|
||||||
|
sym_panel.BeginHorizontal()
|
||||||
|
for sym in SYMBOLS[row_start:row_start + PER_ROW]:
|
||||||
|
btn = forms.Button()
|
||||||
|
btn.Text = sym
|
||||||
|
btn.MinimumSize = drawing.Size(28, 24)
|
||||||
|
btn.ToolTip = "Einfuegen: " + sym
|
||||||
|
btn.Click += make_sym_handler(sym)
|
||||||
|
sym_panel.Add(btn)
|
||||||
|
sym_panel.Add(None, True, False)
|
||||||
|
sym_panel.EndHorizontal()
|
||||||
|
|
||||||
|
# === Hochstellen / Tiefstellen (Unicode-Konvertierung der Selektion
|
||||||
|
# bzw. der zuletzt eingegebenen Zahl. Einfacher Quick-Insert: ²³ etc.)
|
||||||
|
SUP_MAP = {'0':'⁰','1':'¹','2':'²','3':'³','4':'⁴','5':'⁵','6':'⁶','7':'⁷','8':'⁸','9':'⁹','+':'⁺','-':'⁻','=':'⁼'}
|
||||||
|
SUB_MAP = {'0':'₀','1':'₁','2':'₂','3':'₃','4':'₄','5':'₅','6':'₆','7':'₇','8':'₈','9':'₉','+':'₊','-':'₋','=':'₌'}
|
||||||
|
|
||||||
|
def convert_selection(mapping):
|
||||||
|
try:
|
||||||
|
sel = ta.SelectedText or ""
|
||||||
|
if not sel: return
|
||||||
|
converted = "".join(mapping.get(c, c) for c in sel)
|
||||||
|
txt = ta.Text or ""
|
||||||
|
start = ta.Selection.Start
|
||||||
|
ta.Text = txt[:start] + converted + txt[start + len(sel):]
|
||||||
|
try: ta.Focus()
|
||||||
|
except Exception: pass
|
||||||
|
except Exception as ex:
|
||||||
|
print("[TEXT] convert:", ex)
|
||||||
|
|
||||||
|
sup_btn = forms.Button()
|
||||||
|
sup_btn.Text = "x²"
|
||||||
|
sup_btn.ToolTip = "Markierte Ziffern hochstellen"
|
||||||
|
sup_btn.Click += lambda s, e: convert_selection(SUP_MAP)
|
||||||
|
|
||||||
|
sub_btn = forms.Button()
|
||||||
|
sub_btn.Text = "x₂"
|
||||||
|
sub_btn.ToolTip = "Markierte Ziffern tiefstellen"
|
||||||
|
sub_btn.Click += lambda s, e: convert_selection(SUB_MAP)
|
||||||
|
|
||||||
|
# === Buttons unten ===
|
||||||
|
ok_btn = forms.Button()
|
||||||
|
ok_btn.Text = "Einfuegen"
|
||||||
|
cancel_btn = forms.Button()
|
||||||
|
cancel_btn.Text = "Abbrechen"
|
||||||
|
def on_ok(s, e):
|
||||||
|
st["text"] = ta.Text or ""
|
||||||
|
result["committed"] = True
|
||||||
|
result["state"] = dict(st)
|
||||||
|
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 im TextArea = OK
|
||||||
|
def on_ta_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_ta_keydown
|
||||||
|
|
||||||
|
# === Layout zusammenbauen ===
|
||||||
|
def lbl(text, width=None):
|
||||||
|
l = forms.Label()
|
||||||
|
l.Text = text
|
||||||
|
try: l.VerticalAlignment = forms.VerticalAlignment.Center
|
||||||
|
except Exception: pass
|
||||||
|
return l
|
||||||
|
|
||||||
|
layout = forms.DynamicLayout()
|
||||||
|
layout.Spacing = drawing.Size(8, 8)
|
||||||
|
|
||||||
|
# Row 1: Font + Size + Color
|
||||||
|
layout.BeginHorizontal()
|
||||||
|
layout.Add(lbl("Schrift"))
|
||||||
|
layout.Add(font_dd, True, False)
|
||||||
|
layout.Add(lbl("Grösse"))
|
||||||
|
layout.Add(size_input)
|
||||||
|
layout.Add(lbl("Farbe"))
|
||||||
|
layout.Add(color_picker)
|
||||||
|
layout.Add(color_reset)
|
||||||
|
layout.EndHorizontal()
|
||||||
|
|
||||||
|
# Row 2: B I U + Align + Sup/Sub
|
||||||
|
layout.BeginHorizontal()
|
||||||
|
layout.Add(bold_check)
|
||||||
|
layout.Add(italic_check)
|
||||||
|
layout.Add(underline_check)
|
||||||
|
layout.Add(lbl(" "))
|
||||||
|
layout.Add(align_left)
|
||||||
|
layout.Add(align_center)
|
||||||
|
layout.Add(align_right)
|
||||||
|
layout.Add(lbl(" "))
|
||||||
|
layout.Add(sup_btn)
|
||||||
|
layout.Add(sub_btn)
|
||||||
|
layout.Add(None, True, False)
|
||||||
|
layout.EndHorizontal()
|
||||||
|
|
||||||
|
# Symbol-Palette
|
||||||
|
layout.BeginVertical()
|
||||||
|
layout.AddRow(lbl("Sonderzeichen"))
|
||||||
|
layout.AddRow(sym_panel)
|
||||||
|
layout.EndVertical()
|
||||||
|
|
||||||
|
# TextArea
|
||||||
|
layout.BeginVertical()
|
||||||
|
layout.AddRow(ta)
|
||||||
|
layout.EndVertical()
|
||||||
|
|
||||||
|
# Buttons unten
|
||||||
|
layout.BeginHorizontal()
|
||||||
|
layout.Add(None, True, False)
|
||||||
|
layout.Add(cancel_btn)
|
||||||
|
layout.Add(ok_btn)
|
||||||
|
layout.EndHorizontal()
|
||||||
|
|
||||||
|
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:", ex)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not result["committed"]: return None
|
||||||
|
return result["state"]
|
||||||
|
|
||||||
|
|
||||||
def _inline_editor(p1, p2, initial=""):
|
def _inline_editor(p1, p2, initial=""):
|
||||||
"""Editor-Dialog mit Multi-Line-TextArea, positioniert NEBEN dem
|
"""Editor-Dialog mit Multi-Line-TextArea, positioniert NEBEN dem
|
||||||
gepickten Frame (statt zentriert). Eto.Dialog.ShowModal — reliable
|
gepickten Frame (statt zentriert). Eto.Dialog.ShowModal — reliable
|
||||||
@@ -548,40 +862,69 @@ def read_selection_settings(doc):
|
|||||||
|
|
||||||
|
|
||||||
def create_text():
|
def create_text():
|
||||||
"""InDesign-Stil: User zieht Frame (Live-Rechteck-Vorschau) → Inline-
|
"""DOSSIER Custom Text-Workflow:
|
||||||
Editor poppt direkt ueber dem Frame im Viewport → tippen → Cmd+Enter
|
1. Frame ziehen (Live-Rechteck-Vorschau)
|
||||||
fuegt TextEntity ein. Esc bricht ab."""
|
2. _dossier_text_editor (eigener Editor mit Toolbar, Sonderzeichen,
|
||||||
|
Farbe, Sub/Superscript) oeffnet sich neben dem Frame
|
||||||
|
3. TextEntity wird im Frame mit allen gewaehlten Settings erstellt
|
||||||
|
und mit UserString "dossier_text=1" getagged (fuer evtl. spaeteren
|
||||||
|
Double-Click-Hook auf unseren Editor)
|
||||||
|
"""
|
||||||
|
import System
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is None: return
|
if doc is None: return
|
||||||
settings = load_settings(doc)
|
settings = load_settings(doc)
|
||||||
|
fonts = available_fonts()
|
||||||
|
|
||||||
frame = _pick_text_frame()
|
frame = _pick_text_frame()
|
||||||
if frame is None: return
|
if frame is None: return
|
||||||
p1, p2, origin, width, height = frame
|
p1, p2, origin, width, height = frame
|
||||||
|
|
||||||
text = _inline_editor(p1, p2)
|
new_state = _dossier_text_editor(p1, p2, settings, fonts)
|
||||||
|
if not new_state: return
|
||||||
|
text = (new_state.get("text") or "").strip()
|
||||||
if not text: return
|
if not text: return
|
||||||
|
|
||||||
|
# Defaults aus Editor uebernehmen (ohne color)
|
||||||
|
save_settings(doc, {
|
||||||
|
"font": new_state.get("font"),
|
||||||
|
"size": new_state.get("size"),
|
||||||
|
"bold": new_state.get("bold"),
|
||||||
|
"italic": new_state.get("italic"),
|
||||||
|
"underline": new_state.get("underline"),
|
||||||
|
"align": new_state.get("align"),
|
||||||
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
te = rg.TextEntity()
|
te = rg.TextEntity()
|
||||||
te.Plane = rg.Plane(origin, rg.Vector3d.ZAxis)
|
te.Plane = rg.Plane(origin, rg.Vector3d.ZAxis)
|
||||||
te.PlainText = text
|
te.PlainText = text
|
||||||
try:
|
try: te.TextHeight = float(new_state.get("size") or 0.2)
|
||||||
te.TextHeight = float(settings.get("size", 0.20))
|
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
_apply_font(te, settings.get("font") or "Helvetica",
|
_apply_font(te, new_state.get("font") or "Helvetica",
|
||||||
settings.get("bold"), settings.get("italic"),
|
new_state.get("bold"), new_state.get("italic"),
|
||||||
settings.get("underline"))
|
new_state.get("underline"))
|
||||||
_apply_align(te, settings.get("align") or "left")
|
_apply_align(te, new_state.get("align") or "left")
|
||||||
for attr in ("FormatWidth", "TextWidth", "MaskWidth"):
|
for attr in ("FormatWidth", "TextWidth", "MaskWidth"):
|
||||||
try:
|
try: setattr(te, attr, width); break
|
||||||
setattr(te, attr, width); break
|
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
try: te.TextIsWrapped = True
|
try: te.TextIsWrapped = True
|
||||||
except Exception:
|
except Exception:
|
||||||
try: te.TextWrap = True
|
try: te.TextWrap = True
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
doc.Objects.AddText(te)
|
|
||||||
|
# Object-Attribute: Farbe wenn explizit gesetzt + DOSSIER-Tag
|
||||||
|
attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
|
col = new_state.get("color")
|
||||||
|
if col is not None:
|
||||||
|
try:
|
||||||
|
attrs.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject
|
||||||
|
attrs.ObjectColor = System.Drawing.Color.FromArgb(
|
||||||
|
int(col[0]), int(col[1]), int(col[2]))
|
||||||
|
except Exception as ex:
|
||||||
|
print("[TEXT] color attr:", ex)
|
||||||
|
attrs.SetUserString("dossier_text", "1")
|
||||||
|
doc.Objects.AddText(te, attrs)
|
||||||
doc.Views.Redraw()
|
doc.Views.Redraw()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[TEXT] create:", ex)
|
print("[TEXT] create:", ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user