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,469 @@
|
||||
#! python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (C) 2026 Karim Gabriele Varano
|
||||
"""
|
||||
aliases/loader.py
|
||||
Liest shortcuts_default.json + User-Overrides aus dossier_settings.json,
|
||||
merged und wendet via Rhino.ApplicationSettings.CommandAliasList /
|
||||
ShortcutKeySettings an. Wird einmal beim Rhino-Start aus startup.py
|
||||
aufgerufen (idempotent — SetMacro ueberschreibt).
|
||||
|
||||
User-Override-Format in dossier_settings.json:
|
||||
"shortcuts_user": {
|
||||
"<action_id>": "<trigger_string>" // leer = Default
|
||||
}
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import Rhino
|
||||
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_quit_xml_pairs = [] # gefuellt in apply_all(), genutzt vom Closing-Hook
|
||||
_DEFAULTS_PATH = os.path.join(_HERE, "shortcuts_default.json")
|
||||
_SETTINGS_PATHS = [
|
||||
os.path.expanduser("~/Library/Application Support/ch.gabrielevarano.Dossier/dossier_settings.json"),
|
||||
os.path.expanduser("~/Library/Application Support/RhinoPanel/dossier_settings.json"), # legacy
|
||||
]
|
||||
|
||||
|
||||
def _read_defaults():
|
||||
try:
|
||||
with open(_DEFAULTS_PATH, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
out = {}
|
||||
for k, v in data.items():
|
||||
if k.startswith("_"): continue
|
||||
if not isinstance(v, dict): continue
|
||||
out[k] = v
|
||||
return out
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Defaults lesen:", ex)
|
||||
return {}
|
||||
|
||||
|
||||
def _read_user_overrides():
|
||||
"""Liest 'shortcuts_user' aus dossier_settings.json. Format:
|
||||
{ action_id: trigger_string }. Leerer String / None = Default."""
|
||||
for path in _SETTINGS_PATHS:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
so = data.get("shortcuts_user")
|
||||
if isinstance(so, dict): return so
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Settings lesen:", ex)
|
||||
return {}
|
||||
|
||||
|
||||
def _expand_macro(macro):
|
||||
"""Platzhalter {ALIASDIR} → absoluter Pfad zum aliases/-Ordner."""
|
||||
return macro.replace("{ALIASDIR}", _HERE)
|
||||
|
||||
|
||||
# Sonderzeichen → Rhino-Enum-Namen (Mac XML + ShortcutKey-API)
|
||||
_SPECIAL_KEY_NAMES = {
|
||||
"-": "Minus", "+": "Plus", "=": "Equals",
|
||||
"/": "Slash", "\\": "Backslash",
|
||||
".": "Period", ",": "Comma",
|
||||
";": "Semicolon", "'": "Quote", "`": "Backquote",
|
||||
"[": "OpenBracket", "]": "CloseBracket",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_key_part(key_part):
|
||||
"""Mapped Sonderzeichen wie '-' auf Enum-Namen ('Minus'). Buchstaben/F-Keys
|
||||
bleiben unveraendert (Case-preserved)."""
|
||||
if key_part in _SPECIAL_KEY_NAMES:
|
||||
return _SPECIAL_KEY_NAMES[key_part]
|
||||
return key_part
|
||||
|
||||
|
||||
def _xml_key_from_trigger(trigger):
|
||||
"""'Cmd+Shift+F3' → 'CommandShiftF3' (Mac Rhino XML-Schema).
|
||||
Cmd/Ctrl → 'Command', Shift → 'Shift', Alt/Option → 'Option'.
|
||||
Sonderzeichen ('-', '/', etc.) werden auf Enum-Namen gemapped."""
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+") if "+" in t[1:] else [t]
|
||||
# Edge-Case: trigger endet auf literal '+' oder '-' → letztes Element ist Key
|
||||
# 'Cmd+-' → ['Cmd', '', '-'] via split. Fix: re-split last token wenn leer
|
||||
parts = [p for p in parts if p != ""]
|
||||
# Sonderfall trigger == 'Cmd+-' → split('+') = ['Cmd', '-'], OK
|
||||
# Sonderfall trigger == 'Cmd++' → split('+') = ['Cmd', '', ''] → key = '+'
|
||||
if "Cmd++" in trigger or "Ctrl++" in trigger or "Shift++" in trigger:
|
||||
parts = trigger.replace(" ", "").rstrip("+").split("+") + ["+"]
|
||||
if not parts: return None
|
||||
key_part = _normalize_key_part(parts[-1])
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_cmd = ("cmd" in mods) or ("ctrl" in mods) or ("command" in mods)
|
||||
has_shift = "shift" in mods
|
||||
has_alt = ("alt" in mods) or ("option" in mods) or ("opt" in mods)
|
||||
prefix = ""
|
||||
if has_cmd: prefix += "Command"
|
||||
if has_shift: prefix += "Shift"
|
||||
if has_alt: prefix += "Option"
|
||||
return prefix + key_part
|
||||
|
||||
|
||||
def _entry_in_xml(xml_key, expected_macro):
|
||||
"""True wenn <entry key='<xml_key>'>expected_macro</entry> bereits im
|
||||
Mac Rhino settings-XML existiert."""
|
||||
import os
|
||||
import re
|
||||
paths = [
|
||||
os.path.expanduser("~/Library/Application Support/McNeel/Rhinoceros/8.0/settings/settings-Scheme__Default.xml"),
|
||||
]
|
||||
_esc = lambda s: s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
pat = re.compile(
|
||||
r'<entry\s+key="' + re.escape(xml_key) + r'"\s*>([^<]*)</entry>')
|
||||
for path in paths:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
m = pat.search(content)
|
||||
if m and m.group(1) == _esc(expected_macro):
|
||||
return True
|
||||
except Exception: pass
|
||||
return False
|
||||
|
||||
|
||||
def _xml_persist_shortcut(xml_key, macro, verbose=False):
|
||||
"""Schreibt <entry key="<xml_key>"><macro></entry> direkt in Mac Rhino's
|
||||
settings-Scheme__Default.xml unter <child key='ShortcutKeys'>. String-
|
||||
basiert damit die Original-Formatierung 1:1 erhalten bleibt."""
|
||||
import os
|
||||
import re
|
||||
paths = [
|
||||
os.path.expanduser("~/Library/Application Support/McNeel/Rhinoceros/8.0/settings/settings-Scheme__Default.xml"),
|
||||
]
|
||||
n_written = 0
|
||||
_esc = lambda s: s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
for path in paths:
|
||||
if not os.path.exists(path): continue
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
new_entry = '<entry key="{}">{}</entry>'.format(xml_key, _esc(macro))
|
||||
|
||||
# Existing entry? Loeschen (mit umgebendem Whitespace+Newline)
|
||||
# und neu hinzufuegen mit sauberem Format. Vermeidet
|
||||
# kaputt-formatierte Entries.
|
||||
pat = re.compile(
|
||||
r'<entry\s+key="' + re.escape(xml_key) + r'"\s*(/>|>[^<]*</entry>)')
|
||||
m = pat.search(content)
|
||||
if m:
|
||||
# Check Line-Kontext: nur diese Entry auf Zeile + unveraendert?
|
||||
line_start = content.rfind("\n", 0, m.start()) + 1
|
||||
line_end = content.find("\n", m.end())
|
||||
if line_end < 0: line_end = len(content)
|
||||
line_trim = content[line_start:line_end].strip()
|
||||
if line_trim == new_entry:
|
||||
if verbose: print("[ALIAS-LOADER] XML '{}' unchanged".format(xml_key))
|
||||
continue
|
||||
# Sonst: loeschen inkl. preceding-newline+whitespace damit
|
||||
# keine orphan-line uebrig bleibt
|
||||
del_start = m.start()
|
||||
while del_start > 0 and content[del_start-1] in " \t":
|
||||
del_start -= 1
|
||||
if del_start > 0 and content[del_start-1] == "\n":
|
||||
del_start -= 1
|
||||
content = content[:del_start] + content[m.end():]
|
||||
if True:
|
||||
# ShortcutKeys-Section finden
|
||||
sec_start = content.find('<child key="ShortcutKeys">')
|
||||
if sec_start < 0:
|
||||
if verbose: print("[ALIAS-LOADER] ShortcutKeys-section fehlt")
|
||||
continue
|
||||
sec_end = content.find('</child>', sec_start)
|
||||
if sec_end < 0:
|
||||
if verbose: print("[ALIAS-LOADER] ShortcutKeys-close fehlt")
|
||||
continue
|
||||
# Indent vom letzten <entry> in der Section uebernehmen
|
||||
section = content[sec_start:sec_end]
|
||||
ms = list(re.finditer(r'\n([ \t]*)<entry\s', section))
|
||||
entry_indent = ms[-1].group(1) if ms else " "
|
||||
# Indent vor </child> (typisch 6 spaces)
|
||||
close_match = re.search(r'\n([ \t]*)$', content[:sec_end])
|
||||
close_indent = close_match.group(1) if close_match else " "
|
||||
# Section neu zusammensetzen: alles vor </child> bereinigt
|
||||
# + sauberer Insert
|
||||
before = content[:sec_end].rstrip(" \t") + "\n"
|
||||
content = (before + entry_indent + new_entry + "\n"
|
||||
+ close_indent + content[sec_end:])
|
||||
action = "added"
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
n_written += 1
|
||||
if verbose: print("[ALIAS-LOADER] XML {} '{}'".format(action, xml_key))
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] XML-Write {}: {}".format(path, ex))
|
||||
return n_written
|
||||
|
||||
|
||||
def _install_quit_xml_save(pairs):
|
||||
"""Rhino's Closing-Event fired auf Mac NICHT zuverlaessig. Wir
|
||||
installieren MEHRERE Hooks parallel:
|
||||
1. Rhino.RhinoApp.Closing (Mac: meist No-op, Windows: ok)
|
||||
2. Python atexit (laeuft wenn Interpreter terminiert)
|
||||
3. AppDomain.ProcessExit (.NET-Level Hook)
|
||||
4. Idle-Watcher: schreibt XML alle 30s wenn Aenderung erkannt
|
||||
(Fallback fuer Rhino's runtime-flush)
|
||||
Marker-Logging zur Verifikation welcher Hook wirklich feuert."""
|
||||
import os as _os
|
||||
import datetime as _dt
|
||||
_marker = _os.path.expanduser("~/Library/Logs/dossier_quit_hook.log")
|
||||
try:
|
||||
_os.makedirs(_os.path.dirname(_marker), exist_ok=True)
|
||||
except Exception: pass
|
||||
|
||||
def _log(msg):
|
||||
try:
|
||||
with open(_marker, "a") as f:
|
||||
f.write("[{}] {}\n".format(_dt.datetime.now().isoformat(), msg))
|
||||
except Exception: pass
|
||||
|
||||
def _write_all(source):
|
||||
n_ok = 0
|
||||
for xml_key, macro in pairs:
|
||||
if _xml_persist_shortcut(xml_key, macro, verbose=False) > 0:
|
||||
n_ok += 1
|
||||
_log("{} FIRED — {}/{} ok".format(source, n_ok, len(pairs)))
|
||||
return n_ok
|
||||
|
||||
n_hooks = 0
|
||||
try:
|
||||
import Rhino
|
||||
def _on_closing(*_):
|
||||
try: _write_all("RhinoClosing")
|
||||
except Exception as ex: _log("RhinoClosing ERROR: {}".format(ex))
|
||||
Rhino.RhinoApp.Closing += _on_closing
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("RhinoClosing install err: {}".format(ex))
|
||||
|
||||
try:
|
||||
import atexit
|
||||
def _on_atexit():
|
||||
try: _write_all("atexit")
|
||||
except Exception as ex: _log("atexit ERROR: {}".format(ex))
|
||||
atexit.register(_on_atexit)
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("atexit install err: {}".format(ex))
|
||||
|
||||
try:
|
||||
import System
|
||||
def _on_process_exit(*_):
|
||||
try: _write_all("ProcessExit")
|
||||
except Exception as ex: _log("ProcessExit ERROR: {}".format(ex))
|
||||
System.AppDomain.CurrentDomain.ProcessExit += _on_process_exit
|
||||
n_hooks += 1
|
||||
except Exception as ex:
|
||||
_log("ProcessExit install err: {}".format(ex))
|
||||
|
||||
# Idle-Watcher: periodisch (alle ~30s) checken ob unsere XML-Entries
|
||||
# noch da sind. Wenn nein → wieder reinschreiben. Ueberlebt Rhino-
|
||||
# Runtime-Flushes auch ohne Close-Event.
|
||||
try:
|
||||
import Rhino
|
||||
import time as _time
|
||||
_state = {"last": 0.0}
|
||||
def _idle_watcher(*_):
|
||||
try:
|
||||
now = _time.time()
|
||||
if now - _state["last"] < 30.0: return
|
||||
_state["last"] = now
|
||||
# Pruefen ob entries fehlen — wenn ja, alle re-schreiben
|
||||
_write_all("IdleWatch")
|
||||
except Exception as ex:
|
||||
_log("IdleWatch ERROR: {}".format(ex))
|
||||
Rhino.RhinoApp.Idle += _idle_watcher
|
||||
n_hooks += 1
|
||||
_log("IdleWatch installed (30s interval)")
|
||||
except Exception as ex:
|
||||
_log("IdleWatch install err: {}".format(ex))
|
||||
|
||||
_log("Hooks INSTALLED ({} of 4) for {} shortcuts".format(n_hooks, len(pairs)))
|
||||
# Initiale Schreibung im ersten Pass auch — falls Rhino sofort flusht
|
||||
_write_all("InitialWrite")
|
||||
return n_hooks > 0
|
||||
|
||||
|
||||
def _resolve_fkey(trigger):
|
||||
"""'F3' / 'Shift+F3' / 'Cmd+F3' / 'Cmd+Alt+F3' → ShortcutKey-Enum-Wert.
|
||||
Enum-Naming-Konvention von Rhino: Ctrl → Shift → Alt → KeyName
|
||||
(z.B. CtrlAltF3, CtrlShiftAltF3). Cmd auf Mac mappt auf Ctrl,
|
||||
Option/Opt auf Alt. Sonderzeichen via _SPECIAL_KEY_NAMES."""
|
||||
SK = Rhino.ApplicationSettings.ShortcutKey
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+")
|
||||
parts = [p for p in parts if p != ""]
|
||||
if not parts: return None
|
||||
raw_last = parts[-1]
|
||||
if raw_last in _SPECIAL_KEY_NAMES:
|
||||
key_part = _SPECIAL_KEY_NAMES[raw_last]
|
||||
else:
|
||||
key_part = raw_last.upper()
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_ctrl = ("ctrl" in mods) or ("cmd" in mods) or ("command" in mods)
|
||||
has_shift = "shift" in mods
|
||||
has_alt = ("alt" in mods) or ("option" in mods) or ("opt" in mods)
|
||||
prefix = ""
|
||||
if has_ctrl: prefix += "Ctrl"
|
||||
if has_shift: prefix += "Shift"
|
||||
if has_alt: prefix += "Alt"
|
||||
return getattr(SK, prefix + key_part, None)
|
||||
|
||||
|
||||
def _resolve_cmd_letter(trigger):
|
||||
"""'Cmd+W' / 'Cmd+Shift+W' → ShortcutKey-Enum (Ctrl* auf Rhino-Naming-
|
||||
Konvention; Mac mappt Ctrl auf Cmd intern)."""
|
||||
SK = Rhino.ApplicationSettings.ShortcutKey
|
||||
t = trigger.replace(" ", "")
|
||||
parts = t.split("+")
|
||||
if len(parts) < 2: return None
|
||||
letter = parts[-1].upper()
|
||||
if not (len(letter) == 1 and letter.isalpha()): return None
|
||||
mods = set(p.lower() for p in parts[:-1])
|
||||
has_cmd = ("cmd" in mods) or ("ctrl" in mods)
|
||||
if not has_cmd: return None
|
||||
name = "Ctrl"
|
||||
if "shift" in mods: name += "Shift"
|
||||
if "alt" in mods: name += "Alt"
|
||||
name += letter
|
||||
return getattr(SK, name, None)
|
||||
|
||||
|
||||
def apply_all():
|
||||
"""Liest Defaults + Overrides, wendet alle Aliases + Shortcuts an.
|
||||
Returnt (n_alias, n_fkey, n_cmd, n_skipped)."""
|
||||
global _quit_xml_pairs
|
||||
_quit_xml_pairs = []
|
||||
defaults = _read_defaults()
|
||||
overrides = _read_user_overrides()
|
||||
aliases = Rhino.ApplicationSettings.CommandAliasList
|
||||
skset = Rhino.ApplicationSettings.ShortcutKeySettings
|
||||
n_alias = n_fkey = n_cmd = n_skipped = 0
|
||||
seen_triggers = {} # trigger_normalized -> action_id (Konflikt-Erkennung)
|
||||
|
||||
for action_id, spec in defaults.items():
|
||||
# User-Override hat Vorrang. Leerer String = Default, None/missing = Default.
|
||||
user_trig = overrides.get(action_id)
|
||||
if user_trig is not None and str(user_trig).strip() == "":
|
||||
user_trig = None
|
||||
trigger = user_trig if user_trig else spec.get("trigger", "")
|
||||
if not trigger:
|
||||
n_skipped += 1
|
||||
continue
|
||||
spec_type = spec.get("type", "alias")
|
||||
macro = _expand_macro(spec.get("macro", ""))
|
||||
if not macro:
|
||||
n_skipped += 1; continue
|
||||
|
||||
# Konflikt-Check (gleicher Trigger → letzter gewinnt, Warning)
|
||||
norm = (spec_type, str(trigger).lower())
|
||||
if norm in seen_triggers:
|
||||
print("[ALIAS-LOADER] Konflikt: '{}' fuer {} bereits von {} belegt"
|
||||
.format(trigger, action_id, seen_triggers[norm]))
|
||||
seen_triggers[norm] = action_id
|
||||
|
||||
try:
|
||||
if spec_type == "alias":
|
||||
tname = str(trigger)
|
||||
try:
|
||||
if aliases.IsAlias(tname):
|
||||
aliases.Delete(tname)
|
||||
except Exception: pass
|
||||
added = False
|
||||
try:
|
||||
added = aliases.Add(tname, macro)
|
||||
except Exception as _addex:
|
||||
print("[ALIAS-LOADER] Add({}, ...) Exception: {}"
|
||||
.format(tname, _addex))
|
||||
if not added:
|
||||
try: aliases.SetMacro(tname, macro)
|
||||
except Exception: pass
|
||||
# Verifizieren ob Alias wirklich registriert ist
|
||||
try:
|
||||
is_ok = aliases.IsAlias(tname)
|
||||
if not is_ok:
|
||||
print("[ALIAS-LOADER] WARN: '{}' (action={}) NICHT registriert "
|
||||
"— Rhino lehnt Namen wahrscheinlich ab (z.B. reine Zahl)"
|
||||
.format(tname, action_id))
|
||||
n_skipped += 1
|
||||
continue
|
||||
except Exception: pass
|
||||
n_alias += 1
|
||||
elif spec_type == "fkey":
|
||||
sk = _resolve_fkey(str(trigger))
|
||||
xml_key = _xml_key_from_trigger(str(trigger))
|
||||
api_ok = False
|
||||
if sk is not None:
|
||||
try:
|
||||
skset.SetMacro(sk, macro)
|
||||
got = skset.GetMacro(sk)
|
||||
api_ok = (got == macro)
|
||||
except Exception as _sex:
|
||||
print("[ALIAS-LOADER] SetMacro({}): {}".format(trigger, _sex))
|
||||
if not api_ok and xml_key:
|
||||
# Enum-Wert fehlt → direkt ins XML (mit verbose-Log).
|
||||
# n_xml=0 kann "schon korrekt" ODER "gescheitert" heissen
|
||||
# — wir checken explizit ob Entry im XML existiert.
|
||||
n_xml = _xml_persist_shortcut(xml_key, macro, verbose=True)
|
||||
if n_xml > 0:
|
||||
_quit_xml_pairs.append((xml_key, macro))
|
||||
else:
|
||||
# n_xml == 0 → entweder "unchanged" (= schon korrekt
|
||||
# im XML) oder "missing path/section". Check via
|
||||
# IsAliasInXml damit wir nicht falsch warnen.
|
||||
if _entry_in_xml(xml_key, macro):
|
||||
# Schon korrekt im XML → fuer Quit-Hook merken
|
||||
# damit Rhino-Quit-Save sie nicht ueberschreibt
|
||||
_quit_xml_pairs.append((xml_key, macro))
|
||||
else:
|
||||
print("[ALIAS-LOADER] WARN F-Key {} ({}) konnte weder "
|
||||
"API noch XML gesetzt werden".format(trigger, action_id))
|
||||
n_skipped += 1; continue
|
||||
n_fkey += 1
|
||||
elif spec_type == "cmd":
|
||||
sk = _resolve_cmd_letter(str(trigger))
|
||||
if sk is None:
|
||||
# Fallback: Cmd+Letter API u.U. nicht im Enum → als Alias mit dem
|
||||
# Letter (single-char) registrieren. User tippt dann Letter+Enter.
|
||||
letter_only = str(trigger).split("+")[-1].lower()
|
||||
if len(letter_only) == 1 and letter_only.isalpha():
|
||||
aliases.SetMacro(letter_only, macro)
|
||||
n_alias += 1
|
||||
print("[ALIAS-LOADER] {} ({}): Cmd+Letter nicht im Enum, "
|
||||
"fallback Alias '{}'".format(action_id, trigger, letter_only))
|
||||
else:
|
||||
n_skipped += 1
|
||||
continue
|
||||
skset.SetMacro(sk, macro)
|
||||
n_cmd += 1
|
||||
else:
|
||||
print("[ALIAS-LOADER] Unbekannter Type:", spec_type); n_skipped += 1
|
||||
except Exception as ex:
|
||||
print("[ALIAS-LOADER] Apply", action_id, "->", trigger, ":", ex)
|
||||
n_skipped += 1
|
||||
|
||||
# Quit-Hook installieren falls XML-only Shortcuts gesetzt wurden — diese
|
||||
# ueberlebt sonst Rhino's Auto-Save beim Quit nicht.
|
||||
if _quit_xml_pairs:
|
||||
_install_quit_xml_save(list(_quit_xml_pairs))
|
||||
print("[ALIAS-LOADER] {} XML-only Shortcuts werden bei Quit "
|
||||
"re-persistiert (Closing-Hook installiert)"
|
||||
.format(len(_quit_xml_pairs)))
|
||||
|
||||
return n_alias, n_fkey, n_cmd, n_skipped
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a, f, c, s = apply_all()
|
||||
print("[ALIAS-LOADER] OK: {} alias, {} fkey, {} cmd, {} skipped"
|
||||
.format(a, f, c, s))
|
||||
Reference in New Issue
Block a user