Text-Editor: Rhino RTF-Dialekt + Round-Trip + Oberleiste-Sync
Lange Iteration mit dem Rhino TextEntity-RTF-Parser (siehe MEMORY:
rhino_textentity_rtf_limits.md). Finale Form:
- RTF-Body: per-Segment {\fN\cfN\b\i\ulnone seg}-Groups, \par
zwischen Groups als Linebreak, { }-Space-Group fuer Leerzeilen
(Rhino collapsed sonst aufeinanderfolgende \par). \fs (Font-Size)
ist NICHT unterstuetzt → eine Size pro TextEntity (global).
- htmlToRuns: emittiert \n VOR Block-Elementen wenn schon Content
davor — fixt nested <div>A<div>B</div></div> die sonst A+B ohne
Trenner als ein Run liefern.
- Round-Trip-Erhaltung: editor.innerHTML 1:1 als UserString
"dossier_text_html" persistiert, beim Reopen direkt gesetzt
(kein runsToHtml-Konvertieren das Zeilen verlieren kann).
- Oberleiste-Editing: in-place modify von obj.Geometry + Commit-
Changes statt Duplicate+Replace (Mac Rhino gibt False zurueck
bei RichText-Klonen). Plus _patch_rtf_b_i_ul: regex-flippt
\b/\b0, \i/\i0, \ul/\ulnone global in der RTF damit Bold/Italic/
Underline OFF in der Oberleiste auch wirklich auf DOSSIER-Texte
greift (per-Segment-Codes wuerden te.Font-Aenderung sonst
uebersteuern).
- Stil-ID am Text persistiert + von read_selection_settings
zurueckgelesen → Stil-Dropdown spiegelt Selektion.
- Editor neu: V-Align (Top/Middle/Bottom), Mask-Type (None/Viewport/
Solid) mit Farb-Picker, Case-Transform (upper/lower/capitalize/
invert), Masstaeblich-Toggle (AnnotationScalingEnabled),
Symbol-Popover, Frame-Optionen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+81
-20
@@ -188,6 +188,9 @@ def apply_style(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)
|
||||
# styleId mitschicken damit apply_settings_to_selection ihn als
|
||||
# UserString an die Texte haengt — fuer "Stil aktiv"-Anzeige
|
||||
patch["__style_id__"] = sid
|
||||
apply_settings_to_selection(doc, patch)
|
||||
|
||||
|
||||
@@ -707,6 +710,30 @@ def _pick_text_frame():
|
||||
return None
|
||||
|
||||
|
||||
def _patch_rtf_b_i_ul(rt, bold, italic, underline):
|
||||
"""Patcht alle Bold/Italic/Underline-Codes in der RTF auf den
|
||||
gewuenschten globalen State. Erhaelt aber per-Segment Font/Color/
|
||||
Sup/Sub.
|
||||
|
||||
Wird vom Oberleiste-Path benutzt: die te.Font-Aenderung greift bei
|
||||
DOSSIER-Texten nicht (RTF-per-Segment-Codes ueberschreiben sie).
|
||||
Indem wir die Codes selber auf den globalen Toggle setzen, wirken
|
||||
Bold/Italic/Underline OFF auch tatsaechlich auf den ganzen Text."""
|
||||
import re
|
||||
if not rt: return rt
|
||||
# Bold: \b oder \b0 als komplettes Token (nicht gefolgt von alpha/digit,
|
||||
# damit z.B. \bullet nicht versehentlich matched)
|
||||
pat_b = re.compile(r'\\b0?(?![a-zA-Z0-9])')
|
||||
rt = pat_b.sub(lambda m: '\\b' if bold else '\\b0', rt)
|
||||
# Italic
|
||||
pat_i = re.compile(r'\\i0?(?![a-zA-Z0-9])')
|
||||
rt = pat_i.sub(lambda m: '\\i' if italic else '\\i0', rt)
|
||||
# Underline: \ul (on) oder \ulnone (off) — nicht gefolgt von alpha
|
||||
pat_ul = re.compile(r'\\ul(?:none)?(?![a-zA-Z])')
|
||||
rt = pat_ul.sub(lambda m: '\\ul' if underline else '\\ulnone', rt)
|
||||
return rt
|
||||
|
||||
|
||||
def _apply_font(te, face, bold, italic, underline=False):
|
||||
"""Setzt Font auf TextEntity. Mehrere Konstruktor-Pfade fuer
|
||||
verschiedene RhinoCommon-Versionen:
|
||||
@@ -826,7 +853,6 @@ def apply_settings_to_selection(doc, patch):
|
||||
for obj in selected:
|
||||
try:
|
||||
old = obj.Geometry
|
||||
# Aktuelle Werte lesen (vor Modifikation)
|
||||
cur = old.Font
|
||||
try: cur_face = cur.QuartetName if cur else "Helvetica"
|
||||
except Exception: cur_face = "Helvetica"
|
||||
@@ -837,7 +863,6 @@ def apply_settings_to_selection(doc, patch):
|
||||
try: cur_underline = bool(cur.Underlined) if cur else False
|
||||
except Exception: cur_underline = False
|
||||
|
||||
# Neue Werte aus Patch + Fallback auf aktuell
|
||||
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
|
||||
@@ -845,27 +870,57 @@ def apply_settings_to_selection(doc, patch):
|
||||
size = float(patch["size"]) if "size" in patch else float(old.TextHeight)
|
||||
align = patch["align"] if patch.get("align") in _ALIGNS else None
|
||||
|
||||
# FRESH TextEntity bauen statt Duplicate-Modify. Bypassed
|
||||
# Probleme wo te.Font-Setter wegen Rich-Text-Runs oder
|
||||
# DimensionStyle-Override nicht greift.
|
||||
te = rg.TextEntity()
|
||||
te.Plane = old.Plane
|
||||
try: te.PlainText = old.PlainText
|
||||
is_dossier = False
|
||||
try:
|
||||
is_dossier = obj.Attributes.GetUserString("dossier_text") == "1"
|
||||
except Exception: pass
|
||||
te.TextHeight = size
|
||||
# DimensionStyle entkoppeln damit unser Font nicht von Style
|
||||
# ueberschrieben wird.
|
||||
try: te.DimensionStyleId = System.Guid.Empty
|
||||
except Exception: pass
|
||||
_apply_font(te, face, bool(bold), bool(italic), bool(underline))
|
||||
# Alignment: aus Patch oder vom alten Entity uebernehmen
|
||||
|
||||
# IN-PLACE Modifikation der Live-Geometry (kein Duplicate,
|
||||
# keine fresh-Entity — Mac Rhino hat Probleme mit Replace
|
||||
# auf RichText-Entities die nicht aus dem Doc kommen).
|
||||
old.TextHeight = size
|
||||
_apply_font(old, face, bool(bold), bool(italic), bool(underline))
|
||||
if align:
|
||||
_apply_align(te, align)
|
||||
else:
|
||||
try: te.TextHorizontalAlignment = old.TextHorizontalAlignment
|
||||
_apply_align(old, align)
|
||||
|
||||
# DOSSIER-Texte: die RTF hat per-Segment Codes (\b0, \i0,
|
||||
# \ulnone) die die te.Font-Aenderung uebersteuern. Wir
|
||||
# patchen die Codes global damit Bold/Italic/Underline OFF
|
||||
# auch wirklich greifen.
|
||||
if is_dossier:
|
||||
try:
|
||||
rt = old.RichText
|
||||
if rt:
|
||||
new_rt = _patch_rtf_b_i_ul(
|
||||
rt, bool(bold), bool(italic), bool(underline))
|
||||
if new_rt != rt:
|
||||
old.RichText = new_rt
|
||||
except Exception as ex:
|
||||
print("[TEXT] RTF patch fail:", ex)
|
||||
|
||||
# Style-ID am Text persistieren wenn ueber apply_style
|
||||
# appliziert (Oberleiste-Anzeige "Stil aktiv" bei Selektion)
|
||||
sid = patch.get("__style_id__")
|
||||
if sid:
|
||||
try: obj.Attributes.SetUserString("dossier_text_style_id", sid)
|
||||
except Exception: pass
|
||||
|
||||
doc.Objects.Replace(obj.Id, te)
|
||||
# CommitChanges ist die RhinoObject-API um Aenderungen an der
|
||||
# in-place modifizierten Geometry persistent zu machen.
|
||||
try:
|
||||
ok = obj.CommitChanges()
|
||||
print("[TEXT] CommitChanges: {} (dossier={})".format(ok, is_dossier))
|
||||
except Exception as ex:
|
||||
print("[TEXT] CommitChanges fail:", ex)
|
||||
ok = False
|
||||
|
||||
# Falls CommitChanges nicht greift → Replace als Fallback
|
||||
if not ok:
|
||||
try:
|
||||
ok2 = doc.Objects.Replace(obj.Id, old)
|
||||
print("[TEXT] Replace fallback: {}".format(ok2))
|
||||
except Exception as ex:
|
||||
print("[TEXT] Replace fallback fail:", ex)
|
||||
n += 1
|
||||
except Exception as ex:
|
||||
print("[TEXT] apply selection:", ex)
|
||||
@@ -880,7 +935,8 @@ def read_selection_settings(doc):
|
||||
sel = _selected_text_objects(doc)
|
||||
if not sel: return None
|
||||
try:
|
||||
te = sel[0].Geometry
|
||||
obj = sel[0]
|
||||
te = obj.Geometry
|
||||
font = te.Font
|
||||
face = font.QuartetName if font else "Helvetica"
|
||||
bold = bool(font.Bold) if font else False
|
||||
@@ -893,6 +949,10 @@ def read_selection_settings(doc):
|
||||
if h == Rhino.DocObjects.TextHorizontalAlignment.Center: align = "center"
|
||||
elif h == Rhino.DocObjects.TextHorizontalAlignment.Right: align = "right"
|
||||
except Exception: pass
|
||||
# Style-ID falls am Text gespeichert (= via apply_style appliziert)
|
||||
style_id = None
|
||||
try: style_id = obj.Attributes.GetUserString("dossier_text_style_id") or None
|
||||
except Exception: pass
|
||||
return {
|
||||
"font": face,
|
||||
"size": float(te.TextHeight),
|
||||
@@ -900,6 +960,7 @@ def read_selection_settings(doc):
|
||||
"italic": italic,
|
||||
"underline": underline,
|
||||
"align": align,
|
||||
"styleId": style_id,
|
||||
}
|
||||
except Exception as ex:
|
||||
print("[TEXT] read selection:", ex)
|
||||
|
||||
Reference in New Issue
Block a user