ab0ecfbf14
User-Wunsch: eigener WYSIWYG-Editor im React/Topbar-GUI-Stil. Topmost. Verschiedene Schriftarten/-Dicken sichtbar im Editor selbst. Neues Backend (rhino/text_editor.py): - TextEditorBridge mit Frame-Daten im Konstruktor, INIT-Push mit Settings + Font-Liste, COMMIT erstellt TextEntity, CANCEL schliesst - open_with_frame(p1, p2, origin, width, height): oeffnet Satellite- Window mit mode='text_editor' + topmost=True - panel_base.open_satellite_window: neuer Parameter topmost (default False) der form.Topmost setzt text_create.create_text: ruft jetzt text_editor.open_with_frame nach dem Frame-Pick. Eto-basierter _dossier_text_editor bleibt im Modul als Fallback aber wird nicht mehr verwendet. Neues Frontend (src/TextEditorApp.jsx, mode='text_editor'): - Layout im DOSSIER-Topbar-Stil (dunkle Pills, accent on hover) - Pill-Helper-Komponente fuer alle Toggle/Action-Buttons - Dropdown-Helper fuer Font + Size - Toolbar Row 1: Font-Dropdown | Size-Dropdown | Color-Picker | Layer-Reset - Toolbar Row 2: B/I/U mit Material-Icons | L/C/R Align | x²/x₂ Sup/Sub - Sonderzeichen-Palette: 41 Unicode-Symbole (Architektur/Math/Pfeile/ Auszeichnungen), Klick inserted am Cursor - WYSIWYG-Editor: contentEditable div mit fontFamily=ausgewaehlt, textAlign=ausgewaehlt — Format-Toolbar wirkt via document.execCommand (bold/italic/underline/justifyLeft/Center/Right/superscript/subscript/ fontName/foreColor) - "Einfuegen" sendet COMMIT mit text (innerText) + settings - "Abbrechen" CANCEL → Bridge schliesst Form Phase 1 Limitation: rendert in Rhino als PlainText mit den globalen Settings (font/size/bold/italic/align/color) — verschiedene Schriftarten INNERHALB des Texts sind im Editor sichtbar aber nicht im finalen Rhino-TextEntity. Phase 2: HTML → Rhino RichText/RTF Mapping. rhinoBridge.js: send() jetzt exportiert (war intern) damit TextEditorApp generisch COMMIT/CANCEL senden kann. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
132 lines
4.4 KiB
Python
132 lines
4.4 KiB
Python
#! python 3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
text_editor.py
|
|
React-WYSIWYG-Editor in Satellite-WebView (Topmost). User picked Frame
|
|
in create_text(), dann oeffnet sich dieser Editor neben dem Frame.
|
|
TextEditorBridge haelt Frame-Daten + Settings, auf COMMIT erstellt es
|
|
die TextEntity und schliesst das Fenster.
|
|
"""
|
|
import os
|
|
import sys
|
|
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)
|
|
|
|
import panel_base
|
|
import text_create
|
|
|
|
|
|
class TextEditorBridge(panel_base.BaseBridge):
|
|
def __init__(self, frame_data, settings, fonts):
|
|
panel_base.BaseBridge.__init__(self, "text_editor")
|
|
self._frame = frame_data # (origin, width, height, p1, p2)
|
|
self._initial_settings = settings
|
|
self._fonts = fonts
|
|
self._form_ref = None
|
|
|
|
def set_form(self, form):
|
|
self._form_ref = form
|
|
|
|
def _on_ready(self):
|
|
self.send("INIT", {
|
|
"settings": self._initial_settings,
|
|
"fonts": self._fonts,
|
|
})
|
|
|
|
def handle(self, data):
|
|
if not isinstance(data, dict): return
|
|
t = data.get("type", "")
|
|
p = data.get("payload") or {}
|
|
if t == "READY":
|
|
self._on_ready()
|
|
elif t == "COMMIT":
|
|
self._commit(p)
|
|
try: self._form_ref.Close()
|
|
except Exception: pass
|
|
elif t == "CANCEL":
|
|
try: self._form_ref.Close()
|
|
except Exception: pass
|
|
|
|
def _commit(self, payload):
|
|
import System
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
if doc is None or self._frame is None: return
|
|
text = (payload.get("text") or "").strip()
|
|
if not text: return
|
|
st = payload.get("settings") or {}
|
|
origin, width, height, _p1, _p2 = self._frame
|
|
|
|
try:
|
|
te = rg.TextEntity()
|
|
te.Plane = rg.Plane(origin, rg.Vector3d.ZAxis)
|
|
te.PlainText = text
|
|
try: te.TextHeight = float(st.get("size") or 0.2)
|
|
except Exception: pass
|
|
text_create._apply_font(
|
|
te,
|
|
st.get("font") or "Helvetica",
|
|
st.get("bold"), st.get("italic"),
|
|
st.get("underline"))
|
|
text_create._apply_align(te, st.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
|
|
|
|
attrs = Rhino.DocObjects.ObjectAttributes()
|
|
col = st.get("color") # [r,g,b] oder None
|
|
if col is not None and len(col) >= 3:
|
|
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-EDITOR] color:", ex)
|
|
attrs.SetUserString("dossier_text", "1")
|
|
doc.Objects.AddText(te, attrs)
|
|
doc.Views.Redraw()
|
|
|
|
# Defaults speichern (ohne color)
|
|
text_create.save_settings(doc, {
|
|
"font": st.get("font"),
|
|
"size": st.get("size"),
|
|
"bold": st.get("bold"),
|
|
"italic": st.get("italic"),
|
|
"underline": st.get("underline"),
|
|
"align": st.get("align"),
|
|
})
|
|
except Exception as ex:
|
|
print("[TEXT-EDITOR] commit:", ex)
|
|
|
|
|
|
def open_with_frame(p1, p2, origin, width, height):
|
|
"""Aufgerufen aus text_create.create_text() nach Frame-Pick.
|
|
Oeffnet das React-WYSIWYG-Editor-Fenster (Topmost) neben dem Frame.
|
|
Non-blocking — Bridge handlet die Eingabe + erstellt TextEntity bei
|
|
COMMIT.
|
|
"""
|
|
doc = Rhino.RhinoDoc.ActiveDoc
|
|
settings = text_create.load_settings(doc)
|
|
fonts = text_create.available_fonts()
|
|
bridge = TextEditorBridge((origin, width, height, p1, p2),
|
|
settings, fonts)
|
|
sc.sticky["text_editor_bridge"] = bridge
|
|
|
|
form = panel_base.open_satellite_window(
|
|
"text_editor",
|
|
title="Dossier Text",
|
|
size=(640, 480),
|
|
bridge=bridge,
|
|
topmost=True)
|
|
if form is not None:
|
|
bridge.set_form(form)
|