Audit-Cleanup: doppelte Brüstungs-Mitnahme entfernt + dead files raus
elemente.py: - Idle-Pfad Brüstungs-Mitnahme entfernt — war Duplikat zur CommandEnd-Logik und konnte je nach Reihenfolge entweder doppelt anwenden oder gar nicht (race condition mit `_elemente_wand_z_delta` Sticky-Reset). - `float(z_delta)` mit try/except für ValueError/TypeError gewrapped — vorher konnte ein korruptes Sticky-Tuple den Idle/CommandEnd-Pass crashen. - `_elemente_replace_selected_ids` wird nach Migrate consumiert (auf None gesetzt). Sonst blieb eine stale Liste hängen und beeinflusste spätere unverwandte Migrations. - Einrückung im CommandEnd-Brüstungs-Block normalisiert. Dead Files: - `rhino/startup.py3` entfernt — veraltetes Backup ohne Marker-Code für den Launcher-Splash. `rhino/startup.py` ist die aktuelle Version. - `rhino/__pycache__` aufgeräumt (war eh in .gitignore). Kein funktionales Verhalten geändert. Audit-Findings HIGH/MEDIUM bereinigt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+30
-51
@@ -7056,7 +7056,11 @@ def _migrate_openings_to_new_axis(wall_id, old_geom, new_geom):
|
|||||||
|
|
||||||
# Selected-Snapshot vom Replace-Handler — nicht live IsSelected, weil
|
# Selected-Snapshot vom Replace-Handler — nicht live IsSelected, weil
|
||||||
# op_obj im laufenden Move-Event evtl. schon stale ist.
|
# op_obj im laufenden Move-Event evtl. schon stale ist.
|
||||||
|
# Snapshot der vom User selektierten IDs vom Replace-Handler ziehen UND
|
||||||
|
# gleich consumen — sonst bleibt eine stale Liste im sticky und wirkt sich
|
||||||
|
# auf spaetere unverwandte Migrations aus.
|
||||||
skip_ids = sc.sticky.get("_elemente_replace_selected_ids") or set()
|
skip_ids = sc.sticky.get("_elemente_replace_selected_ids") or set()
|
||||||
|
sc.sticky["_elemente_replace_selected_ids"] = None
|
||||||
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
||||||
sc.sticky[_REGEN_BUSY] = True
|
sc.sticky[_REGEN_BUSY] = True
|
||||||
try:
|
try:
|
||||||
@@ -7504,33 +7508,6 @@ def _on_idle_selection(sender, e):
|
|||||||
ids = list(pending)
|
ids = list(pending)
|
||||||
pending.clear()
|
pending.clear()
|
||||||
sc.sticky[_REGEN_BUSY] = True
|
sc.sticky[_REGEN_BUSY] = True
|
||||||
# Wand-Z-Drag-Mitnahme: Bruestungen der Oeffnungen um delta_z
|
|
||||||
# anpassen BEVOR die Wand regen wird. Hier passiert's NACH Rhinos
|
|
||||||
# User-Move-Operation (im Idle), daher kollidiert es nicht mehr.
|
|
||||||
z_delta_entry = sc.sticky.get("_elemente_wand_z_delta")
|
|
||||||
if isinstance(z_delta_entry, tuple) and len(z_delta_entry) == 2:
|
|
||||||
wand_id, z_delta = z_delta_entry
|
|
||||||
sc.sticky["_elemente_wand_z_delta"] = None
|
|
||||||
if abs(float(z_delta)) >= 1e-6:
|
|
||||||
try:
|
|
||||||
for op_obj, op_meta in _find_openings_for_wall(doc, wand_id):
|
|
||||||
cur_b = op_meta.get("oeff_brueest")
|
|
||||||
try:
|
|
||||||
cur_b_val = float(cur_b) if cur_b not in (None, "") else 0.0
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
cur_b_val = 0.0
|
|
||||||
new_b = max(0.0, cur_b_val + float(z_delta))
|
|
||||||
attrs = op_obj.Attributes.Duplicate()
|
|
||||||
attrs.SetUserString(_KEY_OEFF_BRUEST,
|
|
||||||
"{:.6f}".format(new_b))
|
|
||||||
doc.Objects.ModifyAttributes(op_obj.Id, attrs, True)
|
|
||||||
pt_geom = op_obj.Geometry
|
|
||||||
if hasattr(pt_geom, "Location"):
|
|
||||||
pt = pt_geom.Location
|
|
||||||
doc.Objects.Replace(op_obj.Id,
|
|
||||||
rg.Point(rg.Point3d(pt.X, pt.Y, new_b)))
|
|
||||||
except Exception as ex:
|
|
||||||
print("[ELEMENTE] idle z-delta apply:", ex)
|
|
||||||
# Bulk-Performance: ein einziger Undo-Record fuer alle queued
|
# Bulk-Performance: ein einziger Undo-Record fuer alle queued
|
||||||
# Regens + Redraw nur am Ende (statt einem pro AddBrep/Delete).
|
# Regens + Redraw nur am Ende (statt einem pro AddBrep/Delete).
|
||||||
undo_serial = doc.BeginUndoRecord(
|
undo_serial = doc.BeginUndoRecord(
|
||||||
@@ -7804,35 +7781,37 @@ def _on_command_end(sender, e):
|
|||||||
_migrate_openings_to_new_axis(m["id"], old_line, geom)
|
_migrate_openings_to_new_axis(m["id"], old_line, geom)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] post-cmd migrate:", ex)
|
print("[ELEMENTE] post-cmd migrate:", ex)
|
||||||
# Z-Drag detect + Brüstungs-Mitnahme (= setzt sticky-delta
|
# Z-Drag detect + Brüstungs-Mitnahme. Constraint setzt
|
||||||
# den der Idle-Pfad spaeter applied — aber wir koennen
|
# sticky-delta wenn Z geaendert; wir consumen es direkt.
|
||||||
# gleich hier syncen)
|
|
||||||
_apply_wand_z_drag_constraint(obj, m)
|
_apply_wand_z_drag_constraint(obj, m)
|
||||||
z_entry = sc.sticky.get("_elemente_wand_z_delta")
|
z_entry = sc.sticky.get("_elemente_wand_z_delta")
|
||||||
|
z_delta = 0.0
|
||||||
if isinstance(z_entry, tuple) and len(z_entry) == 2 \
|
if isinstance(z_entry, tuple) and len(z_entry) == 2 \
|
||||||
and z_entry[0] == m["id"]:
|
and z_entry[0] == m["id"]:
|
||||||
z_delta = float(z_entry[1])
|
try: z_delta = float(z_entry[1])
|
||||||
|
except (ValueError, TypeError): z_delta = 0.0
|
||||||
sc.sticky["_elemente_wand_z_delta"] = None
|
sc.sticky["_elemente_wand_z_delta"] = None
|
||||||
# Brüstungen aller Öffnungen mit anpassen
|
if abs(z_delta) >= 1e-6:
|
||||||
if abs(z_delta) >= 1e-6:
|
# Brüstungen aller Öffnungen der Wand um delta mitnehmen
|
||||||
for op_obj, op_meta in _find_openings_for_wall(doc, m["id"]):
|
for op_obj, op_meta in _find_openings_for_wall(doc, m["id"]):
|
||||||
cur_b = op_meta.get("oeff_brueest")
|
cur_b = op_meta.get("oeff_brueest")
|
||||||
try:
|
try:
|
||||||
cur_b_val = float(cur_b) if cur_b not in (None, "") else 0.0
|
cur_b_val = float(cur_b) if cur_b not in (None, "") else 0.0
|
||||||
except (ValueError, TypeError): cur_b_val = 0.0
|
except (ValueError, TypeError):
|
||||||
new_b = max(0.0, cur_b_val + z_delta)
|
cur_b_val = 0.0
|
||||||
try:
|
new_b = max(0.0, cur_b_val + z_delta)
|
||||||
attrs = op_obj.Attributes.Duplicate()
|
try:
|
||||||
attrs.SetUserString(_KEY_OEFF_BRUEST,
|
attrs = op_obj.Attributes.Duplicate()
|
||||||
"{:.6f}".format(new_b))
|
attrs.SetUserString(_KEY_OEFF_BRUEST,
|
||||||
doc.Objects.ModifyAttributes(op_obj.Id, attrs, True)
|
"{:.6f}".format(new_b))
|
||||||
pt_geom = op_obj.Geometry
|
doc.Objects.ModifyAttributes(op_obj.Id, attrs, True)
|
||||||
if hasattr(pt_geom, "Location"):
|
pt_geom = op_obj.Geometry
|
||||||
pt = pt_geom.Location
|
if hasattr(pt_geom, "Location"):
|
||||||
doc.Objects.Replace(op_obj.Id,
|
pt = pt_geom.Location
|
||||||
rg.Point(rg.Point3d(pt.X, pt.Y, new_b)))
|
doc.Objects.Replace(op_obj.Id,
|
||||||
except Exception as ex:
|
rg.Point(rg.Point3d(pt.X, pt.Y, new_b)))
|
||||||
print("[ELEMENTE] post-cmd brueest:", ex)
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] post-cmd brueest:", ex)
|
||||||
affected_walls.add(m["id"])
|
affected_walls.add(m["id"])
|
||||||
elif t == "oeffnung_point":
|
elif t == "oeffnung_point":
|
||||||
op_pos = old.get("pos")
|
op_pos = old.get("pos")
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
# ! python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
startup.py
|
|
||||||
Laedt DOSSIER-Panels beim Rhino-Start. Liest pro geoeffnetem Dokument eine
|
|
||||||
`dossier.project.json` (neben der `.3dm` abgelegt vom Dossier-Launcher) und
|
|
||||||
aktiviert nur die dort gelisteten Module. Fehlt die Datei → alle bekannten
|
|
||||||
Module laden (Backwards-Compat fuer Setups ohne Launcher).
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import Rhino
|
|
||||||
import scriptcontext as sc
|
|
||||||
|
|
||||||
# DIAGNOSE — welcher Python-Engine laeuft hier wirklich? Einmalig beim Start.
|
|
||||||
print("[STARTUP] Python: {}".format(sys.version))
|
|
||||||
print("[STARTUP] Implementation: {}".format(
|
|
||||||
sys.implementation.name if hasattr(sys, "implementation") else "n/a (IPy2)"))
|
|
||||||
print("[STARTUP] Platform: {}".format(sys.platform))
|
|
||||||
|
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
if _HERE not in sys.path:
|
|
||||||
sys.path.insert(0, _HERE)
|
|
||||||
|
|
||||||
# Pfad zur Custom-UI (Toolbars/Sidebar) — wird einmal pro Session geladen
|
|
||||||
_UI_FILE = os.path.join(_HERE, "DOSSIERUI.rhw")
|
|
||||||
|
|
||||||
# Map: Modul-ID (aus dossier.project.json) -> Python-Modulname (Datei in rhino/).
|
|
||||||
# Muss synchron sein mit launcher/modules.json. Wenn neue Module dazukommen,
|
|
||||||
# beide Stellen pflegen.
|
|
||||||
_MODULE_TO_PY = {
|
|
||||||
"ebenen": "rhinopanel",
|
|
||||||
"oberleiste": "oberleiste",
|
|
||||||
"ausschnitte": "ausschnitte",
|
|
||||||
"gestaltung": "gestaltung",
|
|
||||||
"werkzeuge": "werkzeuge",
|
|
||||||
"overrides": "overrides_panel",
|
|
||||||
"dimensionen": "dimensionen",
|
|
||||||
"layouts": "layouts",
|
|
||||||
"elemente": "elemente",
|
|
||||||
}
|
|
||||||
|
|
||||||
_ALL_MODULES = list(_MODULE_TO_PY.keys())
|
|
||||||
|
|
||||||
|
|
||||||
def _read_project_config():
|
|
||||||
"""Liest dossier.project.json aus dem Ordner des aktiven Docs. Rueckgabe:
|
|
||||||
dict oder None. None heisst „keine Config" -> Fallback alle Module."""
|
|
||||||
try:
|
|
||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
|
||||||
if doc is None or not getattr(doc, "Path", None):
|
|
||||||
return None
|
|
||||||
doc_dir = os.path.dirname(doc.Path)
|
|
||||||
if not doc_dir:
|
|
||||||
return None
|
|
||||||
config_path = os.path.join(doc_dir, "dossier.project.json")
|
|
||||||
if not os.path.isfile(config_path):
|
|
||||||
return None
|
|
||||||
with open(config_path, "rb") as f:
|
|
||||||
data = json.loads(f.read().decode("utf-8"))
|
|
||||||
return data if isinstance(data, dict) else None
|
|
||||||
except Exception as ex:
|
|
||||||
print("[STARTUP] Project-Config lesen:", ex)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _migrate_active_doc(*_):
|
|
||||||
"""Migriert Legacy-Keys (traite_*, pause_*) -> dossier_* fuer das aktive Doc."""
|
|
||||||
try:
|
|
||||||
import panel_base
|
|
||||||
panel_base.migrate_to_dossier(Rhino.RhinoDoc.ActiveDoc)
|
|
||||||
except Exception as ex:
|
|
||||||
print("[STARTUP] Migration:", ex)
|
|
||||||
|
|
||||||
|
|
||||||
def _on_doc_opened(sender, e):
|
|
||||||
"""Greift bei jedem geoeffneten Doc nach Rhino-Start. Migration ist
|
|
||||||
idempotent (Flag in doc.Strings)."""
|
|
||||||
try:
|
|
||||||
doc = e.Document if hasattr(e, "Document") else Rhino.RhinoDoc.ActiveDoc
|
|
||||||
import panel_base
|
|
||||||
panel_base.migrate_to_dossier(doc)
|
|
||||||
except Exception as ex:
|
|
||||||
print("[STARTUP] _on_doc_opened:", ex)
|
|
||||||
|
|
||||||
|
|
||||||
def _hint_dossier_ui():
|
|
||||||
"""Mac Rhino 8 kann Window-Layout-Dateien nicht via Skript laden — der
|
|
||||||
Dialog ueber Window-Menue nutzt interne API ohne Command-Echo. Wir
|
|
||||||
geben nur einen Hinweis-Pfad aus, damit der User DOSSIERUI.rhw einmal
|
|
||||||
manuell laden kann. Rhino merkt sich die Anordnung dann persistent."""
|
|
||||||
if not os.path.isfile(_UI_FILE):
|
|
||||||
return
|
|
||||||
print("[STARTUP] DOSSIERUI gefunden: {}".format(_UI_FILE))
|
|
||||||
print("[STARTUP] Einmalig laden: Window -> Window Layout -> Open -> obige Datei")
|
|
||||||
print("[STARTUP] Anordnung bleibt danach ueber Rhino-Restarts erhalten.")
|
|
||||||
|
|
||||||
|
|
||||||
def _load_all(sender, e):
|
|
||||||
"""Wird beim ersten Idle ausgefuehrt — entkoppelt sich danach selbst."""
|
|
||||||
try:
|
|
||||||
Rhino.RhinoApp.Idle -= _load_all
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
print("[STARTUP] Lade DOSSIER-Panels...")
|
|
||||||
# Migration einmal fuer das beim Start aktive Doc
|
|
||||||
_migrate_active_doc()
|
|
||||||
# Und Listener fuer spaeter geoeffnete Docs registrieren
|
|
||||||
try:
|
|
||||||
Rhino.RhinoDoc.EndOpenDocument += _on_doc_opened
|
|
||||||
except Exception as ex:
|
|
||||||
print("[STARTUP] EndOpenDocument-Hook:", ex)
|
|
||||||
# Projekt-Config bestimmt, welche Module geladen werden. Ohne Config
|
|
||||||
# (kein Launcher benutzt, oder Datei nicht da) laedt der Host alles.
|
|
||||||
config = _read_project_config()
|
|
||||||
if config and isinstance(config.get("modules"), list):
|
|
||||||
enabled_ids = [m for m in config["modules"] if m in _MODULE_TO_PY]
|
|
||||||
unknown = [m for m in config["modules"] if m not in _MODULE_TO_PY]
|
|
||||||
print("[STARTUP] Projekt: '{}'".format(config.get("name") or "?"))
|
|
||||||
print("[STARTUP] Aktivierte Module: {}".format(", ".join(enabled_ids) or "(keine)"))
|
|
||||||
if unknown:
|
|
||||||
print("[STARTUP] Unbekannte Modul-IDs in Config: {}".format(unknown))
|
|
||||||
else:
|
|
||||||
enabled_ids = _ALL_MODULES
|
|
||||||
print("[STARTUP] Keine dossier.project.json — alle Module laden")
|
|
||||||
# massstab.py wird als Library mitgeladen (von oberleiste/ausschnitte/...)
|
|
||||||
# und braucht hier nicht mehr als eigenstaendiges Panel zu erscheinen.
|
|
||||||
for mod_id in enabled_ids:
|
|
||||||
py_name = _MODULE_TO_PY[mod_id]
|
|
||||||
try:
|
|
||||||
__import__(py_name)
|
|
||||||
print("[STARTUP] {} ({}) OK".format(mod_id, py_name))
|
|
||||||
except Exception as ex:
|
|
||||||
print("[STARTUP] {} ({}) FEHLER: {}".format(mod_id, py_name, ex))
|
|
||||||
# DOSSIERUI Window-Layout — Hinweis fuer manuelles Laden
|
|
||||||
_hint_dossier_ui()
|
|
||||||
print("[STARTUP] Fertig")
|
|
||||||
|
|
||||||
|
|
||||||
Rhino.RhinoApp.Idle += _load_all
|
|
||||||
print("[STARTUP] geplant - laedt sobald Rhino idle ist")
|
|
||||||
Reference in New Issue
Block a user