Swisstopo + OSM Importer + Höhenlinien + Bulk-Op Performance
Swisstopo Iter 3:
- Ortho-Drape: TIN-Mesh aus Terrain-Grid mit per-vertex UVs + PictureFrame-Material
- Project-Cache: TIFs werden neben .3dm gespeichert (SMB-shareable)
- Layer-Restruktur: 80_swisstopo/{Terrain, Luftbild} Sub-Ebenen
- TIFs direkt (kein PNG-Downsampling) für volle Auflösung
- UV-Inset gegen weisse Streifen zwischen Kacheln
- Hoehenlinien (2D, swissALTI3D) auf aktives Geschoss OKFF projiziert
- TIN-Mesh + Schichtenmodell aus Contours (separate Optionen)
- TLM3D entfernt (swisstopo liefert nur GDB/SHP, kein DXF)
OSM Importer (neu):
- rhino/osm.py: Overpass-API-Client
- src/OsmApp.jsx: React-Dialog mit Adresse + Radius + 7 Kategorien
- Strassen/Gebäude/Wasser/Wasserläufe/Parks/Wald/Fusswege (Codes 7101-7107)
- ElementeApp: PillGroup "Importer" mit Swisstopo + OSM Buttons
Sub-Ebenen — rekursiv durch hierarchische Ebenen:
- Visibility-Toggle: slimEbene rekursiv (children bleiben erhalten)
- Settings-Dialog: _find_sublayer_by_code_recursive + _replace_in_tree
- Hatch Auto-Fill: refresh_layer_fills + _fill_signature + _ebene_fill_for_layer
alle rekursiv durch children
- EbenenSettingsApp: flattenEbenen-Helper
Bulk-Op Performance (Delete/Cut/etc.):
- _USER_BULK_CMDS + _BULK_ACTIVE_KEY Sticky-Flag
- CommandBegin: doc.Views.RedrawEnabled = False + Listener-Bail aktiv
- CommandEnd: RedrawEnabled restore + 1× Redraw + Selection-Refresh
- Bail-outs in dimensionen.on_idle/on_select, elemente._on_idle_selection,
gestaltung.on_idle_flush/on_delete
- Verhindert das sichtbare "Runterzählen" pro Element bei Bulk-Delete
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+351
-18
@@ -20,11 +20,37 @@ import Rhino
|
||||
import Rhino.Geometry as rg
|
||||
import System
|
||||
|
||||
CACHE_DIR = os.path.expanduser("~/Library/Caches/Dossier/swisstopo")
|
||||
DEFAULT_CACHE_DIR = os.path.expanduser("~/Library/Caches/Dossier/swisstopo")
|
||||
CACHE_DIR = DEFAULT_CACHE_DIR
|
||||
STAC_BASE = "https://data.geo.admin.ch/api/stac/v1"
|
||||
SEARCH_API = "https://api3.geo.admin.ch/rest/services/api/SearchServer"
|
||||
|
||||
|
||||
def get_cache_dir_for_doc(doc):
|
||||
"""Cache-Pfad fuer ein Doc. Wenn das Doc auf Disk liegt: Subfolder neben
|
||||
der .3dm-Datei (`<dir>/<basename>_swisstopo/`). Damit reisen die Files
|
||||
mit dem Projekt — kann via SMB von anderen Maschinen geoeffnet werden
|
||||
solange der Mount-Pfad identisch ist. Falls Doc nicht gespeichert:
|
||||
globaler Fallback-Cache."""
|
||||
try:
|
||||
p = doc.Path if doc else None
|
||||
if p and os.path.isfile(p):
|
||||
doc_dir = os.path.dirname(p)
|
||||
doc_base = os.path.splitext(os.path.basename(p))[0]
|
||||
return os.path.join(doc_dir, doc_base + "_swisstopo")
|
||||
except Exception: pass
|
||||
return DEFAULT_CACHE_DIR
|
||||
|
||||
|
||||
def set_cache_dir(path):
|
||||
"""Stellt das aktive Cache-Verzeichnis. Alle nachfolgenden Downloads
|
||||
landen dort. Aufrufer-Verantwortung: vor jedem Import den richtigen
|
||||
Cache setzen (per-Doc oder global)."""
|
||||
global CACHE_DIR
|
||||
CACHE_DIR = path
|
||||
_ensure_cache()
|
||||
|
||||
|
||||
def _ensure_cache():
|
||||
if not os.path.isdir(CACHE_DIR):
|
||||
try: os.makedirs(CACHE_DIR)
|
||||
@@ -416,24 +442,86 @@ def _fetch_buildings_from_collection(collection_id, bbox_wgs, variant,
|
||||
return paths
|
||||
|
||||
|
||||
def fetch_buildings_dwg(bbox_lv95, progress=None, variant="separated"):
|
||||
"""Holt swissBUILDINGS3D Tile-CAD-Files. Versucht erst v3.0 (separated/
|
||||
solid Varianten), faellt automatisch auf v2.0 zurueck wenn v3.0 in der
|
||||
Region keine brauchbaren Files liefert (typisch in Staedten — die 3.0-
|
||||
Tiles sind dort >700 MB pro Stueck und werden vom Size-Limit geblockt)."""
|
||||
def fetch_buildings_dwg(bbox_lv95, progress=None, variant="separated",
|
||||
version="v2"):
|
||||
"""Holt swissBUILDINGS3D Tile-CAD-Files.
|
||||
|
||||
version='v2': stabile 2.0-Variante (1km-Tiles, keine Solid/Separated-
|
||||
Aufteilung — alle Kategorien auf eigenen DXF-Layern).
|
||||
version='v3': Beta 3.0-Variante mit Solid/Separated-Wahl. In Staedten
|
||||
oft >700 MB pro Tile → auto-fallback auf v2 wenn v3
|
||||
nichts brauchbares liefert."""
|
||||
bbox_wgs = lv95_bbox_to_wgs84_bbox(*bbox_lv95)
|
||||
paths = _fetch_buildings_from_collection(
|
||||
_BUILDINGS_COLLECTION_V3, bbox_wgs, variant, progress=progress)
|
||||
if not paths:
|
||||
if progress: progress("v3.0 lieferte keine Tiles — fallback auf v2.0 (1km-Tiles)...")
|
||||
# v2.0 hat keine variant-Marker im Filename, ist immer "separated"-
|
||||
# artig (Kategorien auf eigenen DXF-Layern innerhalb einer DWG).
|
||||
if version == "v3":
|
||||
paths = _fetch_buildings_from_collection(
|
||||
_BUILDINGS_COLLECTION_V3, bbox_wgs, variant, progress=progress)
|
||||
if not paths:
|
||||
if progress: progress("v3.0 lieferte keine Tiles — fallback auf v2.0...")
|
||||
paths = _fetch_buildings_from_collection(
|
||||
_BUILDINGS_COLLECTION_V2, bbox_wgs, variant, progress=progress)
|
||||
else:
|
||||
paths = _fetch_buildings_from_collection(
|
||||
_BUILDINGS_COLLECTION_V2, bbox_wgs, variant, progress=progress)
|
||||
if progress: progress("{} CAD-Datei(en) bereit".format(len(paths)))
|
||||
return paths
|
||||
|
||||
|
||||
# --- TLM3D Vektor (Strassen / Gewaesser / Bahn / Vegetation) ----------------
|
||||
# swisstopo bietet TLM3D unter mehreren Collection-IDs an (genaue Namen
|
||||
# variieren). Wir probieren defensiv mehrere Kandidaten und nehmen DXF/DWG
|
||||
# wenn verfuegbar (alles andere — GPKG/SHP — koennen wir nicht parsen).
|
||||
|
||||
# Echte swisstopo TLM-Collections (verifiziert via STAC API):
|
||||
# ch.swisstopo.swisstlm3d — voller TLM3D Layer (~ganze CH)
|
||||
# ch.swisstopo.swisstlmregio — kleinere Auflösung 1:200000
|
||||
# ch.swisstopo.swissboundaries3d — Verwaltungsgrenzen
|
||||
# ch.swisstopo.swiss-map-vector25 — 1:25000 Vektor
|
||||
# Achtung: ALLE liefern nur GDB/SHP/GPKG/XTF — KEIN DXF/DWG. Direkter Rhino-
|
||||
# Import funktioniert nicht ohne Shapefile-/GPKG-Parser.
|
||||
_TLM_COLLECTIONS = [
|
||||
"ch.swisstopo.swisstlm3d",
|
||||
"ch.swisstopo.swisstlmregio",
|
||||
"ch.swisstopo.swissboundaries3d",
|
||||
]
|
||||
|
||||
|
||||
def fetch_tlm3d_vector(bbox_lv95, kinds, progress=None):
|
||||
"""Versucht swissTLM3D-Daten als DXF/DWG zu holen. swisstopo liefert
|
||||
aktuell NUR GDB/SHP/GPKG-Formate — kein DXF. Diese Funktion findet
|
||||
daher in den meisten Faellen keine importierbaren Files; sie loggt
|
||||
aber sauber, was verfuegbar waere, falls wir spaeter einen
|
||||
Shapefile-Parser einbauen."""
|
||||
bbox_wgs = lv95_bbox_to_wgs84_bbox(*bbox_lv95)
|
||||
out = {}
|
||||
if progress:
|
||||
progress("TLM3D-Import: swisstopo bietet aktuell KEINE DXF-Assets")
|
||||
progress(" (nur GDB/SHP/GPKG — Rhino kann diese nicht nativ lesen)")
|
||||
progress(" Verfuegbare Collections (zur Info):")
|
||||
for coll in _TLM_COLLECTIONS:
|
||||
try:
|
||||
items = stac_query(coll, bbox_wgs,
|
||||
asset_extensions=None) # alle Assets
|
||||
except Exception as ex:
|
||||
if progress: progress(" {}: HTTP-fail ({})".format(coll, ex))
|
||||
continue
|
||||
if not items:
|
||||
if progress: progress(" {}: keine Items in der Region".format(coll))
|
||||
continue
|
||||
sample = items[0]
|
||||
formats = set()
|
||||
for k, a in (sample.get("assets") or {}).items():
|
||||
href = (a.get("href") or "").lower()
|
||||
for ext in (".gdb.zip", ".shp.zip", ".gpkg.zip", ".gpkg",
|
||||
".xtf.zip", ".dxf", ".dwg"):
|
||||
if href.endswith(ext): formats.add(ext.lstrip("."))
|
||||
if progress: progress(" {}: {} Items, Formate: {}".format(
|
||||
coll, len(items), ", ".join(sorted(formats)) or "?"))
|
||||
if progress:
|
||||
progress("→ TLM3D-Direct-Import nicht moeglich. Nutze OSM-Importer "
|
||||
"fuer Vector-Daten (Strassen/Wasser/Gebaeude).")
|
||||
return out
|
||||
|
||||
|
||||
# --- Terrain: swissALTI3D via XYZ ASCII -------------------------------------
|
||||
|
||||
def fetch_terrain_xyz(bbox_lv95, resolution="2.0", progress=None):
|
||||
@@ -640,6 +728,151 @@ def mesh_from_grid(grid, origin_shift=(0, 0, 0), unit_scale=1.0):
|
||||
return mesh
|
||||
|
||||
|
||||
def generate_mesh_from_contours(doc, contour_curves, sample_step_m=2.0,
|
||||
m_to_unit=1.0, progress=None):
|
||||
"""Baut ein TIN-Mesh aus Hoehenlinien-Curves. Jede Curve hat ihre echte
|
||||
Z-Hoehe — wir sampeln Vertices entlang der Curves und triangulieren
|
||||
sie via Rhinos _-MeshPatch / _-Delaunay Command. Resultat: Topographie-
|
||||
Mesh basierend auf den diskreten Hoehenlinien-Stufen.
|
||||
|
||||
Liefert RhinoObject (Mesh) oder None."""
|
||||
import System
|
||||
if not contour_curves: return None
|
||||
pts = []
|
||||
for c in contour_curves:
|
||||
if c is None: continue
|
||||
# Polyline-Vertices wenn moeglich (exakt), sonst entlang Curve sampeln
|
||||
ok, poly = c.TryGetPolyline()
|
||||
if ok and poly is not None:
|
||||
for pt in poly: pts.append(rg.Point3d(pt))
|
||||
else:
|
||||
try:
|
||||
L = c.GetLength()
|
||||
n = max(2, int(L / (sample_step_m * m_to_unit)))
|
||||
params = c.DivideByCount(n, True)
|
||||
if params:
|
||||
for t in params: pts.append(c.PointAt(t))
|
||||
except Exception: pass
|
||||
if len(pts) < 3:
|
||||
if progress: progress("Contour-Mesh: zu wenig Vertices ({})".format(len(pts)))
|
||||
return None
|
||||
if progress: progress("Contour-Mesh: trianguliere {} Vertices...".format(len(pts)))
|
||||
# Temp-Points erzeugen + selektieren
|
||||
temp_pids = []
|
||||
try:
|
||||
for p in pts:
|
||||
pid = doc.Objects.AddPoint(p)
|
||||
if pid and pid != System.Guid.Empty:
|
||||
temp_pids.append(pid)
|
||||
if not temp_pids:
|
||||
if progress: progress("Contour-Mesh: keine Temp-Points")
|
||||
return None
|
||||
doc.Objects.UnselectAll()
|
||||
for pid in temp_pids: doc.Objects.Select(pid)
|
||||
before = set(o.Id for o in doc.Objects
|
||||
if o and not o.IsDeleted
|
||||
and isinstance(o.Geometry, rg.Mesh))
|
||||
# Mehrere Commands probieren (Mac Rhino 8 vs neuere Versionen)
|
||||
cmd_tried = None
|
||||
for cmd in ['_-MeshPatch _Enter _Enter',
|
||||
'_-Delaunay _Enter',
|
||||
'_-DelaunayMesh _Enter',
|
||||
'_-MeshFromPoints _Enter']:
|
||||
try:
|
||||
Rhino.RhinoApp.RunScript(cmd, False)
|
||||
except Exception: continue
|
||||
cmd_tried = cmd
|
||||
new_mesh = next((o for o in doc.Objects
|
||||
if o and not o.IsDeleted
|
||||
and isinstance(o.Geometry, rg.Mesh)
|
||||
and o.Id not in before), None)
|
||||
if new_mesh:
|
||||
if progress: progress("→ Contour-Mesh via '{}'".format(cmd.split()[0]))
|
||||
return new_mesh
|
||||
if progress:
|
||||
progress("Contour-Mesh: kein Command lieferte ein Mesh "
|
||||
"(zuletzt: {})".format(cmd_tried))
|
||||
return None
|
||||
finally:
|
||||
# Temp-Points wieder weg
|
||||
doc.Objects.UnselectAll()
|
||||
for pid in temp_pids:
|
||||
try: doc.Objects.Delete(pid, True)
|
||||
except Exception: pass
|
||||
|
||||
|
||||
def generate_schichtenmodell(doc, contour_curves, progress=None):
|
||||
"""Schichtenmodell: jede geschlossene Hoehenlinie wird zu einer planaren
|
||||
Flaeche auf ihrer Z-Hoehe. Stacked Discs — der architektonische
|
||||
'Pappmodell'-Look. Offene Konturen (typ. am bbox-Rand) werden
|
||||
uebersprungen.
|
||||
|
||||
Liefert Liste von erzeugten RhinoObjects."""
|
||||
import System
|
||||
if not contour_curves: return []
|
||||
created = []
|
||||
tol = doc.ModelAbsoluteTolerance
|
||||
n_open = 0
|
||||
for c in contour_curves:
|
||||
if c is None: continue
|
||||
try:
|
||||
if not c.IsClosed:
|
||||
n_open += 1
|
||||
continue
|
||||
breps = rg.Brep.CreatePlanarBreps(c, tol)
|
||||
except Exception:
|
||||
continue
|
||||
if not breps: continue
|
||||
for brep in breps:
|
||||
gid = doc.Objects.AddBrep(brep)
|
||||
if gid and gid != System.Guid.Empty:
|
||||
obj = doc.Objects.Find(gid)
|
||||
if obj: created.append(obj)
|
||||
if progress:
|
||||
progress("→ {} Schichten-Flaechen ({} offene Konturen skipped)".format(
|
||||
len(created), n_open))
|
||||
return created
|
||||
|
||||
|
||||
def generate_contour_curves(grid, shift_lv95, m_to_unit, interval=2.0,
|
||||
progress=None):
|
||||
"""Generiert Hoehenlinien (Contour-Curves) aus dem Terrain-Grid via
|
||||
Mesh.CreateContourCurves.
|
||||
|
||||
interval: Hoehenabstand in REALEN METERN (1.0/2.0/5.0 typisch).
|
||||
Liefert Liste von rg.Curve-Objekten in Doc-Units. Caller macht
|
||||
doc.Objects.AddCurve + Layer-Move."""
|
||||
if not grid or not grid.get("points"): return []
|
||||
# Temp-Mesh aus Grid (gleicher Pipeline wie mesh_from_grid)
|
||||
mesh = mesh_from_grid(grid, origin_shift=shift_lv95, unit_scale=m_to_unit)
|
||||
if mesh.Vertices.Count < 3: return []
|
||||
bb = mesh.GetBoundingBox(True)
|
||||
z_min_doc = bb.Min.Z
|
||||
z_max_doc = bb.Max.Z
|
||||
interval_doc = interval * m_to_unit
|
||||
if interval_doc <= 0: return []
|
||||
if progress:
|
||||
z_min_m = z_min_doc / m_to_unit + shift_lv95[2]
|
||||
z_max_m = z_max_doc / m_to_unit + shift_lv95[2]
|
||||
progress("Hoehenlinien: Z {:.1f}–{:.1f} m.ü.M, Abstand {} m".format(
|
||||
z_min_m, z_max_m, interval))
|
||||
try:
|
||||
curves = rg.Mesh.CreateContourCurves(
|
||||
mesh,
|
||||
rg.Point3d(0, 0, z_min_doc),
|
||||
rg.Point3d(0, 0, z_max_doc),
|
||||
interval_doc)
|
||||
except Exception as ex:
|
||||
if progress: progress("Contour fail: {}".format(ex))
|
||||
return []
|
||||
if not curves:
|
||||
if progress: progress("Keine Hoehenlinien erzeugt")
|
||||
return []
|
||||
out = list(curves)
|
||||
if progress: progress("→ {} Hoehenlinien-Kurven".format(len(out)))
|
||||
return out
|
||||
|
||||
|
||||
# --- Orthofoto: SWISSIMAGE 10cm via GeoTIFF --------------------------------
|
||||
|
||||
def fetch_orthophoto(bbox_lv95, resolution="2.0", progress=None):
|
||||
@@ -737,6 +970,108 @@ def _geotiff_to_png(tif_path, max_dim=2048):
|
||||
return None
|
||||
|
||||
|
||||
def add_ortho_draped_mesh(doc, ortho_path, tile_bbox_lv95, terrain_grid,
|
||||
shift_lv95, m_to_unit, z_lift=0.05,
|
||||
target_layer_idx=-1):
|
||||
"""Erzeugt ein Mesh, das der Topographie folgt — textured mit dem Ortho-
|
||||
Foto. Statt einer flachen Plane: Per-Tile-Sub-Mesh aus dem Terrain-Grid
|
||||
mit Per-Vertex-UV (0..1 ueber die Tile-Breite). Material kommt von einem
|
||||
temporaeren PictureFrame (das ist der einzige Weg auf Mac Rhino 8 die
|
||||
embedded Bitmap in Cycles zur Anzeige zu bringen) — der PictureFrame
|
||||
wird hinterher geloescht, nur das Drape-Mesh bleibt.
|
||||
|
||||
terrain_grid: dict aus merge_grids() — wir extrahieren daraus die Punkte
|
||||
innerhalb der Tile-bbox.
|
||||
z_lift: kleiner Z-Offset (in doc-units) gegen Z-Fighting mit dem
|
||||
darunterliegenden Terrain-Mesh."""
|
||||
if not (ortho_path and os.path.isfile(ortho_path)): return None
|
||||
# TIF direkt verwenden — Rhino's _Picture liest GeoTIFF nativ ueber
|
||||
# NSImage (Mac) und behaelt 10cm-Aufloesung (10000×10000 px statt 2k PNG).
|
||||
e_min, n_min, e_max, n_max = tile_bbox_lv95
|
||||
sx, sy, sz = shift_lv95
|
||||
# Terrain-Punkte innerhalb des Tiles aus dem Merged-Grid extrahieren
|
||||
es = sorted(e for e in terrain_grid["es"]
|
||||
if e_min - 0.01 <= e <= e_max + 0.01)
|
||||
ns = sorted(n for n in terrain_grid["ns"]
|
||||
if n_min - 0.01 <= n <= n_max + 0.01)
|
||||
if len(es) < 2 or len(ns) < 2:
|
||||
print("[SWISSTOPO] drape: zu wenig Terrain-Punkte fuer Tile")
|
||||
return None
|
||||
pts = terrain_grid["points"]
|
||||
span_e = e_max - e_min
|
||||
span_n = n_max - n_min
|
||||
# Half-Pixel-Inset: bei 10000×10000 px Tiles wuerde ein Sample exakt an
|
||||
# u=0 oder u=1 auf der Pixel-Grenze landen; mit clamp-to-border kann das
|
||||
# weisse Linien an den Tile-Boundaries erzeugen. Wir verschieben UV
|
||||
# minimal nach innen.
|
||||
UV_INSET = 0.5 / 10000.0 # halbe Pixel-Breite im UV-Raum
|
||||
mesh = rg.Mesh()
|
||||
idx_for = {}
|
||||
for j, ny in enumerate(ns):
|
||||
for i, ex in enumerate(es):
|
||||
z = pts.get((ex, ny))
|
||||
if z is None: continue
|
||||
v_idx = mesh.Vertices.Add(
|
||||
(ex - sx) * m_to_unit,
|
||||
(ny - sy) * m_to_unit,
|
||||
(z - sz) * m_to_unit + z_lift)
|
||||
u = UV_INSET + (ex - e_min) / span_e * (1.0 - 2 * UV_INSET)
|
||||
v = UV_INSET + (ny - n_min) / span_n * (1.0 - 2 * UV_INSET)
|
||||
mesh.TextureCoordinates.Add(u, v)
|
||||
idx_for[(i, j)] = v_idx
|
||||
n_faces = 0
|
||||
for j in range(len(ns) - 1):
|
||||
for i in range(len(es) - 1):
|
||||
a = idx_for.get((i, j))
|
||||
b = idx_for.get((i+1, j))
|
||||
c = idx_for.get((i+1, j+1))
|
||||
d = idx_for.get((i, j+1))
|
||||
if a is None or b is None or c is None or d is None: continue
|
||||
mesh.Faces.AddFace(a, b, c, d)
|
||||
n_faces += 1
|
||||
if n_faces == 0:
|
||||
print("[SWISSTOPO] drape: keine Faces erzeugt")
|
||||
return None
|
||||
mesh.Normals.ComputeNormals()
|
||||
mesh.Compact()
|
||||
# Temp-PictureFrame off-screen erzeugen — ergibt working RenderMaterial
|
||||
# mit Bitmap-Texture, das wir auf das Mesh uebertragen.
|
||||
# embedBitmap=False: Pfad-Referenz statt 70MB-TIF-Embedding ins .3dm.
|
||||
# Cache ist persistent (~/Library/Caches), Pfad bleibt gueltig.
|
||||
pf_plane = rg.Plane(rg.Point3d(-1e6, -1e6, -1e6),
|
||||
rg.Vector3d.XAxis, rg.Vector3d.YAxis)
|
||||
try:
|
||||
pf_gid = doc.Objects.AddPictureFrame(
|
||||
pf_plane, ortho_path, False, 1.0, 1.0, True, False)
|
||||
except Exception as ex:
|
||||
print("[SWISSTOPO] drape: PictureFrame-create fail:", ex)
|
||||
return None
|
||||
if not pf_gid or pf_gid == System.Guid.Empty:
|
||||
print("[SWISSTOPO] drape: PictureFrame Empty-GUID")
|
||||
return None
|
||||
pf_obj = doc.Objects.Find(pf_gid)
|
||||
pf_mat_idx = pf_obj.Attributes.MaterialIndex
|
||||
# Mesh ins Doc + Material vom PictureFrame uebernehmen
|
||||
mesh_gid = doc.Objects.AddMesh(mesh)
|
||||
mesh_obj = doc.Objects.Find(mesh_gid)
|
||||
if mesh_obj is None:
|
||||
try: doc.Objects.Delete(pf_gid, True)
|
||||
except Exception: pass
|
||||
return None
|
||||
attrs = mesh_obj.Attributes.Duplicate()
|
||||
attrs.MaterialSource = Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject
|
||||
attrs.MaterialIndex = pf_mat_idx
|
||||
if target_layer_idx >= 0:
|
||||
attrs.LayerIndex = target_layer_idx
|
||||
doc.Objects.ModifyAttributes(mesh_obj, attrs, True)
|
||||
# Temp-PictureFrame loeschen — das Mesh hat jetzt das Material
|
||||
try: doc.Objects.Delete(pf_gid, True)
|
||||
except Exception: pass
|
||||
print("[SWISSTOPO] drape mesh: {}x{} grid, {} faces, mat={}".format(
|
||||
len(es), len(ns), n_faces, pf_mat_idx))
|
||||
return mesh_obj
|
||||
|
||||
|
||||
def add_ortho_plane(doc, ortho_path, tile_bbox_lv95, shift_lv95, m_to_unit,
|
||||
z_doc=0.0, target_layer_idx=-1):
|
||||
"""Erzeugt eine planare Brep-Flaeche mit dem SWISSIMAGE-Foto als Material,
|
||||
@@ -749,11 +1084,8 @@ def add_ortho_plane(doc, ortho_path, tile_bbox_lv95, shift_lv95, m_to_unit,
|
||||
|
||||
Liefert den RhinoObject der erzeugten Plane (oder None)."""
|
||||
if not (ortho_path and os.path.isfile(ortho_path)): return None
|
||||
# GeoTIFF → PNG damit Rhino's Material-Bitmap es als Diffuse nehmen kann
|
||||
if ortho_path.lower().endswith((".tif", ".tiff")):
|
||||
png = _geotiff_to_png(ortho_path)
|
||||
if not png: return None
|
||||
ortho_path = png
|
||||
# TIF direkt — Rhino's Picture-Pfad liest GeoTIFF nativ (NSImage auf Mac).
|
||||
# Behaelt die volle 10cm-Aufloesung statt auf 2k PNG runter zu skalieren.
|
||||
# bbox in Doc-Units (nach Shift + Scale)
|
||||
e_min, n_min, e_max, n_max = tile_bbox_lv95
|
||||
sx, sy, sz = shift_lv95
|
||||
@@ -785,7 +1117,8 @@ def add_ortho_plane(doc, ortho_path, tile_bbox_lv95, shift_lv95, m_to_unit,
|
||||
True, # selfIllumination=True — Textur unabhaengig von
|
||||
# Lighting sichtbar (sonst evtl. dunkel in modes
|
||||
# ohne Lichtquellen)
|
||||
True) # embedBitmap=True (Pfad-Probleme umgehen)
|
||||
False) # embedBitmap=False — Pfad-Referenz (Cache bleibt
|
||||
# persistent, kein 70MB-Embedding pro Tile)
|
||||
if gid == System.Guid.Empty:
|
||||
print("[SWISSTOPO] AddPictureFrame: Empty-GUID")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user