Swisstopo Iter 2 + hierarchische Ebenen + 0-Kote m.ü.M
Swisstopo
- swissBUILDINGS3D 3.0 + Variant-Toggle (separated/solid) im Dialog
- Auto-Fallback auf 2.0 wenn 3.0-Tiles ueber 200 MB sind (Stadt-Fall)
- Defensiver Variant-Filter auf 3 Ebenen (Item, Asset, ZIP-Extract) — keine
Doppelimporte mehr
- Auto-Skala korrigiert jetzt die importierten Objekte (×1000) statt die
User-bbox zu schrumpfen — Buildings bleiben in m-Doc-Skala
- merge_grids: XYZ-Tiles werden vor dem Mesh-Bau vereint, kein 1m-Streifen
zwischen Tiles mehr
- Layer-Konsolidierung: Build_*/Roof_*/Wall_*/Floor_* DWG-Source-Layer
werden auf Sub-Sub-Layer unter 81_Swissbuildings/{Build,Roof,Wall,Floor}
gemappt; solid-Variante landet flach direkt auf dem Parent
- 0-Kote m.ü.M (Projekt-Nullpunkt) wird beim Import als Z-Offset angewandt
Hierarchische Ebenen
- dossier_ebenen unterstuetzt jetzt 'children'-Array (rekursiv)
- layer_builder.build_layers rekursiv (Parent + Children unter jedem Geschoss)
- apply_visibility/update_layer_style/set_ebene_visible/set_ebene_locked
walken den Tree (Sub-Sub-Layer mit gleichem Code-Prefix werden mit-gepflegt)
- EbenenManager mit Chevron-Toggle + Indent pro Level + Context-Menue-Item
'Sub-Ebene hinzufuegen'
- rhinoBridge.applyVisibility schickt Children-Tree (nicht nur Top-Level) —
sonst kommen Sub-Toggles nicht beim Backend an
- Visibility-Key in App.jsx rekursiv durch Children — useEffect feuert jetzt
auch bei Sub-Eye-Toggles
0-Kote m.ü.M
- Eingabefeld im Geschoss-Settings-Dialog (projektweit)
- Speicherung als dossier_project_zero_mum in doc.Strings
- Wird im Swisstopo-Import als Z-Offset (m + doc-units) angewandt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+83
-21
@@ -87,9 +87,15 @@ def _broadcast_state(doc=None, hatch_patterns=None):
|
||||
try:
|
||||
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
|
||||
e_raw = doc.Strings.GetValue("dossier_ebenen")
|
||||
# Projekt-Nullpunkt in m.ü.M — wird beim Swisstopo-Import als
|
||||
# Z-Offset angewandt (Real-Welt-Höhen → Doc-Z relativ zu OKFF=0).
|
||||
zero_raw = doc.Strings.GetValue("dossier_project_zero_mum")
|
||||
try: zero_mum = float(zero_raw) if zero_raw else 0.0
|
||||
except Exception: zero_mum = 0.0
|
||||
payload = {
|
||||
"zeichnungsebenen": json.loads(z_raw) if z_raw else None,
|
||||
"ebenen": json.loads(e_raw) if e_raw else None,
|
||||
"projectZeroMum": zero_mum,
|
||||
"hatchPatterns": hatch_patterns if hatch_patterns is not None
|
||||
else _hatch_pattern_names(doc),
|
||||
}
|
||||
@@ -367,9 +373,13 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
layer_builder.build_layers(doc, z, e)
|
||||
layer_builder.cleanup_default_layers(doc)
|
||||
self._ensure_active_sublayer()
|
||||
zero_raw = doc.Strings.GetValue("dossier_project_zero_mum")
|
||||
try: zero_mum = float(zero_raw) if zero_raw else 0.0
|
||||
except Exception: zero_mum = 0.0
|
||||
self.send("STATE_SYNC", {
|
||||
"zeichnungsebenen": z,
|
||||
"ebenen": e,
|
||||
"projectZeroMum": zero_mum,
|
||||
"hatchPatterns": _hatch_pattern_names(doc),
|
||||
})
|
||||
except Exception as ex:
|
||||
@@ -471,9 +481,28 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
print("[EBENEN] open_geschoss_settings: kein Geschoss-Payload")
|
||||
return
|
||||
gid = geschoss["id"]
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
# Projekt-Nullpunkt (m.ü.M) mit ins Param-Bundle — als projektweite
|
||||
# Settings auch im Geschoss-Dialog editierbar.
|
||||
try:
|
||||
z_mum_raw = doc.Strings.GetValue("dossier_project_zero_mum") if doc else None
|
||||
project_zero_mum = float(z_mum_raw) if z_mum_raw else 0.0
|
||||
except Exception:
|
||||
project_zero_mum = 0.0
|
||||
params = dict(geschoss)
|
||||
params["projectZeroMum"] = project_zero_mum
|
||||
def on_save(updated):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
# Projekt-Nullpunkt extrahieren (project-weit, nicht pro Geschoss)
|
||||
try:
|
||||
if "projectZeroMum" in updated:
|
||||
val = updated.pop("projectZeroMum")
|
||||
val = float(val) if val is not None else 0.0
|
||||
doc.Strings.SetString("dossier_project_zero_mum", str(val))
|
||||
print("[EBENEN] project_zero_mum = {} m.ü.M".format(val))
|
||||
except Exception as ex:
|
||||
print("[EBENEN] project_zero_mum save:", ex)
|
||||
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
|
||||
if not z_raw:
|
||||
print("[EBENEN] save_geschoss: kein z-Store"); return
|
||||
@@ -497,9 +526,9 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
self._apply(z_list, e_list, save_z=True, save_e=False)
|
||||
panel_base.open_satellite_window(
|
||||
"geschoss_settings",
|
||||
params=geschoss,
|
||||
params=params,
|
||||
title="Zeichnungsebene: {}".format(geschoss.get("name", "")),
|
||||
size=(380, 540),
|
||||
size=(380, 580),
|
||||
on_save=on_save)
|
||||
|
||||
def _open_ebenen_settings(self, ebene, hatch_patterns):
|
||||
@@ -733,22 +762,36 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
return
|
||||
payload_z = p.get("zeichnungsebenen") or []
|
||||
payload_e = p.get("ebenen") or []
|
||||
# Hilfsfunktion: alle Codes (inkl. Children) als flat dict {code: ebene}
|
||||
def _walk_codes(lst):
|
||||
out = {}
|
||||
if not isinstance(lst, list): return out
|
||||
for x in lst:
|
||||
if not isinstance(x, dict): continue
|
||||
c = x.get("code")
|
||||
if c: out[c] = x
|
||||
kids = x.get("children")
|
||||
if isinstance(kids, list):
|
||||
out.update(_walk_codes(kids))
|
||||
return out
|
||||
# Strukturelle Aenderung pending? Wenn React-Payload IDs/Codes enthaelt
|
||||
# die noch nicht in doc.Strings sind (= User hat gerade neue Ebene
|
||||
# angelegt aber der strukturelle APPLY ist noch in der 200ms-Debounce),
|
||||
# NICHT speichern. Sonst ueberschreibt die schnellere SET_VISIBILITY
|
||||
# den geplanten APPLY-Save und die neue Ebene geht in der Race
|
||||
# verloren.
|
||||
payload_z_ids = {z.get("id") for z in payload_z if isinstance(z, dict)}
|
||||
payload_e_codes = {e.get("code") for e in payload_e if isinstance(e, dict)}
|
||||
existing_z_ids = {z.get("id") for z in z_full if isinstance(z, dict)}
|
||||
existing_e_codes = {e.get("code") for e in e_full if isinstance(e, dict)}
|
||||
payload_z_ids = {z.get("id") for z in payload_z if isinstance(z, dict)}
|
||||
payload_e_codes = set(_walk_codes(payload_e).keys())
|
||||
existing_z_ids = {z.get("id") for z in z_full if isinstance(z, dict)}
|
||||
existing_e_codes = set(_walk_codes(e_full).keys())
|
||||
has_new_structural = (
|
||||
bool(payload_z_ids - existing_z_ids - {None}) or
|
||||
bool(payload_e_codes - existing_e_codes - {None})
|
||||
)
|
||||
z_state = {z["id"]: z for z in payload_z if isinstance(z, dict) and z.get("id")}
|
||||
e_state = {e["code"]: e for e in payload_e if isinstance(e, dict) and e.get("code")}
|
||||
# e_state ist flach (Code → Ebene) ueber den ganzen Tree des Payloads,
|
||||
# damit auch Child-Visibility-Toggles ankommen.
|
||||
e_state = _walk_codes(payload_e)
|
||||
merged_z = []
|
||||
for z in z_full:
|
||||
if not isinstance(z, dict): continue
|
||||
@@ -758,23 +801,40 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
m["visible"] = s.get("visible", True)
|
||||
m["locked"] = s.get("locked", False)
|
||||
merged_z.append(m)
|
||||
merged_e = []
|
||||
for e in e_full:
|
||||
if not isinstance(e, dict): continue
|
||||
m = dict(e)
|
||||
s = e_state.get(e.get("code"))
|
||||
if s is not None:
|
||||
m["visible"] = s.get("visible", True)
|
||||
m["locked"] = s.get("locked", False)
|
||||
merged_e.append(m)
|
||||
# Merge fuer Ebenen rekursiv: jedes Element behaelt seine Position +
|
||||
# children-Struktur, nur visible/locked werden ueberschrieben falls
|
||||
# im Payload anwesend.
|
||||
def _merge_ebenen_tree(orig_list):
|
||||
out = []
|
||||
for e in orig_list:
|
||||
if not isinstance(e, dict): continue
|
||||
m = dict(e)
|
||||
s = e_state.get(e.get("code"))
|
||||
if s is not None:
|
||||
m["visible"] = s.get("visible", True)
|
||||
m["locked"] = s.get("locked", False)
|
||||
kids = e.get("children")
|
||||
if isinstance(kids, list):
|
||||
m["children"] = _merge_ebenen_tree(kids)
|
||||
out.append(m)
|
||||
return out
|
||||
merged_e = _merge_ebenen_tree(e_full)
|
||||
# Detect whether the merge actually changed any visible/locked values.
|
||||
# Wenn nicht: das ist nur der Echo-Roundtrip eines apply_layer_preset
|
||||
# (React-State == doc.Strings → kein User-Click) und wir wollen das
|
||||
# aktive Preset NICHT clearen.
|
||||
# aktive Preset NICHT clearen. Bei Ebenen rekursiv durch Children.
|
||||
def _flatten(lst):
|
||||
out = []
|
||||
for x in (lst or []):
|
||||
if not isinstance(x, dict): continue
|
||||
out.append(x)
|
||||
kids = x.get("children")
|
||||
if isinstance(kids, list):
|
||||
out.extend(_flatten(kids))
|
||||
return out
|
||||
def _vis_lock_changed(old, new):
|
||||
old_by = {x.get("id") or x.get("code"): x for x in old if isinstance(x, dict)}
|
||||
for nx in new:
|
||||
if not isinstance(nx, dict): continue
|
||||
old_by = {x.get("id") or x.get("code"): x for x in _flatten(old)}
|
||||
for nx in _flatten(new):
|
||||
key = nx.get("id") or nx.get("code")
|
||||
if key is None: continue
|
||||
ox = old_by.get(key)
|
||||
@@ -815,10 +875,12 @@ class EbenenBridge(panel_base.BaseBridge):
|
||||
bool(z.get("visible", True)),
|
||||
bool(z.get("locked", False)))
|
||||
for z in zlist if isinstance(z, dict))
|
||||
# Ebenen flat ueber Children — sonst dedupt der Cache auch nach
|
||||
# einem Child-Toggle, weil die Top-Level-Liste identisch aussieht.
|
||||
es = tuple((e.get("code"),
|
||||
bool(e.get("visible", True)),
|
||||
bool(e.get("locked", False)))
|
||||
for e in elist if isinstance(e, dict))
|
||||
for e in _flatten(elist))
|
||||
return (active_z_id, active_code, z_mode, e_mode, zs, es)
|
||||
cur_sig = _sig(merged_z, merged_e)
|
||||
if sc.sticky.get("_vis_last_sig") == cur_sig and not any_changed:
|
||||
|
||||
Reference in New Issue
Block a user