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
+127
View File
@@ -909,6 +909,133 @@ def generate_patch_from_contours(doc, contour_curves, progress=None):
return None
def volumize_terrain_object(doc, top_obj, depth_doc, progress=None):
"""Wandelt ein offenes Terrain (Mesh ODER Brep) in ein geschlossenes
Mesh-Volumen um: Skirt um den Boundary + planarer Boden bei
(min_z - depth_doc). Resultat hat eine Section beim Schneiden mit
einer Clipping-Plane.
Strategie:
1. Mesh-Source ermitteln (Brep → Mesh.CreateFromBrep, Mesh → direkt)
2. GetNakedEdges() liefert die Boundary-Loop(s) als Polylines
3. Pro Loop: Skirt-Quads zwischen Top-Edge und Bottom-Vertices
4. Pro Loop: Bottom-Cap via Mesh.CreateFromClosedPolyline (Rhino
triangliert auch nicht-konvexe Boundaries sauber)
5. CombineIdentical schweisst Top + Skirt-Top zusammen
Ersetzt das Original im Doc (Delete+Add mit gleichen Attributes).
Liefert das neue RhinoObject oder None bei Fehler."""
import System
if top_obj is None or top_obj.IsDeleted: return None
geom = top_obj.Geometry
if geom is None: return None
# 1) Top-Mesh ermitteln (Brep meshen wenn noetig)
top_mesh = None
if isinstance(geom, rg.Mesh):
top_mesh = geom.Duplicate()
elif isinstance(geom, rg.Brep):
try:
mp = rg.MeshingParameters.Default
meshes = rg.Mesh.CreateFromBrep(geom, mp)
if meshes and len(meshes) > 0:
joined = rg.Mesh()
for m in meshes: joined.Append(m)
top_mesh = joined
except Exception as ex:
if progress: progress("Volumize: Brep-Meshing-Fehler: {}".format(ex))
return None
elif isinstance(geom, rg.Extrusion):
try:
brep = geom.ToBrep(False)
mp = rg.MeshingParameters.Default
meshes = rg.Mesh.CreateFromBrep(brep, mp)
if meshes and len(meshes) > 0:
joined = rg.Mesh()
for m in meshes: joined.Append(m)
top_mesh = joined
except Exception: pass
if top_mesh is None or top_mesh.Vertices.Count < 3:
if progress: progress("Volumize: kein Mesh-Top")
return None
# 2) Boundary-Loops
naked = top_mesh.GetNakedEdges()
if naked is None or len(naked) == 0:
if progress: progress("Volumize: keine Boundary — Terrain schon geschlossen")
return None
# 3) Bottom-Z = min_z des Top - depth
bb = top_mesh.GetBoundingBox(True)
if not bb.IsValid:
if progress: progress("Volumize: ungueltige BoundingBox")
return None
bottom_z = bb.Min.Z - float(depth_doc)
if progress:
progress("Volumize: {} Boundary-Loop(s), Boden bei Z={:.3f}".format(
len(naked), bottom_z))
# 4) Volumen-Mesh aufbauen: top + Skirt + Bottom-Cap
vol = top_mesh.Duplicate()
for loop in naked:
try:
if loop is None or loop.Count < 3: continue
# Polyline-Punkte (offene Form — closing point ggf. entfernen)
pts = [rg.Point3d(p) for p in loop]
if len(pts) > 1 and pts[0].DistanceTo(pts[-1]) < 1e-6:
pts = pts[:-1]
n = len(pts)
if n < 3: continue
# Top + Bottom Vertices anfuegen
top_idx = []
bot_idx = []
for p in pts:
top_idx.append(vol.Vertices.Add(p.X, p.Y, p.Z))
bot_idx.append(vol.Vertices.Add(p.X, p.Y, bottom_z))
# Skirt: Quads zwischen aufeinanderfolgenden Top/Bottom-Paaren.
# Faces sind "innen orientiert" — bei Bedarf normals
# umdrehen via ComputeNormals + RebuildNormals.
for i in range(n):
j = (i + 1) % n
vol.Faces.AddFace(top_idx[i], top_idx[j],
bot_idx[j], bot_idx[i])
# Bottom-Cap via planar Polyline → Mesh
bot_pts = [rg.Point3d(p.X, p.Y, bottom_z) for p in pts]
bot_pts.append(bot_pts[0]) # schliessen
bot_poly = rg.Polyline(bot_pts)
cap = rg.Mesh.CreateFromClosedPolyline(bot_poly)
if cap is not None and cap.Vertices.Count >= 3:
vol.Append(cap)
except Exception as ex:
if progress: progress("Volumize: Loop-Fehler: {}".format(ex))
# 5) Cleanup
try: vol.Vertices.CombineIdentical(True, True)
except Exception: pass
try: vol.Compact()
except Exception: pass
try:
vol.Normals.ComputeNormals()
vol.FaceNormals.ComputeFaceNormals()
# Topologie pruefen + Naked-Edges-Anzahl loggen
post_naked = vol.GetNakedEdges()
if progress:
n_naked = len(post_naked) if post_naked else 0
progress("Volumize: Resultat {} naked-edge-loops (0 = closed)".format(
n_naked))
except Exception: pass
# 6) Original ersetzen — Attributes + LayerIndex behalten
try:
attrs = top_obj.Attributes.Duplicate()
old_id = top_obj.Id
new_gid = doc.Objects.AddMesh(vol, attrs)
if new_gid is None or new_gid == System.Guid.Empty:
if progress: progress("Volumize: AddMesh fehlgeschlagen")
return None
doc.Objects.Delete(old_id, True)
new_obj = doc.Objects.Find(new_gid)
if progress: progress("→ Terrain-Volumen erzeugt")
return new_obj
except Exception as ex:
if progress: progress("Volumize: Replace-Fehler: {}".format(ex))
return None
def generate_contour_curves(grid, shift_lv95, m_to_unit, interval=2.0,
progress=None):
"""Generiert Hoehenlinien (Contour-Curves) aus dem Terrain-Grid via