Schnitt/Ansicht-Feature + Terrain-Volumen + Geschoss-Add-Dialog

Schnitt-Feature V1+V2:
- Neues rhino/schnitte.py mit Pick-Workflow, Activation (Clipping-Planes +
  Parallel-View), 2D-Plan-Symbol auf 18_Schnittlinien-Sublayer
- Doppelklick auf Symbol aktiviert den Schnitt
- Schnitt-Settings (cutAtLine/Tiefe/Höhen/Blickrichtung) im GeschossSettingsDialog
- View-Snapshot + Restore beim Wechsel Schnitt → Geschoss
- Symbol-Cleanup bei Delete via normalem Ebenen-Menü

Terrain als Volumen:
- swisstopo.volumize_terrain_object: Skirt + Bottom-Cap auf Mesh/Brep
  damit Clipping-Planes gefuellte Querschnitte erzeugen
- UI im SwisstopoApp mit Nachbearbeitung-Section + Tiefen-Eingabe

Geschoss-Add mit Dialog:
- + im GeschossManager oeffnet 3-Optionen-Picker (Geschoss/Schnitt/Zeichnung)
- Geschoss-Dialog mit Anker-Dropdown, Position über/unter, Auto-Name,
  Höhen-Prefill aus Anker

Fix: _send_state fallback — Element gilt als selektiert wenn Source ODER
Volume in der Selection ist (robust gegen Layer-Visibility wenn Referenz-
linien-Layer im aktuellen Mode versteckt ist)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 18:28:59 +02:00
parent 3277f61ced
commit 059cbf8d4d
8 changed files with 1356 additions and 22 deletions
+58 -1
View File
@@ -657,6 +657,16 @@ def _layer_path_referenz(doc, geschoss_name):
return "{}::{}".format(geschoss_name, sub)
def _layer_path_schnittlinie(doc, geschoss_name):
"""Sublayer 'Schnittlinien' (Code 18) — fuer die 2D-Plan-Symbole von
Schnitten/Ansichten. Eigene Ebene damit der User sie unabhaengig vom
Restplan ein-/ausblenden kann. Konvention SIA: dunkle Linie + Pfeile."""
sub = _find_ebene_sublayer_name(doc, ["schnittlinie", "schnittlinien"],
"18", "Schnittlinien",
default_color="#404040", default_lw=0.35)
return "{}::{}".format(geschoss_name, sub)
def _ensure_oeff_ebenen_in_doc(doc):
"""Registriert die Oeffnungen-Ebene + alle Piece-Children im
`dossier_ebenen`-JSON-Tree (als Children der WAENDE-Ebene). Damit
@@ -5551,6 +5561,23 @@ class ElementeBridge(panel_base.BaseBridge):
self.send("STATE", {"elements": [], "geschosse": [], "selection": None})
return
geschosse = _load_geschosse(doc)
# Vorpass: alle Element-IDs sammeln deren Source ODER irgendein
# Volume gerade selektiert ist. Macht die Selection-Detection
# robust gegen Layer-Visibility-Edge-Cases: wenn die Source-Curve
# auf einem hidden Layer liegt (z.B. Referenzlinien ausgeblendet
# oder im "Nur aktive"-Visibility-Mode), wuerde der Partner-Sync
# in _on_select_objects das Source-Objekt nicht selektieren
# koennen — der User klickt aber das sichtbare Volume an, also
# ist die Element-Auswahl trotzdem eindeutig.
sel_eids = set()
try:
for o in doc.Objects.GetSelectedObjects(False, False):
m = _read_meta(o)
if not m: continue
t = m.get("type")
if t in SOURCE_TYPES or t in VOLUME_TYPES:
sel_eids.add(m["id"])
except Exception: pass
# Alle Source-Objekte (Achsen + Outlines) durchgehen
elements = []
seen_ids = set()
@@ -5562,7 +5589,7 @@ class ElementeBridge(panel_base.BaseBridge):
seen_ids.add(meta["id"])
g = _geschoss_by_id(doc, meta["geschoss"])
geschoss_name = g.get("name", "?") if g else "?"
selected = obj.IsSelected(False) > 0
selected = (meta["id"] in sel_eids) or (obj.IsSelected(False) > 0)
base = {
"id": meta["id"],
"objectId": str(obj.Id),
@@ -7968,6 +7995,13 @@ class ElementeBridge(panel_base.BaseBridge):
except Exception as ex:
self._push_log("Grid-Merge fehlgeschlagen: {}".format(ex))
# Terrain-Volumize-Option: aus Opts lesen. depth in
# METERN aus UI → in Doc-Units konvertieren.
as_volume = bool(opts.get("terrainAsVolume"))
try: vol_depth_m = float(opts.get("terrainVolumeDepth") or 10.0)
except Exception: vol_depth_m = 10.0
vol_depth_doc = max(0.01, vol_depth_m) * m_to_unit
# 3D-Mesh bauen wenn Terrain gewuenscht — unabhaengig vom
# Ortho. Wenn Ortho auch an ist: Drape-Mesh liegt ueber
# dem Plain-Mesh (User togglet im Layer-Panel was er
@@ -7982,6 +8016,11 @@ class ElementeBridge(panel_base.BaseBridge):
mesh.Vertices.Count, mesh.Faces.Count))
gid = d.Objects.AddMesh(mesh)
obj = d.Objects.Find(gid)
if obj and as_volume:
vol_obj = swisstopo.volumize_terrain_object(
d, obj, vol_depth_doc,
progress=self._push_log)
if vol_obj is not None: obj = vol_obj
if obj: mesh_objects.append((obj, merged_grid["bbox"]))
except Exception as ex:
self._push_log("Mesh-Bau fehlgeschlagen: {}".format(ex))
@@ -8059,6 +8098,11 @@ class ElementeBridge(panel_base.BaseBridge):
m_to_unit=m_to_unit,
progress=self._push_log)
if tin_obj:
if as_volume:
vol_obj = swisstopo.volumize_terrain_object(
d, tin_obj, vol_depth_doc,
progress=self._push_log)
if vol_obj is not None: tin_obj = vol_obj
# Tag + auf 80_swisstopo Parent
at = tin_obj.Attributes.Duplicate()
at.SetUserString("dossier_swisstopo_kind", "contour_tin")
@@ -8100,6 +8144,11 @@ class ElementeBridge(panel_base.BaseBridge):
patch_obj = swisstopo.generate_patch_from_contours(
d, raw_contours, progress=self._push_log)
if patch_obj:
if as_volume:
vol_obj = swisstopo.volumize_terrain_object(
d, patch_obj, vol_depth_doc,
progress=self._push_log)
if vol_obj is not None: patch_obj = vol_obj
at = patch_obj.Attributes.Duplicate()
at.SetUserString("dossier_swisstopo_kind", "contour_patch")
d.Objects.ModifyAttributes(patch_obj, at, True)
@@ -11496,6 +11545,14 @@ def _install_listeners(bridge):
"cmd_end": _on_command_end,
}
print("[ELEMENTE] Listener aktiv (Replace + Add + Delete + Select + Idle + Cmd)")
# Schnitt-Doppelklick-Handler global registrieren — idempotent via
# sticky-Flag im schnitte-Modul, mehrfache Aufrufe bei Re-Loads
# raeumen den alten Handler vorher weg.
try:
import schnitte
schnitte.install_double_click_handler()
except Exception as ex:
print("[ELEMENTE] schnitt dblclick install:", ex)
def _bridge_factory():