DOSSIER Multi-Phase: C#-Plugin + Yak + Wandstile + UX-Polish
- C#-Plugin "DOSSIER" mit 23 nativen Commands (dWall, dDoor, ..., dSection)
- Native Command-Namen + Autocomplete + saubere History
- Idle-Defer + RhinoCode-API → kein _-RunPythonScript-Echo
- Yak-Paket via build.sh, Install in ~/Library/.../packages/8.0/
- Launcher (Tauri):
- dossier_init Tauri-Command + Setup-Tab in Settings
- Yak-Install + StartupCommands-XML + Window-Layout in einem Schritt
- clean-rhino.sh fuer reproduzierbare Resets
- check_dossier_initialized triggert Auto-Open-Setup beim ersten Start
- Wand-Architektur:
- Chain-Logik DEAKTIVIERT → jede Wand baut eigenes Volume (individuell
anwaehlbar, einzeln loeschbar)
- Polyline-Wand: jedes Segment = eigene Wand
- Smart-Split fuer wand_axis/decke/dach/raum/aussparung/traeger
- Auto-Group axis+volume → kein ChooseOne-Dialog, Delete loescht beides
- Stale-Mitre-Fix: Joint-Cache wird vor jedem Wand-Regen invalidiert
- T-Junction-Tolerance auf 1mm (war 1cm, lieferte falsche T-Mitres)
- Wand-Stile:
- Schema in dossier_project_settings.wand_styles (Material + Prio +
Default-Dicke + Referenz, oder Layered mit Schichten)
- dWall-Command Stil-Picker
- ProjectSettingsDialog: Sidebar-Layout (Pill-Selection) +
Wandstile-Tab mit Liste/Editor
- _wand_chain_compat benutzt style_id
- Prio-Dominanz: hoehere Prio gewinnt Eckverbindung, niedrigere wird
T-mitered (siehe _resolve_corner_miter)
- Cmd+G fuer Group (Geschoss-Up auf Alias 'gu')
- Welcome + Cheatsheet borderless mit X/Back-Buttons
- BeginCommand-Hook fuer Gestaltung-Panel-Auto-Open
- panel_base: Python.NET-Enum-Fix fuer Material-Render
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
welcome.py
|
||||
Welcome-Screen + Shortcuts-Cheatsheet als WebView-Dialog im DOSSIER-Style
|
||||
(passend zum Splashscreen — Petrol-Gradient, Mono-Font).
|
||||
|
||||
Funktionen:
|
||||
- show_welcome() — erscheint NACH dem Splash (eigener Idle-Timer), einmal
|
||||
pro Version. User kann "nicht mehr anzeigen" rechts unten anklicken.
|
||||
- show_cheatsheet() — DOSSIER-Shortcut-Liste, aufrufbar via dkeys-Alias.
|
||||
|
||||
Marker-Datei fuer "schon gesehen" wird in
|
||||
~/Library/Application Support/ch.gabrielevarano.Dossier/welcome_shown abgelegt.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import Rhino
|
||||
|
||||
|
||||
DOSSIER_VERSION = "0.6.3"
|
||||
DOSSIER_GITHUB = "https://github.com/karimgvarano/DOSSIER"
|
||||
DOSSIER_SUPPORT_EMAIL = "karim@gabrielevarano.ch"
|
||||
|
||||
_WELCOME_DIR = os.path.expanduser(
|
||||
"~/Library/Application Support/ch.gabrielevarano.Dossier")
|
||||
_WELCOME_FLAG = os.path.join(_WELCOME_DIR, "welcome_shown.txt")
|
||||
_WELCOME_OPTOUT = os.path.join(_WELCOME_DIR, "welcome_dontshow.txt")
|
||||
_SPLASH_MIN_DELAY_SEC = 3.5
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_SHORTCUTS_JSON = os.path.join(_HERE, "aliases", "shortcuts_default.json")
|
||||
|
||||
|
||||
def _has_optout():
|
||||
return os.path.exists(_WELCOME_OPTOUT)
|
||||
|
||||
|
||||
def _has_seen_version(version):
|
||||
try:
|
||||
if not os.path.exists(_WELCOME_FLAG): return False
|
||||
with open(_WELCOME_FLAG, "r") as f:
|
||||
return f.read().strip() == version
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _mark_seen(version):
|
||||
try:
|
||||
os.makedirs(_WELCOME_DIR, exist_ok=True)
|
||||
with open(_WELCOME_FLAG, "w") as f:
|
||||
f.write(version)
|
||||
except Exception as ex:
|
||||
print("[WELCOME] mark-seen err:", ex)
|
||||
|
||||
|
||||
def _write_optout():
|
||||
try:
|
||||
os.makedirs(_WELCOME_DIR, exist_ok=True)
|
||||
with open(_WELCOME_OPTOUT, "w") as f:
|
||||
f.write("1")
|
||||
except Exception as ex:
|
||||
print("[WELCOME] optout-write err:", ex)
|
||||
|
||||
|
||||
def _load_shortcuts():
|
||||
try:
|
||||
with open(_SHORTCUTS_JSON, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
items = []
|
||||
for k, v in data.items():
|
||||
if k.startswith("_") or not isinstance(v, dict): continue
|
||||
items.append({
|
||||
"id": k,
|
||||
"trigger": v.get("trigger", ""),
|
||||
"label": v.get("label", k),
|
||||
"type": v.get("type", ""),
|
||||
})
|
||||
return items
|
||||
except Exception as ex:
|
||||
print("[WELCOME] shortcuts-load err:", ex)
|
||||
return []
|
||||
|
||||
|
||||
# ---- HTML — DOSSIER-Style passend zum Splash ----------------------------
|
||||
|
||||
_WELCOME_HTML = """<!DOCTYPE html>
|
||||
<html lang="de"><head><meta charset="utf-8"/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=DM+Mono:ital,wght@0,300;0,400;0,500&family=Playfair+Display:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
:root {{
|
||||
--accent: #5fa896; --accent-soft: #6fb5a3; --accent-deep: #2f5d54;
|
||||
--paper: #fff; --paper-mute: rgba(255,255,255,0.78); --paper-faint: rgba(255,255,255,0.5);
|
||||
--font-display: Krungthep, 'Archivo Black', sans-serif;
|
||||
--font-serif: 'Playfair Display', serif;
|
||||
--font-mono: 'DM Mono', 'Menlo', monospace;
|
||||
}}
|
||||
* {{ box-sizing:border-box; }}
|
||||
html, body {{
|
||||
margin:0; padding:0; width:100%; height:100%; background:transparent !important;
|
||||
color:var(--paper); overflow:hidden; font-family:var(--font-mono); user-select:none;
|
||||
-webkit-user-select:none;
|
||||
}}
|
||||
.frame {{
|
||||
box-sizing:border-box; width:100%; height:100%; padding:28px 32px 24px;
|
||||
display:flex; flex-direction:column;
|
||||
background: radial-gradient(120% 140% at 0% 0%, var(--accent-soft) 0%, var(--accent) 55%, var(--accent-deep) 130%);
|
||||
border-radius:16px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
|
||||
}}
|
||||
.brand-row {{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }}
|
||||
.brand {{
|
||||
font-family:var(--font-display); font-size:32px; letter-spacing:-0.01em;
|
||||
line-height:1; color:var(--paper);
|
||||
}}
|
||||
.brand-dot {{ color:var(--accent-deep); }}
|
||||
.version {{
|
||||
font-family:var(--font-mono); font-size:10px; letter-spacing:0.10em;
|
||||
color:var(--paper-mute); text-transform:uppercase;
|
||||
}}
|
||||
.title {{
|
||||
font-family:var(--font-serif); font-size:22px; line-height:1.3;
|
||||
color:var(--paper); margin-top:20px; font-weight:500;
|
||||
}}
|
||||
.intro {{
|
||||
font-size:11px; line-height:1.65; color:var(--paper-mute); margin-top:10px;
|
||||
letter-spacing:0.02em;
|
||||
}}
|
||||
.section-title {{
|
||||
font-size:9px; letter-spacing:0.18em; text-transform:uppercase;
|
||||
color:var(--paper-faint); margin:22px 0 10px;
|
||||
}}
|
||||
.links {{ display:flex; flex-direction:column; gap:8px; }}
|
||||
a {{ color:inherit; text-decoration:none; }}
|
||||
.link {{
|
||||
display:flex; align-items:flex-start; gap:12px;
|
||||
padding:10px 14px; border-radius:6px; cursor:pointer;
|
||||
background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.12);
|
||||
transition:background 0.15s;
|
||||
color:var(--paper); text-decoration:none;
|
||||
}}
|
||||
.link:hover {{ background:rgba(255,255,255,0.16); }}
|
||||
.link-icon {{
|
||||
font-family:var(--font-display); font-size:14px; color:var(--accent-deep);
|
||||
background:var(--paper); width:24px; height:24px; border-radius:50%;
|
||||
display:flex; align-items:center; justify-content:center; flex-shrink:0;
|
||||
margin-top:1px;
|
||||
}}
|
||||
.link-content {{ flex:1; min-width:0; }}
|
||||
.link-title {{ font-size:12px; color:var(--paper); font-weight:500; }}
|
||||
.link-desc {{ font-size:10px; color:var(--paper-mute); margin-top:2px; }}
|
||||
kbd {{
|
||||
background:rgba(0,0,0,0.18); padding:1px 6px; border-radius:3px;
|
||||
font-family:var(--font-mono); font-size:10px; color:var(--paper);
|
||||
border:1px solid rgba(255,255,255,0.15);
|
||||
}}
|
||||
.footer {{
|
||||
margin-top:auto; display:flex; align-items:center; justify-content:space-between;
|
||||
padding-top:18px; gap:12px;
|
||||
}}
|
||||
.footer-meta {{
|
||||
font-size:9px; letter-spacing:0.14em; color:var(--paper-faint);
|
||||
text-transform:uppercase;
|
||||
}}
|
||||
.optout {{
|
||||
display:flex; align-items:center; gap:6px; cursor:pointer;
|
||||
font-size:10px; color:var(--paper-mute); user-select:none;
|
||||
}}
|
||||
.optout:hover {{ color:var(--paper); }}
|
||||
.optout input {{ accent-color:var(--paper); margin:0; }}
|
||||
.win-ctrl {{
|
||||
position:absolute; top:14px; right:16px; display:flex; gap:6px; z-index:20;
|
||||
}}
|
||||
.win-btn {{
|
||||
width:22px; height:22px; border-radius:50%; cursor:pointer;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
background:rgba(0,0,0,0.18); border:1px solid rgba(255,255,255,0.18);
|
||||
color:var(--paper); font-family:var(--font-mono); font-size:13px;
|
||||
text-decoration:none; transition:background 0.12s;
|
||||
line-height:1; user-select:none;
|
||||
}}
|
||||
.win-btn:hover {{ background:rgba(0,0,0,0.32); }}
|
||||
</style></head><body>
|
||||
<div class="frame">
|
||||
<div class="win-ctrl">
|
||||
<a class="win-btn" href="dossier:close" title="Schliessen">×</a>
|
||||
</div>
|
||||
<div class="brand-row">
|
||||
<div class="brand">DOSSIER<span class="brand-dot">.</span></div>
|
||||
<div class="version">Version {ver}</div>
|
||||
</div>
|
||||
|
||||
<div class="title">Willkommen im Studio</div>
|
||||
<div class="intro">
|
||||
DOSSIER ist dein Architektur-Studio-Plugin fuer Rhino 8 —
|
||||
Waende, Decken, Treppen, Fenster, Tueren, Raumstempel,
|
||||
Layouts. Alles aus einer Hand, im selben Stil.
|
||||
</div>
|
||||
|
||||
<div class="section-title">Einstieg</div>
|
||||
<div class="links">
|
||||
<a class="link" href="dossier:cheatsheet">
|
||||
<div class="link-icon">⌘</div>
|
||||
<div class="link-content">
|
||||
<div class="link-title">Shortcuts & Cheatsheet</div>
|
||||
<div class="link-desc">Tippe <kbd>dkeys</kbd> im Command-Prompt fuer die volle Liste</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="link" href="{github}" target="_blank">
|
||||
<div class="link-icon">i</div>
|
||||
<div class="link-content">
|
||||
<div class="link-title">Einfuehrung & Doku</div>
|
||||
<div class="link-desc">{github}</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="link" href="{github}/releases" target="_blank">
|
||||
<div class="link-icon">v</div>
|
||||
<div class="link-content">
|
||||
<div class="link-title">Changelog</div>
|
||||
<div class="link-desc">Was ist neu in dieser Version</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="link" href="mailto:{email}" target="_blank">
|
||||
<div class="link-icon">?</div>
|
||||
<div class="link-content">
|
||||
<div class="link-title">Support & Problem melden</div>
|
||||
<div class="link-desc">{email} oder GitHub-Issues</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-meta">AGPL-3.0 · Karim Gabriele Varano</div>
|
||||
<label class="optout">
|
||||
<input type="checkbox" id="optout" onchange="window.location='dossier:optout?'+this.checked"/>
|
||||
Nicht mehr anzeigen
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>"""
|
||||
|
||||
|
||||
_CHEATSHEET_HTML = """<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=DM+Mono:ital,wght@0,300;0,400;0,500&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
:root {{
|
||||
--accent: #5fa896; --accent-soft: #6fb5a3; --accent-deep: #2f5d54;
|
||||
--paper: #fff; --paper-mute: rgba(255,255,255,0.78); --paper-faint: rgba(255,255,255,0.5);
|
||||
--font-display: Krungthep, 'Archivo Black', sans-serif;
|
||||
--font-mono: 'DM Mono', 'Menlo', monospace;
|
||||
}}
|
||||
* {{ box-sizing:border-box; }}
|
||||
html, body {{
|
||||
margin:0; padding:0; width:100%; height:100%; background:transparent !important;
|
||||
color:var(--paper); overflow:auto; font-family:var(--font-mono);
|
||||
}}
|
||||
.frame {{
|
||||
box-sizing:border-box; min-height:100%; padding:24px 28px;
|
||||
background: radial-gradient(120% 140% at 0% 0%, var(--accent-soft) 0%, var(--accent) 55%, var(--accent-deep) 130%);
|
||||
border-radius:16px;
|
||||
}}
|
||||
.brand-row {{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; }}
|
||||
.brand {{ font-family:var(--font-display); font-size:24px; line-height:1; color:var(--paper); }}
|
||||
.brand-dot {{ color:var(--accent-deep); }}
|
||||
.version {{ font-family:var(--font-mono); font-size:10px; letter-spacing:0.10em; color:var(--paper-mute); text-transform:uppercase; }}
|
||||
h2 {{
|
||||
font-size:10px; letter-spacing:0.18em; color:var(--paper); margin:18px 0 8px;
|
||||
text-transform:uppercase; font-weight:500;
|
||||
}}
|
||||
table {{ width:100%; border-collapse:collapse; }}
|
||||
td {{ padding:5px 8px; border-bottom:1px solid rgba(255,255,255,0.12); vertical-align:middle; }}
|
||||
td:first-child {{ width:170px; }}
|
||||
kbd {{
|
||||
background:rgba(0,0,0,0.18); padding:2px 8px; border-radius:3px;
|
||||
font-family:var(--font-mono); font-size:11px; color:var(--paper);
|
||||
border:1px solid rgba(255,255,255,0.18);
|
||||
}}
|
||||
.lab {{ color:var(--paper); font-size:11px; }}
|
||||
.badge {{
|
||||
font-size:9px; padding:1px 5px; border-radius:2px; margin-left:6px;
|
||||
background:rgba(255,255,255,0.12); color:var(--paper-mute);
|
||||
font-family:var(--font-mono);
|
||||
}}
|
||||
.win-ctrl {{
|
||||
position:fixed; top:14px; right:18px; display:flex; gap:6px; z-index:20;
|
||||
}}
|
||||
.win-btn {{
|
||||
width:22px; height:22px; border-radius:50%; cursor:pointer;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
background:rgba(0,0,0,0.22); border:1px solid rgba(255,255,255,0.18);
|
||||
color:var(--paper); font-family:var(--font-mono); font-size:13px;
|
||||
text-decoration:none; transition:background 0.12s;
|
||||
line-height:1; user-select:none;
|
||||
}}
|
||||
.win-btn:hover {{ background:rgba(0,0,0,0.38); }}
|
||||
</style></head><body>
|
||||
<div class="frame">
|
||||
<div class="win-ctrl">
|
||||
<a class="win-btn" href="dossier:back" title="Zurueck">‹</a>
|
||||
<a class="win-btn" href="dossier:close" title="Schliessen">×</a>
|
||||
</div>
|
||||
<div class="brand-row">
|
||||
<div class="brand">DOSSIER<span class="brand-dot">.</span> Shortcuts</div>
|
||||
<div class="version">v {ver}</div>
|
||||
</div>
|
||||
{sections}
|
||||
</div></body></html>"""
|
||||
|
||||
|
||||
def _build_cheatsheet_html():
|
||||
items = _load_shortcuts()
|
||||
groups = {
|
||||
"DOSSIER BIM": [],
|
||||
"2D-Werkzeuge": [],
|
||||
"Views & Navigation": [],
|
||||
"Modify-Tools": [],
|
||||
"Sonstige Aliases": [],
|
||||
}
|
||||
bim_ids = {"wand", "tuer", "fenster", "decke", "treppe", "stuetze",
|
||||
"traeger", "raum", "symbol", "stempel", "dach", "aussparung"}
|
||||
view_ids = {"view_plan", "view_3d", "view_material", "zoom_ext",
|
||||
"zoom_sel", "geschoss_up", "geschoss_down",
|
||||
"panel_layer", "panel_elemente"}
|
||||
twod_ids = {"text", "line", "arc", "rectangle", "polyline", "curve",
|
||||
"hatch", "polygon", "ellipse", "circle"}
|
||||
for it in items:
|
||||
i = it["id"]
|
||||
if i in bim_ids: groups["DOSSIER BIM"].append(it)
|
||||
elif i in view_ids: groups["Views & Navigation"].append(it)
|
||||
elif i.startswith("mod_"): groups["Modify-Tools"].append(it)
|
||||
elif i in twod_ids or i.endswith("_alias"): groups["2D-Werkzeuge"].append(it)
|
||||
else: groups["Sonstige Aliases"].append(it)
|
||||
|
||||
def _row(it):
|
||||
trig = it["trigger"]
|
||||
trig = trig.replace("Cmd+", "⌘+").replace("Shift+", "⇧+").replace("Alt+", "⌥+")
|
||||
return ('<tr><td><kbd>{}</kbd></td>'
|
||||
'<td class="lab">{}</td></tr>'
|
||||
.format(trig, it["label"]))
|
||||
|
||||
sections = []
|
||||
for gname, gitems in groups.items():
|
||||
if not gitems: continue
|
||||
rows = "".join(_row(it) for it in gitems)
|
||||
sections.append('<h2>{}</h2><table>{}</table>'.format(gname, rows))
|
||||
return _CHEATSHEET_HTML.format(ver=DOSSIER_VERSION, sections="".join(sections))
|
||||
|
||||
|
||||
# ---- Dialog-Anzeige ------------------------------------------------------
|
||||
|
||||
def _try_borderless_mac(form):
|
||||
"""Borderless NSWindow + transparenten Hintergrund (analog _startup_splash)."""
|
||||
try:
|
||||
import System
|
||||
nsw = getattr(form, "ControlObject", None)
|
||||
if nsw is None: return False
|
||||
# StyleMask = 0 (Borderless)
|
||||
try:
|
||||
cur = nsw.StyleMask
|
||||
nsw.StyleMask = System.Enum.ToObject(type(cur), 0)
|
||||
except Exception as ex:
|
||||
print("[WELCOME] StyleMask:", ex)
|
||||
# Transparent background damit border-radius vom HTML sichtbar
|
||||
for prop, val in [("TitlebarAppearsTransparent", True),
|
||||
("IsOpaque", False), ("HasShadow", True),
|
||||
("MovableByWindowBackground", True)]:
|
||||
try: setattr(nsw, prop, val)
|
||||
except Exception: pass
|
||||
try:
|
||||
tv_type = type(nsw.TitleVisibility)
|
||||
nsw.TitleVisibility = System.Enum.ToObject(tv_type, 1)
|
||||
except Exception: pass
|
||||
try:
|
||||
from AppKit import NSColor as _NSC
|
||||
clear = getattr(_NSC, "Clear", None) or getattr(_NSC, "ClearColor", None)
|
||||
if clear is not None: nsw.BackgroundColor = clear
|
||||
except Exception: pass
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[WELCOME] borderless:", ex)
|
||||
return False
|
||||
|
||||
|
||||
def _webview_transparent(web):
|
||||
"""WKWebView vollstaendig transparent — KVC drawsBackground=NO,
|
||||
UnderPageBackgroundColor=Clear, Layer.BackgroundColor=CGColor.Clear."""
|
||||
wk = getattr(web, "ControlObject", None)
|
||||
if wk is None: return
|
||||
try:
|
||||
from Foundation import NSNumber, NSString
|
||||
try: wk.SetValueForKey(NSNumber.FromBoolean(False), NSString("drawsBackground"))
|
||||
except Exception as ex: print("[WELCOME] KVC drawsBackground:", ex)
|
||||
except Exception as ex: print("[WELCOME] Foundation:", ex)
|
||||
try:
|
||||
from AppKit import NSColor as _NSC
|
||||
clear = getattr(_NSC, "Clear", None) or getattr(_NSC, "ClearColor", None)
|
||||
if clear is not None:
|
||||
try: wk.UnderPageBackgroundColor = clear
|
||||
except Exception: pass
|
||||
try:
|
||||
layer = getattr(wk, "Layer", None)
|
||||
if layer is not None:
|
||||
layer.BackgroundColor = clear.CGColor
|
||||
layer.Opaque = False
|
||||
except Exception as ex: print("[WELCOME] Layer:", ex)
|
||||
except Exception as ex: print("[WELCOME] NSColor:", ex)
|
||||
|
||||
|
||||
def _show_html_form(title, html, width=620, height=720, on_navigating=None,
|
||||
borderless=True):
|
||||
"""Eto.Forms.Form mit WebView + Inline-HTML. Optional borderless +
|
||||
Navigation-Hook fuer custom URL-Schemes."""
|
||||
try:
|
||||
import Eto.Forms as ef
|
||||
import Eto.Drawing as ed
|
||||
except Exception as ex:
|
||||
print("[WELCOME] Eto.Forms nicht verfuegbar:", ex)
|
||||
return None
|
||||
|
||||
try:
|
||||
form = ef.Form()
|
||||
form.Title = title
|
||||
form.ClientSize = ed.Size(width, height)
|
||||
form.Topmost = False
|
||||
form.Resizable = False
|
||||
if borderless:
|
||||
try: form.WindowStyle = getattr(ef.WindowStyle, "None")
|
||||
except Exception: pass
|
||||
for attr, val in (("Minimizable", False), ("Maximizable", False),
|
||||
("Closeable", False), ("ShowInTaskbar", False)):
|
||||
try: setattr(form, attr, val)
|
||||
except Exception: pass
|
||||
try: form.BackgroundColor = ed.Colors.Transparent
|
||||
except Exception: pass
|
||||
web = ef.WebView()
|
||||
web.Size = ed.Size(width, height)
|
||||
if borderless:
|
||||
try: web.BackgroundColor = ed.Colors.Transparent
|
||||
except Exception: pass
|
||||
if on_navigating is not None:
|
||||
try: web.DocumentLoading += on_navigating
|
||||
except Exception as ex: print("[WELCOME] nav-hook:", ex)
|
||||
try: web.LoadHtml(html)
|
||||
except Exception as e: print("[WELCOME] LoadHtml:", e)
|
||||
form.Content = web
|
||||
try: form.Owner = Rhino.UI.RhinoEtoApp.MainWindow
|
||||
except Exception: pass
|
||||
form.Show()
|
||||
if borderless:
|
||||
_try_borderless_mac(form)
|
||||
_webview_transparent(web)
|
||||
try: ef.Application.Instance.RunIteration()
|
||||
except Exception: pass
|
||||
return form
|
||||
except Exception as ex:
|
||||
print("[WELCOME] form show:", ex)
|
||||
return None
|
||||
|
||||
|
||||
def show_welcome(force=False):
|
||||
"""Zeigt Welcome NACH Splash. Erscheint bei jedem Start ausser der
|
||||
User klickt 'Nicht mehr anzeigen' (= optout-File).
|
||||
WICHTIG: UI muss auf Main-Thread laufen (Mac Cocoa) — Rhino-Idle-Event
|
||||
feuert dort, deshalb defern wir die Anzeige."""
|
||||
if not force and _has_optout():
|
||||
print("[WELCOME] optout aktiv ({}) — skip".format(_WELCOME_OPTOUT))
|
||||
return
|
||||
print("[WELCOME] geplant — Anzeige nach Splash (>{:.1f}s)".format(_SPLASH_MIN_DELAY_SEC))
|
||||
|
||||
import time
|
||||
state = {"start": time.time(), "fired": False}
|
||||
def _on_idle(sender, e):
|
||||
if state["fired"]: return
|
||||
if time.time() - state["start"] < _SPLASH_MIN_DELAY_SEC: return
|
||||
state["fired"] = True
|
||||
try:
|
||||
Rhino.RhinoApp.Idle -= _on_idle
|
||||
except Exception: pass
|
||||
try:
|
||||
print("[WELCOME] Anzeige starten")
|
||||
_show_welcome_now()
|
||||
except Exception as ex:
|
||||
print("[WELCOME] show err:", ex)
|
||||
try:
|
||||
Rhino.RhinoApp.Idle += _on_idle
|
||||
except Exception as ex:
|
||||
print("[WELCOME] idle-hook err:", ex)
|
||||
|
||||
|
||||
def _show_welcome_now():
|
||||
html = _WELCOME_HTML.format(
|
||||
ver=DOSSIER_VERSION, github=DOSSIER_GITHUB, email=DOSSIER_SUPPORT_EMAIL)
|
||||
form_ref = [None]
|
||||
def _on_nav(sender, e):
|
||||
try:
|
||||
url = e.Uri.ToString() if hasattr(e, "Uri") else str(getattr(e, "Url", ""))
|
||||
except Exception:
|
||||
url = ""
|
||||
if not url: return
|
||||
if url.startswith("dossier:optout"):
|
||||
# Optout-Checkbox-Klick. URL-Form: dossier:optout?true/false
|
||||
checked = url.endswith("true")
|
||||
if checked: _write_optout()
|
||||
else:
|
||||
try:
|
||||
if os.path.exists(_WELCOME_OPTOUT):
|
||||
os.remove(_WELCOME_OPTOUT)
|
||||
except Exception: pass
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
elif url.startswith("dossier:cheatsheet"):
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
show_cheatsheet()
|
||||
try:
|
||||
if form_ref[0] is not None: form_ref[0].Close()
|
||||
except Exception: pass
|
||||
elif url.startswith("dossier:close"):
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
try:
|
||||
if form_ref[0] is not None: form_ref[0].Close()
|
||||
except Exception: pass
|
||||
form_ref[0] = _show_html_form("Willkommen bei DOSSIER", html, 600, 620,
|
||||
on_navigating=_on_nav)
|
||||
|
||||
|
||||
def show_cheatsheet():
|
||||
html = _build_cheatsheet_html()
|
||||
form_ref = [None]
|
||||
def _on_nav(sender, e):
|
||||
try:
|
||||
url = e.Uri.ToString() if hasattr(e, "Uri") else str(getattr(e, "Url", ""))
|
||||
except Exception:
|
||||
url = ""
|
||||
if not url: return
|
||||
if url.startswith("dossier:close"):
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
try:
|
||||
if form_ref[0] is not None: form_ref[0].Close()
|
||||
except Exception: pass
|
||||
elif url.startswith("dossier:back"):
|
||||
try: e.Cancel = True
|
||||
except Exception: pass
|
||||
try:
|
||||
if form_ref[0] is not None: form_ref[0].Close()
|
||||
except Exception: pass
|
||||
try: _show_welcome_now()
|
||||
except Exception as ex: print("[WELCOME] back:", ex)
|
||||
form_ref[0] = _show_html_form("DOSSIER Shortcuts", html, 640, 760,
|
||||
on_navigating=_on_nav)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
show_cheatsheet()
|
||||
Reference in New Issue
Block a user