Oeffnungen-Sublayer + Sturzlinien + Referenz-Layer + Pill-Inputs + Anordnen-Pill
- Oeffnungen-Subtree (Rahmen/Glas/Tuerblatt/Sims/Pane/Schwung/Sturz) als nested Children unter WAENDE im dossier_ebenen-Tree registriert + per-Kind Material (Glas mit Transparenz) - Sturzlinien bei 1:100 Tueren mit Innen/Aussen/Beide/Keine-Dropdown - Referenzlinien-Layer (19) als eigene Ebene fuer wand_axis + oeffnung_point - Swisstopo Patch-Terrain (Brep.CreatePatch) ersetzt das falsche Loft - Pill-Style fuer alle Inputs zentral via index.css - 2x2 Anordnen-Pill in der Oberleiste (BringToFront/Forward/Backward/SendToBack via Rhinos DisplayOrder, kein Z-Offset) - Chevron-Verschiebung in Ebenen-Panel ohne dass Siblings shiften - Fix: _update_ebene_field walked nur Top-Level, nested Sublayer-Style- Changes wurden nicht persistiert - Fix: Sturz-Linetype wurde bei jedem Wand-Regen zurueckgesetzt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+559
-58
@@ -71,31 +71,38 @@ _KEY_OEFF_TUER_TYP = "dossier_oeff_tuer_typ" # "normal" | "wandoeffnu
|
|||||||
_KEY_OEFF_HINGE_SIDE = "dossier_oeff_hinge_side" # "links" | "rechts" — Bandseite (welche Tuerflueg-Seite)
|
_KEY_OEFF_HINGE_SIDE = "dossier_oeff_hinge_side" # "links" | "rechts" — Bandseite (welche Tuerflueg-Seite)
|
||||||
_KEY_OEFF_OPEN_ANGLE = "dossier_oeff_open_angle" # float Grad 0–180 (Plan-Oeffnungswinkel)
|
_KEY_OEFF_OPEN_ANGLE = "dossier_oeff_open_angle" # float Grad 0–180 (Plan-Oeffnungswinkel)
|
||||||
_KEY_OEFF_SWING_INVERT = "dossier_oeff_swing_invert" # "1"/"0" — flippt die Schwung-Richtung
|
_KEY_OEFF_SWING_INVERT = "dossier_oeff_swing_invert" # "1"/"0" — flippt die Schwung-Richtung
|
||||||
|
_KEY_OEFF_STURZ = "dossier_oeff_sturz" # "keine" | "innen" | "aussen" | "beide" — Sturzlinien-Anzeige bei 1:100 Tueren
|
||||||
|
|
||||||
_OEFF_TUER_TYPEN = ("normal", "wandoeffnung")
|
_OEFF_TUER_TYPEN = ("normal", "wandoeffnung")
|
||||||
_OEFF_HINGE_SIDES = ("links", "rechts")
|
_OEFF_HINGE_SIDES = ("links", "rechts")
|
||||||
|
_OEFF_STURZ_OPTIONS = ("keine", "innen", "aussen", "beide")
|
||||||
|
|
||||||
_OEFF_DARSTELLUNGEN = ("einfach", "standard", "detail")
|
_OEFF_DARSTELLUNGEN = ("auto", "einfach", "standard", "detail")
|
||||||
|
# 'auto' = folgt der Doc-Level-Modelldarstellung (Oberleiste). Default
|
||||||
|
# fuer neue Elemente. Explizite Werte ueberstuern den Doc-Level-Wert.
|
||||||
|
_DARSTELLUNG_DEFAULT_GLOBAL = "einfach" # Doc-Level Default = 1:100
|
||||||
|
|
||||||
|
|
||||||
def get_aktive_darstellung(doc):
|
def get_aktive_darstellung(doc):
|
||||||
"""Liest die doc-level Darstellungs-Override. Leer → per-object."""
|
"""Liest die doc-level Modelldarstellung. Default = einfach (1:100).
|
||||||
if doc is None: return ""
|
'auto' wird hier nicht akzeptiert — die Doc-Ebene hat IMMER einen
|
||||||
|
konkreten Wert, an dem sich auto-Elemente orientieren."""
|
||||||
|
if doc is None: return _DARSTELLUNG_DEFAULT_GLOBAL
|
||||||
try:
|
try:
|
||||||
v = doc.Strings.GetValue(_KEY_AKTIVE_DARSTELLUNG) or ""
|
v = doc.Strings.GetValue(_KEY_AKTIVE_DARSTELLUNG) or ""
|
||||||
except Exception:
|
except Exception:
|
||||||
v = ""
|
v = ""
|
||||||
return v if v in _OEFF_DARSTELLUNGEN else ""
|
if v in ("einfach", "standard", "detail"): return v
|
||||||
|
return _DARSTELLUNG_DEFAULT_GLOBAL
|
||||||
|
|
||||||
|
|
||||||
def set_aktive_darstellung(doc, value):
|
def set_aktive_darstellung(doc, value):
|
||||||
"""Setzt die doc-level Darstellungs-Override. Leer → clear."""
|
"""Setzt die doc-level Modelldarstellung. Akzeptiert nur konkrete
|
||||||
|
Werte (einfach/standard/detail). Leer/None/auto → Default einfach."""
|
||||||
if doc is None: return
|
if doc is None: return
|
||||||
try:
|
try:
|
||||||
if value and value in _OEFF_DARSTELLUNGEN:
|
v = value if value in ("einfach", "standard", "detail") else _DARSTELLUNG_DEFAULT_GLOBAL
|
||||||
doc.Strings.SetString(_KEY_AKTIVE_DARSTELLUNG, value)
|
doc.Strings.SetString(_KEY_AKTIVE_DARSTELLUNG, v)
|
||||||
else:
|
|
||||||
doc.Strings.Delete(_KEY_AKTIVE_DARSTELLUNG)
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] set_aktive_darstellung:", ex)
|
print("[ELEMENTE] set_aktive_darstellung:", ex)
|
||||||
|
|
||||||
@@ -414,6 +421,30 @@ _OEFF_SIMS_STYLES = {
|
|||||||
}
|
}
|
||||||
_OEFF_RAHMEN_POS_OPTIONS = ("aussen", "mid", "innen")
|
_OEFF_RAHMEN_POS_OPTIONS = ("aussen", "mid", "innen")
|
||||||
|
|
||||||
|
# Pro Oeffnungs-Piece-Kind: eigener Sublayer unter `WAENDE::Öffnungen` +
|
||||||
|
# zugehoeriges Material (Hex-Diffuse + optional Transparenz). 'name' wird
|
||||||
|
# als Layer-Name benutzt. 'suffix' wird an den WAENDE-Code +"o" angehaengt
|
||||||
|
# (z.B. wand_code="20" -> Öffnungen-Ebene "20o" -> Rahmen "20o1") damit der
|
||||||
|
# Eintrag im dossier_ebenen-Tree eine eindeutige Code-ID bekommt und im
|
||||||
|
# Ebenen-Panel erscheint. transparency: 0.0–1.0.
|
||||||
|
_OEFF_PIECE_DEFS = {
|
||||||
|
"rahmen": {"layer": "Rahmen", "suffix": "1", "color": "#c89a5a",
|
||||||
|
"transparency": 0.0, "ior": 1.0},
|
||||||
|
"glas": {"layer": "Glas", "suffix": "2", "color": "#bcd4e0",
|
||||||
|
"transparency": 0.88, "ior": 1.5},
|
||||||
|
"fluegel": {"layer": "Türblatt", "suffix": "3", "color": "#8a6440",
|
||||||
|
"transparency": 0.0, "ior": 1.0},
|
||||||
|
"sims": {"layer": "Sims", "suffix": "4", "color": "#a8a8a8",
|
||||||
|
"transparency": 0.0, "ior": 1.0},
|
||||||
|
"pane": {"layer": "Pane", "suffix": "5", "color": "#404040",
|
||||||
|
"transparency": 0.0, "ior": 1.0},
|
||||||
|
"schwung": {"layer": "Schwung", "suffix": "6", "color": "#888888",
|
||||||
|
"transparency": 0.0, "ior": 1.0},
|
||||||
|
"sturz": {"layer": "Sturz", "suffix": "7", "color": "#6a6a6a",
|
||||||
|
"transparency": 0.0, "ior": 1.0,
|
||||||
|
"linetype": "Dashed"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# --- Last-Used-Defaults (sticky, session-life) ------------------------------
|
# --- Last-Used-Defaults (sticky, session-life) ------------------------------
|
||||||
# Speichert die letzten Werte (Dicke, Referenz, Modus, Neigung), damit der
|
# Speichert die letzten Werte (Dicke, Referenz, Modus, Neigung), damit der
|
||||||
@@ -614,15 +645,180 @@ def _layer_path_volume(doc, geschoss_name):
|
|||||||
return _layer_path_axis(doc, geschoss_name)
|
return _layer_path_axis(doc, geschoss_name)
|
||||||
|
|
||||||
|
|
||||||
def _layer_path_oeff_swing(doc, geschoss_name):
|
def _layer_path_referenz(doc, geschoss_name):
|
||||||
"""Türschwung-Linien — eigener Sublayer (Code 23) damit User die
|
"""Sublayer 'Referenzlinien' (Code 19) — eigene Ebene fuer wand_axis +
|
||||||
Schwung-Bögen unabhängig von den Türen-Volumes ausblenden kann."""
|
oeffnung_point Source-Objekte. Getrennt vom Wand-Volumen-Layer (20)
|
||||||
sub = _find_ebene_sublayer_name(doc, ["schwung", "tuerschwung"],
|
damit der User die Konstruktions-Referenzen ein-/ausblenden kann ohne
|
||||||
"23", "Türschwung",
|
die Volumen-Sichtbarkeit zu verlieren. Wird automatisch im Ebenen-
|
||||||
default_color="#888888", default_lw=0.13)
|
Panel sichtbar (auto-add via _find_ebene_sublayer_name)."""
|
||||||
|
sub = _find_ebene_sublayer_name(doc, ["referenz", "referenzlinie"],
|
||||||
|
"19", "Referenzlinien",
|
||||||
|
default_color="#a0a0a0", default_lw=0.13)
|
||||||
return "{}::{}".format(geschoss_name, sub)
|
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
|
||||||
|
erscheinen die Sublayer im Dossier-Ebenen-Panel und koennen vom User
|
||||||
|
visible/locked geschaltet werden.
|
||||||
|
|
||||||
|
Returnt den WAENDE-Code (z.B. "20") oder None wenn die WAENDE-Ebene
|
||||||
|
nicht im JSON-Tree liegt. Triggert build_layers + broadcast_state
|
||||||
|
wenn etwas geaendert wurde."""
|
||||||
|
raw = doc.Strings.GetValue("dossier_ebenen")
|
||||||
|
try: ebenen = json.loads(raw) if raw else []
|
||||||
|
except Exception: ebenen = []
|
||||||
|
if not isinstance(ebenen, list): return None
|
||||||
|
wand_eb = None
|
||||||
|
for e in ebenen:
|
||||||
|
if not isinstance(e, dict): continue
|
||||||
|
nm = (e.get("name") or "").lower()
|
||||||
|
if "wand" in nm or "wände" in nm or "waende" in nm:
|
||||||
|
wand_eb = e; break
|
||||||
|
if wand_eb is None: return None
|
||||||
|
wand_code = wand_eb.get("code") or "20"
|
||||||
|
if not isinstance(wand_eb.get("children"), list):
|
||||||
|
wand_eb["children"] = []
|
||||||
|
oeff_code = wand_code + "o"
|
||||||
|
oeff_eb = next((c for c in wand_eb["children"] if isinstance(c, dict)
|
||||||
|
and c.get("code") == oeff_code), None)
|
||||||
|
changed = False
|
||||||
|
if oeff_eb is None:
|
||||||
|
oeff_eb = {
|
||||||
|
"code": oeff_code, "name": "Öffnungen",
|
||||||
|
"color": "#888888", "lw": 0.13,
|
||||||
|
"visible": True, "locked": False, "children": [],
|
||||||
|
}
|
||||||
|
wand_eb["children"].append(oeff_eb)
|
||||||
|
changed = True
|
||||||
|
if not isinstance(oeff_eb.get("children"), list):
|
||||||
|
oeff_eb["children"] = []
|
||||||
|
have_codes = {c.get("code") for c in oeff_eb["children"]
|
||||||
|
if isinstance(c, dict)}
|
||||||
|
for kind, spec in _OEFF_PIECE_DEFS.items():
|
||||||
|
ccode = oeff_code + spec["suffix"]
|
||||||
|
if ccode in have_codes: continue
|
||||||
|
oeff_eb["children"].append({
|
||||||
|
"code": ccode, "name": spec["layer"],
|
||||||
|
"color": spec["color"], "lw": 0.13,
|
||||||
|
"visible": True, "locked": False,
|
||||||
|
})
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
try:
|
||||||
|
doc.Strings.SetString("dossier_ebenen",
|
||||||
|
json.dumps(ebenen, ensure_ascii=False))
|
||||||
|
import layer_builder
|
||||||
|
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
|
||||||
|
zlist = json.loads(z_raw) if z_raw else []
|
||||||
|
if zlist: layer_builder.build_layers(doc, zlist, ebenen)
|
||||||
|
import rhinopanel
|
||||||
|
rhinopanel._broadcast_state(doc)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] _ensure_oeff_ebenen_in_doc build:", ex)
|
||||||
|
return wand_code
|
||||||
|
|
||||||
|
|
||||||
|
def _layer_path_oeff_kind(doc, geschoss_name, kind):
|
||||||
|
"""Sublayer pro Oeffnungs-Piece-Kind: `<Geschoss>::20_Wände::20o_Öffnungen::20oN_<Kind>`.
|
||||||
|
Registriert die Ebenen im dossier_ebenen-Tree (damit das Dossier-
|
||||||
|
Ebenen-Panel sie zeigt) + erstellt die Rhino-Layer-Hierarchie. Liefert
|
||||||
|
den vollen Layer-Pfad."""
|
||||||
|
spec = _OEFF_PIECE_DEFS.get(kind)
|
||||||
|
if spec is None:
|
||||||
|
return _layer_path_volume(doc, geschoss_name)
|
||||||
|
# Sicherstellen dass WAENDE in dossier_ebenen ist (auto-add via
|
||||||
|
# _find_ebene_sublayer_name) + Code extrahieren
|
||||||
|
wand_sub_name = _find_ebene_sublayer_name(doc, ["wand", "wände", "waende"],
|
||||||
|
"20", "Wände",
|
||||||
|
default_color="#0a0a0a",
|
||||||
|
default_lw=0.50)
|
||||||
|
# Oeffnungen + 6 Kind-Sublayer in dossier_ebenen registrieren
|
||||||
|
wand_code = _ensure_oeff_ebenen_in_doc(doc) or "20"
|
||||||
|
oeff_code = wand_code + "o"
|
||||||
|
kind_code = oeff_code + spec["suffix"]
|
||||||
|
# Rhino-Layer-Namen folgen dem `<code>_<name>`-Schema von layer_builder
|
||||||
|
full = "{}::{}::{}::{}".format(
|
||||||
|
geschoss_name, wand_sub_name,
|
||||||
|
"{}_{}".format(oeff_code, "Öffnungen"),
|
||||||
|
"{}_{}".format(kind_code, spec["layer"]))
|
||||||
|
layer_idx = _ensure_layer(doc, full)
|
||||||
|
# Linetype-Default (z.B. 'Dashed' fuer Sturzlinien) — NUR setzen wenn
|
||||||
|
# der Layer aktuell auf Continuous (Index 0) steht. Sonst hat der User
|
||||||
|
# bewusst eine andere Linetype gewaehlt und wir wuerden sie bei jedem
|
||||||
|
# Regen wegmaecken.
|
||||||
|
lt_name = spec.get("linetype")
|
||||||
|
if lt_name and layer_idx >= 0:
|
||||||
|
try:
|
||||||
|
layer = doc.Layers[layer_idx]
|
||||||
|
if layer.LinetypeIndex <= 0: # 0 = Continuous, <0 = unset
|
||||||
|
lt_idx = doc.Linetypes.Find(lt_name)
|
||||||
|
if lt_idx > 0:
|
||||||
|
layer.LinetypeIndex = lt_idx
|
||||||
|
doc.Layers.Modify(layer, layer_idx, True)
|
||||||
|
except Exception: pass
|
||||||
|
return full
|
||||||
|
|
||||||
|
|
||||||
|
def _layer_path_oeff_swing(doc, geschoss_name):
|
||||||
|
"""Türschwung-Linien — Sublayer 'Schwung' unter WAENDE::Öffnungen.
|
||||||
|
Eigene Ebene damit User die Schwung-Bögen unabhaengig ausblenden kann."""
|
||||||
|
return _layer_path_oeff_kind(doc, geschoss_name, "schwung")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_oeff_material(doc, kind):
|
||||||
|
"""Findet oder erstellt ein Material fuer ein Oeffnungs-Piece-Kind.
|
||||||
|
Unterstuetzt Transparenz (fuer Glas) + Index of Refraction. Cached
|
||||||
|
pro Kind ueber sc.sticky. Liefert Material-Index oder -1.
|
||||||
|
|
||||||
|
Defensiv geschrieben — bei alten/neuen Rhino-Versionen koennen einzelne
|
||||||
|
Material-Properties fehlen, wir setzen nur was geht."""
|
||||||
|
spec = _OEFF_PIECE_DEFS.get(kind)
|
||||||
|
if spec is None: return -1
|
||||||
|
cache = sc.sticky.get("_dossier_oeff_mat_cache")
|
||||||
|
if not isinstance(cache, dict):
|
||||||
|
cache = {}
|
||||||
|
sc.sticky["_dossier_oeff_mat_cache"] = cache
|
||||||
|
cached = cache.get(kind)
|
||||||
|
if cached is not None:
|
||||||
|
try:
|
||||||
|
if 0 <= cached < doc.Materials.Count:
|
||||||
|
m = doc.Materials[cached]
|
||||||
|
if m is not None and not m.IsDeleted:
|
||||||
|
return cached
|
||||||
|
except Exception: pass
|
||||||
|
del cache[kind]
|
||||||
|
try:
|
||||||
|
import System.Drawing as SD
|
||||||
|
h = spec["color"].lstrip("#")
|
||||||
|
r = int(h[0:2], 16); g = int(h[2:4], 16); b = int(h[4:6], 16)
|
||||||
|
mat = Rhino.DocObjects.Material()
|
||||||
|
mat.Name = "Dossier_Oeff_" + spec["layer"]
|
||||||
|
mat.DiffuseColor = SD.Color.FromArgb(255, r, g, b)
|
||||||
|
try: mat.Transparency = float(spec.get("transparency", 0.0))
|
||||||
|
except Exception: pass
|
||||||
|
try: mat.IndexOfRefraction = float(spec.get("ior", 1.0))
|
||||||
|
except Exception: pass
|
||||||
|
# Glas zusaetzlich: TransparentColor leicht bluestichig damit Render
|
||||||
|
# nicht komplett klar wird (sonst sieht es im Standard-Display wie
|
||||||
|
# ein Loch aus). Optional weiches Reflektions-Highlight.
|
||||||
|
if kind == "glas":
|
||||||
|
try: mat.TransparentColor = SD.Color.FromArgb(255, 220, 235, 245)
|
||||||
|
except Exception: pass
|
||||||
|
try: mat.Shine = 0.7 * mat.MaxShine
|
||||||
|
except Exception: pass
|
||||||
|
try: mat.Reflectivity = 0.05
|
||||||
|
except Exception: pass
|
||||||
|
idx = doc.Materials.Add(mat)
|
||||||
|
if idx >= 0:
|
||||||
|
cache[kind] = idx
|
||||||
|
return idx
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] _ensure_oeff_material:", ex)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def _layer_path_decke(doc, geschoss_name):
|
def _layer_path_decke(doc, geschoss_name):
|
||||||
"""Decken-Outline + Volumen — Sublayer 'DECKEN' (Code 30)."""
|
"""Decken-Outline + Volumen — Sublayer 'DECKEN' (Code 30)."""
|
||||||
sub = _find_ebene_sublayer_name(doc, ["decke"], "30", "DECKEN",
|
sub = _find_ebene_sublayer_name(doc, ["decke"], "30", "DECKEN",
|
||||||
@@ -1909,7 +2105,7 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
|||||||
oeff_aussenseite=None, oeff_tuer_rahmen=None,
|
oeff_aussenseite=None, oeff_tuer_rahmen=None,
|
||||||
oeff_rahmen_offset=None, oeff_style_id=None,
|
oeff_rahmen_offset=None, oeff_style_id=None,
|
||||||
oeff_tuer_typ=None, oeff_hinge_side=None, oeff_open_angle=None,
|
oeff_tuer_typ=None, oeff_hinge_side=None, oeff_open_angle=None,
|
||||||
oeff_swing_invert=None,
|
oeff_swing_invert=None, oeff_sturz=None,
|
||||||
geschoss_end=None, treppe_breite=None,
|
geschoss_end=None, treppe_breite=None,
|
||||||
treppe_n=None, treppe_referenz=None,
|
treppe_n=None, treppe_referenz=None,
|
||||||
treppe_modus=None, treppe_lauf_d=None, treppe_art=None,
|
treppe_modus=None, treppe_lauf_d=None, treppe_art=None,
|
||||||
@@ -2006,6 +2202,9 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
|||||||
try: obj_attrs.SetUserString(_KEY_OEFF_SWING_INVERT,
|
try: obj_attrs.SetUserString(_KEY_OEFF_SWING_INVERT,
|
||||||
"1" if bool(oeff_swing_invert) else "0")
|
"1" if bool(oeff_swing_invert) else "0")
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
if oeff_sturz is not None and oeff_sturz in _OEFF_STURZ_OPTIONS:
|
||||||
|
try: obj_attrs.SetUserString(_KEY_OEFF_STURZ, oeff_sturz)
|
||||||
|
except Exception: pass
|
||||||
# --- Treppen-Felder ---
|
# --- Treppen-Felder ---
|
||||||
if geschoss_end is not None:
|
if geschoss_end is not None:
|
||||||
obj_attrs.SetUserString(_KEY_GESCHOSS_END, geschoss_end or "")
|
obj_attrs.SetUserString(_KEY_GESCHOSS_END, geschoss_end or "")
|
||||||
@@ -2156,8 +2355,8 @@ def _read_meta(obj):
|
|||||||
ogl = (og_raw == "1") if og_raw in ("0", "1") else is_fenster
|
ogl = (og_raw == "1") if og_raw in ("0", "1") else is_fenster
|
||||||
oref = a.GetUserString(_KEY_OEFF_REFERENZ) or "mid"
|
oref = a.GetUserString(_KEY_OEFF_REFERENZ) or "mid"
|
||||||
if oref not in _OEFF_REFERENZ_OPTIONS: oref = "mid"
|
if oref not in _OEFF_REFERENZ_OPTIONS: oref = "mid"
|
||||||
odarst = a.GetUserString(_KEY_OEFF_DARSTELLUNG) or "standard"
|
odarst = a.GetUserString(_KEY_OEFF_DARSTELLUNG) or "auto"
|
||||||
if odarst not in _OEFF_DARSTELLUNGEN: odarst = "standard"
|
if odarst not in _OEFF_DARSTELLUNGEN: odarst = "auto"
|
||||||
oauss = a.GetUserString(_KEY_OEFF_AUSSENSEITE) or "rechts"
|
oauss = a.GetUserString(_KEY_OEFF_AUSSENSEITE) or "rechts"
|
||||||
if oauss not in _OEFF_AUSSENSEITEN: oauss = "rechts"
|
if oauss not in _OEFF_AUSSENSEITEN: oauss = "rechts"
|
||||||
otrah = a.GetUserString(_KEY_OEFF_TUER_RAHMEN) or "zarge"
|
otrah = a.GetUserString(_KEY_OEFF_TUER_RAHMEN) or "zarge"
|
||||||
@@ -2178,6 +2377,8 @@ def _read_meta(obj):
|
|||||||
if oangle < 0: oangle = 0.0
|
if oangle < 0: oangle = 0.0
|
||||||
elif oangle > 180: oangle = 180.0
|
elif oangle > 180: oangle = 180.0
|
||||||
oswinv = (a.GetUserString(_KEY_OEFF_SWING_INVERT) == "1")
|
oswinv = (a.GetUserString(_KEY_OEFF_SWING_INVERT) == "1")
|
||||||
|
ostz = a.GetUserString(_KEY_OEFF_STURZ) or "beide"
|
||||||
|
if ostz not in _OEFF_STURZ_OPTIONS: ostz = "beide"
|
||||||
# Treppen-Felder
|
# Treppen-Felder
|
||||||
gend = a.GetUserString(_KEY_GESCHOSS_END) or ""
|
gend = a.GetUserString(_KEY_GESCHOSS_END) or ""
|
||||||
try: tb = float(a.GetUserString(_KEY_TREPPE_BREITE) or "1.0")
|
try: tb = float(a.GetUserString(_KEY_TREPPE_BREITE) or "1.0")
|
||||||
@@ -2303,6 +2504,7 @@ def _read_meta(obj):
|
|||||||
"oeff_hinge_side": ohinge,
|
"oeff_hinge_side": ohinge,
|
||||||
"oeff_open_angle": oangle,
|
"oeff_open_angle": oangle,
|
||||||
"oeff_swing_invert": oswinv,
|
"oeff_swing_invert": oswinv,
|
||||||
|
"oeff_sturz": ostz,
|
||||||
"geschoss_end": gend,
|
"geschoss_end": gend,
|
||||||
"treppe_breite": tb,
|
"treppe_breite": tb,
|
||||||
"treppe_n": tn,
|
"treppe_n": tn,
|
||||||
@@ -2523,6 +2725,17 @@ def _resolve_rahmen_perp_range(half_d, rahmen_tiefe, rahmen_pos):
|
|||||||
return lo, hi
|
return lo, hi
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_oeff_darstellung(oeff_meta):
|
||||||
|
"""Per-Object-Setting wenn != 'auto', sonst Doc-Level-Modelldarstellung.
|
||||||
|
Ergibt immer einen konkreten Wert (einfach/standard/detail)."""
|
||||||
|
per_obj = oeff_meta.get("oeff_darstellung", "auto")
|
||||||
|
if per_obj not in _OEFF_DARSTELLUNGEN: per_obj = "auto"
|
||||||
|
if per_obj != "auto" and per_obj in ("einfach", "standard", "detail"):
|
||||||
|
return per_obj
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
return get_aktive_darstellung(doc) if doc is not None else _DARSTELLUNG_DEFAULT_GLOBAL
|
||||||
|
|
||||||
|
|
||||||
def _make_tuer_swing_curves(axis_curve, point_on_axis, wall_dicke,
|
def _make_tuer_swing_curves(axis_curve, point_on_axis, wall_dicke,
|
||||||
oeff_meta, base_z):
|
oeff_meta, base_z):
|
||||||
"""Generiert die 2D-Plan-Schwung-Linien einer Tuer:
|
"""Generiert die 2D-Plan-Schwung-Linien einer Tuer:
|
||||||
@@ -2542,7 +2755,7 @@ def _make_tuer_swing_curves(axis_curve, point_on_axis, wall_dicke,
|
|||||||
pt, tan, perp = frame
|
pt, tan, perp = frame
|
||||||
breite = float(oeff_meta.get("oeff_breite", 0.9))
|
breite = float(oeff_meta.get("oeff_breite", 0.9))
|
||||||
rahmen_b = float(oeff_meta.get("oeff_rahmen_b", 0.06))
|
rahmen_b = float(oeff_meta.get("oeff_rahmen_b", 0.06))
|
||||||
darstellung = oeff_meta.get("oeff_darstellung", "standard")
|
darstellung = _resolve_oeff_darstellung(oeff_meta)
|
||||||
aussenseite = oeff_meta.get("oeff_aussenseite", "rechts")
|
aussenseite = oeff_meta.get("oeff_aussenseite", "rechts")
|
||||||
aus_sign = +1 if aussenseite == "rechts" else -1
|
aus_sign = +1 if aussenseite == "rechts" else -1
|
||||||
hinge_side = oeff_meta.get("oeff_hinge_side", "links")
|
hinge_side = oeff_meta.get("oeff_hinge_side", "links")
|
||||||
@@ -2644,11 +2857,72 @@ def _make_tuer_swing_curves(axis_curve, point_on_axis, wall_dicke,
|
|||||||
return curves
|
return curves
|
||||||
|
|
||||||
|
|
||||||
|
def _make_tuer_sturz_curves(axis_curve, point_on_axis, wall_dicke,
|
||||||
|
oeff_meta, base_z):
|
||||||
|
"""Sturzlinien (Lintel-Projektion) bei 1:100 Tueren — zeigt die
|
||||||
|
Aussen-/Innen-Kante des Wand-Sturzes als gestrichelte Linie quer ueber
|
||||||
|
die Oeffnung. Klassische SIA-Plan-Konvention: 'Was oberhalb der
|
||||||
|
Schnitt-Ebene liegt, wird gestrichelt projiziert'.
|
||||||
|
|
||||||
|
Steuerung via oeff_meta['oeff_sturz']:
|
||||||
|
'keine' → keine Linien
|
||||||
|
'innen' → eine Linie an der Wand-Innenseite
|
||||||
|
'aussen' → eine Linie an der Wand-Aussenseite
|
||||||
|
'beide' → beide Linien (Default)
|
||||||
|
|
||||||
|
Liefert Liste von LineCurves (0, 1 oder 2 Stueck). Plan-Z = base_z
|
||||||
|
(= OKFF des Geschosses fuer die Tuer)."""
|
||||||
|
sturz_mode = oeff_meta.get("oeff_sturz", "beide")
|
||||||
|
if sturz_mode not in _OEFF_STURZ_OPTIONS or sturz_mode == "keine":
|
||||||
|
return []
|
||||||
|
frame = _oeff_axis_frame(axis_curve, point_on_axis)
|
||||||
|
if frame is None: return []
|
||||||
|
pt, tan, _perp = frame
|
||||||
|
|
||||||
|
breite = float(oeff_meta.get("oeff_breite", 1.0))
|
||||||
|
aus_sign = +1 if oeff_meta.get("oeff_aussenseite", "rechts") == "rechts" else -1
|
||||||
|
half_b = breite * 0.5
|
||||||
|
half_d = float(wall_dicke) * 0.5
|
||||||
|
z0 = float(base_z)
|
||||||
|
|
||||||
|
# Wand-Achse → 2 Perp-Offsets fuer Aussen-/Innenkante.
|
||||||
|
# perp_outside_dir = (+aus_sign) * (perp_unit_x, perp_unit_y)
|
||||||
|
perp_x = -tan.Y
|
||||||
|
perp_y = +tan.X
|
||||||
|
outside_perp = +half_d
|
||||||
|
inside_perp = -half_d
|
||||||
|
if aus_sign < 0:
|
||||||
|
outside_perp, inside_perp = inside_perp, outside_perp
|
||||||
|
|
||||||
|
# Pro Seite ein LineCurve: links nach rechts entlang tan, am jeweiligen
|
||||||
|
# perp-Offset (Aussen oder Innen) der Wand.
|
||||||
|
def _line_at(perp_off):
|
||||||
|
pl = rg.Point3d(pt.X + tan.X * (-half_b) + perp_x * perp_off,
|
||||||
|
pt.Y + tan.Y * (-half_b) + perp_y * perp_off, z0)
|
||||||
|
pr = rg.Point3d(pt.X + tan.X * (+half_b) + perp_x * perp_off,
|
||||||
|
pt.Y + tan.Y * (+half_b) + perp_y * perp_off, z0)
|
||||||
|
try: return rg.LineCurve(pl, pr)
|
||||||
|
except Exception: return None
|
||||||
|
|
||||||
|
out = []
|
||||||
|
if sturz_mode in ("aussen", "beide"):
|
||||||
|
c = _line_at(outside_perp)
|
||||||
|
if c is not None: out.append(c)
|
||||||
|
if sturz_mode in ("innen", "beide"):
|
||||||
|
c = _line_at(inside_perp)
|
||||||
|
if c is not None: out.append(c)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base_z):
|
def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base_z):
|
||||||
"""Baut die einzelnen Brep-Pieces der Oeffnung — Rahmen (single Brep
|
"""Baut die einzelnen Brep-Pieces der Oeffnung — Rahmen (single Brep
|
||||||
via Boolean-Differenz), Mittelpfosten (pro Fluegel), Glas, Sims aussen,
|
via Boolean-Differenz), Mittelpfosten (pro Fluegel), Glas, Sims aussen.
|
||||||
Sims innen. Liefert eine Liste von Breps. Caller persistiert jedes
|
Liefert eine Liste von (brep, kind)-Tupeln. Kind ist einer von
|
||||||
als eigenes 'oeffnung_volume' Object mit der gleichen Oeffnungs-ID."""
|
'rahmen' / 'glas' / 'fluegel' / 'sims' / 'pane' — wird vom Caller fuer
|
||||||
|
Layer-Routing (`_layer_path_oeff_kind`) + Material-Assignment
|
||||||
|
(`_ensure_oeff_material`) genutzt. Caller persistiert jedes als eigenes
|
||||||
|
'oeffnung_volume' Object mit der gleichen Oeffnungs-ID + dossier_oeff_piece
|
||||||
|
UserString."""
|
||||||
frame = _oeff_axis_frame(axis_curve, point_on_axis)
|
frame = _oeff_axis_frame(axis_curve, point_on_axis)
|
||||||
if frame is None: return []
|
if frame is None: return []
|
||||||
pt, tan, perp = frame
|
pt, tan, perp = frame
|
||||||
@@ -2664,14 +2938,7 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
sims_in_style = oeff_meta.get("oeff_sims_in", "ohne")
|
sims_in_style = oeff_meta.get("oeff_sims_in", "ohne")
|
||||||
has_glas = bool(oeff_meta.get("oeff_glas", False))
|
has_glas = bool(oeff_meta.get("oeff_glas", False))
|
||||||
is_tuer = (oeff_meta.get("oeff_typ") == "tuer")
|
is_tuer = (oeff_meta.get("oeff_typ") == "tuer")
|
||||||
# Doc-level Darstellungs-Override gewinnt vor per-Object-Setting —
|
darstellung = _resolve_oeff_darstellung(oeff_meta)
|
||||||
# damit Ausschnitt-Wechsel / Oberleiste-Quick-Switch alle Oeffnungen
|
|
||||||
# konsistent zwingen koennen.
|
|
||||||
_doc = Rhino.RhinoDoc.ActiveDoc
|
|
||||||
global_darst = get_aktive_darstellung(_doc) if _doc is not None else ""
|
|
||||||
darstellung = global_darst or oeff_meta.get("oeff_darstellung", "standard")
|
|
||||||
if darstellung not in _OEFF_DARSTELLUNGEN:
|
|
||||||
darstellung = "standard"
|
|
||||||
# Aussenseite: +1 = aussen ist +Plane.ZAxis (default), -1 = aussen ist
|
# Aussenseite: +1 = aussen ist +Plane.ZAxis (default), -1 = aussen ist
|
||||||
# -Plane.ZAxis (Wand "umgedreht"). Wird verwendet um Sims aus/in
|
# -Plane.ZAxis (Wand "umgedreht"). Wird verwendet um Sims aus/in
|
||||||
# konsistent zu platzieren unabhaengig von der Wand-Achsen-Richtung.
|
# konsistent zu platzieren unabhaengig von der Wand-Achsen-Richtung.
|
||||||
@@ -2723,7 +2990,11 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
# Rahmen-Mittelebene (= dort wo das Glas im Standard sitzt). Im
|
# Rahmen-Mittelebene (= dort wo das Glas im Standard sitzt). Im
|
||||||
# 2D-Plan ergibt das eine einzelne Linie quer durch die Oeffnung,
|
# 2D-Plan ergibt das eine einzelne Linie quer durch die Oeffnung,
|
||||||
# auf dem korrekten Tiefen-Offset.
|
# auf dem korrekten Tiefen-Offset.
|
||||||
|
# Bei Tueren ist diese Pane nicht gewuenscht — eine Tuere im Plan
|
||||||
|
# zeigt nur den Wand-Cutout + Schwung-Bogen, keine flache Scheibe.
|
||||||
if darstellung == "einfach":
|
if darstellung == "einfach":
|
||||||
|
if is_tuer:
|
||||||
|
return []
|
||||||
try:
|
try:
|
||||||
pane_perp = (frame_perp_lo + frame_perp_hi) * 0.5
|
pane_perp = (frame_perp_lo + frame_perp_hi) * 0.5
|
||||||
# Plane.Origin ans Wand-Achsenpunkt verschoben um pane_perp
|
# Plane.Origin ans Wand-Achsenpunkt verschoben um pane_perp
|
||||||
@@ -2739,7 +3010,7 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
rg.Interval(-half_b, +half_b),
|
rg.Interval(-half_b, +half_b),
|
||||||
rg.Interval(z_lo, z_hi))
|
rg.Interval(z_lo, z_hi))
|
||||||
brep = surf.ToBrep()
|
brep = surf.ToBrep()
|
||||||
return [brep] if brep is not None else []
|
return [(brep, "pane")] if brep is not None else []
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] einfach pane:", ex)
|
print("[ELEMENTE] einfach pane:", ex)
|
||||||
return []
|
return []
|
||||||
@@ -2768,13 +3039,14 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
diff = rg.Brep.CreateBooleanDifference(
|
diff = rg.Brep.CreateBooleanDifference(
|
||||||
[outer_box], [inner_box], 0.001)
|
[outer_box], [inner_box], 0.001)
|
||||||
if diff and len(diff) > 0:
|
if diff and len(diff) > 0:
|
||||||
pieces.append(diff[0])
|
pieces.append((diff[0], "rahmen"))
|
||||||
else:
|
else:
|
||||||
pieces.append(outer_box)
|
pieces.append((outer_box, "rahmen"))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] Rahmen BoolDiff:", ex)
|
print("[ELEMENTE] Rahmen BoolDiff:", ex)
|
||||||
|
|
||||||
# --- Mittelpfosten (Fluegel > 1): kleine Stege im inneren Bereich
|
# --- Mittelpfosten (Fluegel > 1): kleine Stege im inneren Bereich.
|
||||||
|
# Gleiche Material-Klasse wie Rahmen → 'rahmen'-Kind.
|
||||||
if fluegel > 1:
|
if fluegel > 1:
|
||||||
span = inner_r - inner_l
|
span = inner_r - inner_l
|
||||||
for i in range(1, fluegel):
|
for i in range(1, fluegel):
|
||||||
@@ -2783,7 +3055,7 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
x_hi = x_mid + rahmen_b * 0.5
|
x_hi = x_mid + rahmen_b * 0.5
|
||||||
mp = _make_oeff_box(pt, tan, x_lo, x_hi, payload_z_lo, payload_z_hi,
|
mp = _make_oeff_box(pt, tan, x_lo, x_hi, payload_z_lo, payload_z_hi,
|
||||||
frame_perp_lo, frame_perp_hi)
|
frame_perp_lo, frame_perp_hi)
|
||||||
if mp is not None: pieces.append(mp)
|
if mp is not None: pieces.append((mp, "rahmen"))
|
||||||
|
|
||||||
# --- INNERE FUELLUNG: Glas (Fenster oder verglaste Tuer) ODER
|
# --- INNERE FUELLUNG: Glas (Fenster oder verglaste Tuer) ODER
|
||||||
# Tuerblatt (massive Tuere ohne Glas). Beides als Box-Brep pro Fluegel.
|
# Tuerblatt (massive Tuere ohne Glas). Beides als Box-Brep pro Fluegel.
|
||||||
@@ -2795,7 +3067,10 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
else:
|
else:
|
||||||
fill_t = 0 # nichts (z.B. Fenster ohne Glas)
|
fill_t = 0 # nichts (z.B. Fenster ohne Glas)
|
||||||
|
|
||||||
if fill_t > 0:
|
# Kind der Fuellung: 'glas' fuer Scheiben (Fenster oder verglaste Tuer),
|
||||||
|
# 'fluegel' fuer massive Tuerblaetter.
|
||||||
|
fill_kind = "glas" if has_glas else ("fluegel" if is_tuer else None)
|
||||||
|
if fill_t > 0 and fill_kind is not None:
|
||||||
fill_mid = (frame_perp_lo + frame_perp_hi) * 0.5
|
fill_mid = (frame_perp_lo + frame_perp_hi) * 0.5
|
||||||
# DETAIL (1:20): Doppelverglasung — 2 Scheiben a 6mm, 16mm SZR
|
# DETAIL (1:20): Doppelverglasung — 2 Scheiben a 6mm, 16mm SZR
|
||||||
is_double_glas = (darstellung == "detail" and has_glas and not is_tuer)
|
is_double_glas = (darstellung == "detail" and has_glas and not is_tuer)
|
||||||
@@ -2822,12 +3097,12 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
fp = _make_oeff_box(pt, tan, fx_lo, fx_hi,
|
fp = _make_oeff_box(pt, tan, fx_lo, fx_hi,
|
||||||
payload_z_lo, payload_z_hi,
|
payload_z_lo, payload_z_hi,
|
||||||
fl, fh)
|
fl, fh)
|
||||||
if fp is not None: pieces.append(fp)
|
if fp is not None: pieces.append((fp, fill_kind))
|
||||||
else:
|
else:
|
||||||
fp = _make_oeff_box(pt, tan, inner_l, inner_r,
|
fp = _make_oeff_box(pt, tan, inner_l, inner_r,
|
||||||
payload_z_lo, payload_z_hi,
|
payload_z_lo, payload_z_hi,
|
||||||
fl, fh)
|
fl, fh)
|
||||||
if fp is not None: pieces.append(fp)
|
if fp is not None: pieces.append((fp, fill_kind))
|
||||||
|
|
||||||
# --- SIMS — nur AUSSEN. aus_sign=+1 -> sims auf +perp, =-1 -> auf
|
# --- SIMS — nur AUSSEN. aus_sign=+1 -> sims auf +perp, =-1 -> auf
|
||||||
# -perp. Drinnen nie. Sim ist gleichzeitig der visuelle Indikator
|
# -perp. Drinnen nie. Sim ist gleichzeitig der visuelle Indikator
|
||||||
@@ -2843,7 +3118,7 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
sb = _make_oeff_box(pt, tan,
|
sb = _make_oeff_box(pt, tan,
|
||||||
-half_b - s_oh, +half_b + s_oh,
|
-half_b - s_oh, +half_b + s_oh,
|
||||||
s_lo, z_lo, p_lo, p_hi)
|
s_lo, z_lo, p_lo, p_hi)
|
||||||
if sb is not None: pieces.append(sb)
|
if sb is not None: pieces.append((sb, "sims"))
|
||||||
|
|
||||||
return pieces
|
return pieces
|
||||||
|
|
||||||
@@ -2853,8 +3128,8 @@ SOURCE_TYPES = ("wand_axis", "decke_outline", "dach_outline",
|
|||||||
"stuetze_point", "traeger_axis",
|
"stuetze_point", "traeger_axis",
|
||||||
"raum_outline", "decke_aussparung_outline")
|
"raum_outline", "decke_aussparung_outline")
|
||||||
VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume",
|
VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume",
|
||||||
"oeffnung_volume", "oeffnung_swing", "treppe_volume",
|
"oeffnung_volume", "oeffnung_swing", "oeffnung_sturz",
|
||||||
"stuetze_volume", "traeger_volume",
|
"treppe_volume", "stuetze_volume", "traeger_volume",
|
||||||
"raum_stamp", "raum_fill")
|
"raum_stamp", "raum_fill")
|
||||||
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
|
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
|
||||||
# Oeffnung ihr eigenes Volumen (Rahmen + Sims + Glas) als Sub-Element.
|
# Oeffnung ihr eigenes Volumen (Rahmen + Sims + Glas) als Sub-Element.
|
||||||
@@ -4653,25 +4928,76 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
op_meta["geschoss"], meta["dicke"], "", "",
|
op_meta["geschoss"], meta["dicke"], "", "",
|
||||||
oeff_typ="tuer",
|
oeff_typ="tuer",
|
||||||
oeff_parent=op_meta.get("oeff_parent"))
|
oeff_parent=op_meta.get("oeff_parent"))
|
||||||
|
sw_attrs.SetUserString("dossier_oeff_piece", "schwung")
|
||||||
doc.Objects.AddCurve(crv, sw_attrs)
|
doc.Objects.AddCurve(crv, sw_attrs)
|
||||||
|
# Sturzlinien (Lintel-Projektion) — nur bei 1:100 Tueren mit
|
||||||
|
# explizit gesetztem oeff_sturz. Bei standard/detail uebernimmt
|
||||||
|
# der Rahmen-Brep die Sturz-Darstellung visuell.
|
||||||
|
old_st = list(_find_objects_by_wall_id(doc, op_meta["id"],
|
||||||
|
"oeffnung_sturz"))
|
||||||
|
for o, _m in old_st:
|
||||||
|
try: doc.Objects.Delete(o.Id, True)
|
||||||
|
except Exception: pass
|
||||||
|
if _resolve_oeff_darstellung(op_meta) == "einfach":
|
||||||
|
sturzes = _make_tuer_sturz_curves(geom, pt_loc, meta["dicke"],
|
||||||
|
op_meta, op_uk)
|
||||||
|
if sturzes:
|
||||||
|
st_layer_idx = _ensure_layer(doc,
|
||||||
|
_layer_path_oeff_kind(doc, geschoss_name, "sturz"))
|
||||||
|
for crv in sturzes:
|
||||||
|
st_attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
|
st_attrs.LayerIndex = st_layer_idx
|
||||||
|
_attach_meta(st_attrs, op_meta["id"], "oeffnung_sturz",
|
||||||
|
op_meta["geschoss"], meta["dicke"], "", "",
|
||||||
|
oeff_typ="tuer",
|
||||||
|
oeff_parent=op_meta.get("oeff_parent"),
|
||||||
|
oeff_sturz=op_meta.get("oeff_sturz"))
|
||||||
|
st_attrs.SetUserString("dossier_oeff_piece", "sturz")
|
||||||
|
doc.Objects.AddCurve(crv, st_attrs)
|
||||||
|
|
||||||
old_objs = list(_find_objects_by_wall_id(doc, op_meta["id"],
|
old_objs = list(_find_objects_by_wall_id(doc, op_meta["id"],
|
||||||
"oeffnung_volume"))
|
"oeffnung_volume"))
|
||||||
pieces = _make_oeffnung_pieces(geom, pt_loc, meta["dicke"],
|
pieces = _make_oeffnung_pieces(geom, pt_loc, meta["dicke"],
|
||||||
op_meta, op_uk)
|
op_meta, op_uk)
|
||||||
if len(old_objs) == len(pieces) and len(pieces) > 0:
|
# Replace-Pfad nur wenn Count UND Kind-Reihenfolge identisch
|
||||||
for (old_obj, _old_meta), pbrep in zip(old_objs, pieces):
|
# geblieben sind — andernfalls stimmt das Layer-/Material-Mapping
|
||||||
|
# nicht mehr und wir muessen sauber neu anlegen.
|
||||||
|
can_replace = (len(old_objs) == len(pieces) and len(pieces) > 0)
|
||||||
|
if can_replace:
|
||||||
|
for (old_obj, _m), (_pb, k) in zip(old_objs, pieces):
|
||||||
|
if (old_obj.Attributes.GetUserString("dossier_oeff_piece")
|
||||||
|
or "") != k:
|
||||||
|
can_replace = False; break
|
||||||
|
if can_replace:
|
||||||
|
for (old_obj, _m), (pbrep, _k) in zip(old_objs, pieces):
|
||||||
try: doc.Objects.Replace(old_obj.Id, pbrep)
|
try: doc.Objects.Replace(old_obj.Id, pbrep)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] replace oeff vol:", ex)
|
print("[ELEMENTE] replace oeff vol:", ex)
|
||||||
continue
|
continue
|
||||||
# Fallback: Anzahl hat sich geaendert → alte loeschen + neue adden.
|
# Fallback: Anzahl oder Kind hat sich geaendert → alte loeschen + neue adden.
|
||||||
for o, _m in old_objs:
|
for o, _m in old_objs:
|
||||||
try: doc.Objects.Delete(o.Id, True)
|
try: doc.Objects.Delete(o.Id, True)
|
||||||
except Exception as ex: print("[ELEMENTE] del old oeff vol:", ex)
|
except Exception as ex: print("[ELEMENTE] del old oeff vol:", ex)
|
||||||
for pbrep in pieces:
|
for (pbrep, kind) in pieces:
|
||||||
op_attrs = Rhino.DocObjects.ObjectAttributes()
|
op_attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
op_attrs.LayerIndex = op_layer
|
# Per-Kind Sublayer unter WAENDE::Öffnungen::<Kind>
|
||||||
|
kind_layer = _ensure_layer(doc,
|
||||||
|
_layer_path_oeff_kind(doc, geschoss_name, kind))
|
||||||
|
op_attrs.LayerIndex = kind_layer if kind_layer >= 0 else op_layer
|
||||||
|
# Per-Kind Material (Holz/Glas/Türblatt/...) — Glas hat
|
||||||
|
# Transparenz. Edges bleiben schwarz (siehe unten).
|
||||||
|
mat_idx = _ensure_oeff_material(doc, kind)
|
||||||
|
if mat_idx >= 0:
|
||||||
|
op_attrs.MaterialIndex = mat_idx
|
||||||
|
op_attrs.MaterialSource = (
|
||||||
|
Rhino.DocObjects.ObjectMaterialSource.MaterialFromObject)
|
||||||
|
# Edges/Wireframe immer schwarz, entkoppelt vom Layer/Material.
|
||||||
|
try:
|
||||||
|
import System.Drawing as SD
|
||||||
|
op_attrs.ColorSource = (
|
||||||
|
Rhino.DocObjects.ObjectColorSource.ColorFromObject)
|
||||||
|
op_attrs.ObjectColor = SD.Color.FromArgb(255, 0, 0, 0)
|
||||||
|
except Exception: pass
|
||||||
_attach_meta(op_attrs, op_meta["id"], "oeffnung_volume",
|
_attach_meta(op_attrs, op_meta["id"], "oeffnung_volume",
|
||||||
op_meta["geschoss"], meta["dicke"], "", "",
|
op_meta["geschoss"], meta["dicke"], "", "",
|
||||||
oeff_typ=op_meta.get("oeff_typ"),
|
oeff_typ=op_meta.get("oeff_typ"),
|
||||||
@@ -4695,12 +5021,14 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
oeff_tuer_typ=op_meta.get("oeff_tuer_typ"),
|
oeff_tuer_typ=op_meta.get("oeff_tuer_typ"),
|
||||||
oeff_hinge_side=op_meta.get("oeff_hinge_side"),
|
oeff_hinge_side=op_meta.get("oeff_hinge_side"),
|
||||||
oeff_open_angle=op_meta.get("oeff_open_angle"),
|
oeff_open_angle=op_meta.get("oeff_open_angle"),
|
||||||
oeff_swing_invert=op_meta.get("oeff_swing_invert"))
|
oeff_swing_invert=op_meta.get("oeff_swing_invert"),
|
||||||
|
oeff_sturz=op_meta.get("oeff_sturz"))
|
||||||
|
op_attrs.SetUserString("dossier_oeff_piece", kind)
|
||||||
doc.Objects.AddBrep(pbrep, op_attrs)
|
doc.Objects.AddBrep(pbrep, op_attrs)
|
||||||
|
|
||||||
# Source-Layer migrieren + Volumen-Layer-Index ermitteln
|
# Source-Layer migrieren + Volumen-Layer-Index ermitteln
|
||||||
layer = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
|
layer = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
|
||||||
src_layer = _ensure_layer(doc, _layer_path_axis(doc, geschoss_name))
|
src_layer = _ensure_layer(doc, _layer_path_referenz(doc, geschoss_name))
|
||||||
try:
|
try:
|
||||||
if src_layer >= 0 and src_obj.Attributes.LayerIndex != src_layer:
|
if src_layer >= 0 and src_obj.Attributes.LayerIndex != src_layer:
|
||||||
new_attrs = src_obj.Attributes.Duplicate()
|
new_attrs = src_obj.Attributes.Duplicate()
|
||||||
@@ -5312,6 +5640,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
"hingeSide": meta.get("oeff_hinge_side", "links"),
|
"hingeSide": meta.get("oeff_hinge_side", "links"),
|
||||||
"openAngle": meta.get("oeff_open_angle", 90.0),
|
"openAngle": meta.get("oeff_open_angle", 90.0),
|
||||||
"swingInvert": bool(meta.get("oeff_swing_invert", False)),
|
"swingInvert": bool(meta.get("oeff_swing_invert", False)),
|
||||||
|
"sturz": meta.get("oeff_sturz", "beide"),
|
||||||
})
|
})
|
||||||
elif meta["type"] == "treppe_axis":
|
elif meta["type"] == "treppe_axis":
|
||||||
gs = _geschoss_by_id(doc, meta["geschoss"])
|
gs = _geschoss_by_id(doc, meta["geschoss"])
|
||||||
@@ -5688,7 +6017,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
wall_id = "wall_" + uuid.uuid4().hex[:10]
|
wall_id = "wall_" + uuid.uuid4().hex[:10]
|
||||||
g = _geschoss_by_id(doc, geschoss_id)
|
g = _geschoss_by_id(doc, geschoss_id)
|
||||||
geschoss_name = g.get("name", "EG") if g else "EG"
|
geschoss_name = g.get("name", "EG") if g else "EG"
|
||||||
axis_layer = _ensure_layer(doc, _layer_path_axis(doc, geschoss_name))
|
axis_layer = _ensure_layer(doc, _layer_path_referenz(doc, geschoss_name))
|
||||||
axis = axis_curve.DuplicateCurve()
|
axis = axis_curve.DuplicateCurve()
|
||||||
try:
|
try:
|
||||||
z0 = axis.PointAtStart.Z
|
z0 = axis.PointAtStart.Z
|
||||||
@@ -6212,7 +6541,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
simsa_def = "standard" if is_fenster else "ohne"
|
simsa_def = "standard" if is_fenster else "ohne"
|
||||||
glas_def = is_fenster
|
glas_def = is_fenster
|
||||||
referenz_def = _last("oeff_referenz", "mid")
|
referenz_def = _last("oeff_referenz", "mid")
|
||||||
darst_def = "standard"
|
darst_def = "auto"
|
||||||
tuer_rahmen_def = "zarge"
|
tuer_rahmen_def = "zarge"
|
||||||
# Pending-Style-ID aus sticky (von Stil-Picker gesetzt). Falls noch
|
# Pending-Style-ID aus sticky (von Stil-Picker gesetzt). Falls noch
|
||||||
# kein Style gepickt, nutzen wir den zuletzt-aktiven fuer diesen typ
|
# kein Style gepickt, nutzen wir den zuletzt-aktiven fuer diesen typ
|
||||||
@@ -6359,7 +6688,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
geschoss = wall_meta["geschoss"]
|
geschoss = wall_meta["geschoss"]
|
||||||
g = _geschoss_by_id(doc, geschoss)
|
g = _geschoss_by_id(doc, geschoss)
|
||||||
geschoss_name = g.get("name", "EG") if g else "EG"
|
geschoss_name = g.get("name", "EG") if g else "EG"
|
||||||
layer = _ensure_layer(doc, _layer_path_axis(doc, geschoss_name))
|
layer = _ensure_layer(doc, _layer_path_referenz(doc, geschoss_name))
|
||||||
|
|
||||||
attrs = Rhino.DocObjects.ObjectAttributes()
|
attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
attrs.LayerIndex = layer
|
attrs.LayerIndex = layer
|
||||||
@@ -6382,7 +6711,8 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
oeff_style_id=pending_sid,
|
oeff_style_id=pending_sid,
|
||||||
oeff_tuer_typ="normal",
|
oeff_tuer_typ="normal",
|
||||||
oeff_hinge_side="links",
|
oeff_hinge_side="links",
|
||||||
oeff_open_angle=90.0)
|
oeff_open_angle=90.0,
|
||||||
|
oeff_sturz="beide")
|
||||||
# Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf
|
# Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf
|
||||||
# Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit
|
# Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit
|
||||||
# UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste
|
# UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste
|
||||||
@@ -7604,7 +7934,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
# Terrain-Daten (XYZ + Grid) holen, sobald Mesh ODER
|
# Terrain-Daten (XYZ + Grid) holen, sobald Mesh ODER
|
||||||
# Hoehenlinien gewuenscht sind — beide nutzen das Grid.
|
# Hoehenlinien gewuenscht sind — beide nutzen das Grid.
|
||||||
need_dem = any(k in kinds for k in
|
need_dem = any(k in kinds for k in
|
||||||
("terrain", "contours", "contour_tin", "contour_schicht"))
|
("terrain", "contours", "contour_tin", "contour_schicht", "contour_patch"))
|
||||||
mesh_objects = []
|
mesh_objects = []
|
||||||
merged_grid = None
|
merged_grid = None
|
||||||
if need_dem:
|
if need_dem:
|
||||||
@@ -7656,13 +7986,14 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._push_log("Mesh-Bau fehlgeschlagen: {}".format(ex))
|
self._push_log("Mesh-Bau fehlgeschlagen: {}".format(ex))
|
||||||
|
|
||||||
# Contours sind die Grundlage fuer drei moegliche Outputs:
|
# Contours sind die Grundlage fuer vier moegliche Outputs:
|
||||||
# 'contours' → flache 2D-Curves auf OKFF
|
# 'contours' → flache 2D-Curves auf OKFF
|
||||||
# 'contour_tin' → TIN-Mesh aus Contour-Vertices
|
# 'contour_tin' → TIN-Mesh aus Contour-Vertices
|
||||||
# 'contour_schicht' → Planare Flaechen pro Hoehe
|
# 'contour_schicht' → Planare Flaechen pro Hoehe
|
||||||
|
# 'contour_patch' → NURBS-Patch-Surface durch alle Konturen
|
||||||
# Wir generieren einmal die echten 3D-Curves und teilen
|
# Wir generieren einmal die echten 3D-Curves und teilen
|
||||||
# sie auf die drei Outputs auf.
|
# sie auf die Outputs auf.
|
||||||
contour_kinds = ("contours", "contour_tin", "contour_schicht")
|
contour_kinds = ("contours", "contour_tin", "contour_schicht", "contour_patch")
|
||||||
need_contours = any(k in kinds for k in contour_kinds) and merged_grid is not None
|
need_contours = any(k in kinds for k in contour_kinds) and merged_grid is not None
|
||||||
raw_contours = []
|
raw_contours = []
|
||||||
if need_contours:
|
if need_contours:
|
||||||
@@ -7762,6 +8093,25 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
len(schicht_objs)))
|
len(schicht_objs)))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self._push_log("Schichtenmodell-Fehler: {}".format(ex))
|
self._push_log("Schichtenmodell-Fehler: {}".format(ex))
|
||||||
|
|
||||||
|
# Patch-Terrain (NURBS-Surface durch alle Hoehenlinien)
|
||||||
|
if "contour_patch" in kinds and raw_contours:
|
||||||
|
try:
|
||||||
|
patch_obj = swisstopo.generate_patch_from_contours(
|
||||||
|
d, raw_contours, progress=self._push_log)
|
||||||
|
if patch_obj:
|
||||||
|
at = patch_obj.Attributes.Duplicate()
|
||||||
|
at.SetUserString("dossier_swisstopo_kind", "contour_patch")
|
||||||
|
d.Objects.ModifyAttributes(patch_obj, at, True)
|
||||||
|
if z_id:
|
||||||
|
self._move_to_sublayer(
|
||||||
|
d, [patch_obj], z_id, "80",
|
||||||
|
tag="contour_patch",
|
||||||
|
fallback_name="80_swisstopo",
|
||||||
|
fallback_color="#909090")
|
||||||
|
except Exception as ex:
|
||||||
|
self._push_log("Patch-Terrain-Fehler: {}".format(ex))
|
||||||
|
|
||||||
# Layer-Move auf aktive Geschoss/80_swisstopo Sublayer
|
# Layer-Move auf aktive Geschoss/80_swisstopo Sublayer
|
||||||
if z_id and mesh_objects:
|
if z_id and mesh_objects:
|
||||||
sub_name = _find_ebene_sublayer_name(
|
sub_name = _find_ebene_sublayer_name(
|
||||||
@@ -8833,6 +9183,8 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
o_angle = max(0.0, min(180.0, o_angle))
|
o_angle = max(0.0, min(180.0, o_angle))
|
||||||
o_swinv = bool(p.get("swingInvert",
|
o_swinv = bool(p.get("swingInvert",
|
||||||
old_meta.get("oeff_swing_invert", False)))
|
old_meta.get("oeff_swing_invert", False)))
|
||||||
|
o_sturz = p.get("sturz", old_meta.get("oeff_sturz", "beide"))
|
||||||
|
if o_sturz not in _OEFF_STURZ_OPTIONS: o_sturz = "beide"
|
||||||
if p.get("styleId") and p.get("styleId") != old_meta.get("oeff_style_id"):
|
if p.get("styleId") and p.get("styleId") != old_meta.get("oeff_style_id"):
|
||||||
# Style wurde NEU gewaehlt → seine Werte applizieren
|
# Style wurde NEU gewaehlt → seine Werte applizieren
|
||||||
styles = list_oeff_styles(doc)
|
styles = list_oeff_styles(doc)
|
||||||
@@ -8881,7 +9233,8 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
oeff_tuer_typ=o_tuer_typ,
|
oeff_tuer_typ=o_tuer_typ,
|
||||||
oeff_hinge_side=o_hinge,
|
oeff_hinge_side=o_hinge,
|
||||||
oeff_open_angle=o_angle,
|
oeff_open_angle=o_angle,
|
||||||
oeff_swing_invert=o_swinv)
|
oeff_swing_invert=o_swinv,
|
||||||
|
oeff_sturz=o_sturz)
|
||||||
axis_obj.Attributes = attrs
|
axis_obj.Attributes = attrs
|
||||||
axis_obj.CommitChanges()
|
axis_obj.CommitChanges()
|
||||||
parent_id = old_meta.get("oeff_parent", "")
|
parent_id = old_meta.get("oeff_parent", "")
|
||||||
@@ -8932,7 +9285,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
g = _geschoss_by_id(doc, geschoss)
|
g = _geschoss_by_id(doc, geschoss)
|
||||||
geschoss_name = g.get("name", "EG") if g else "EG"
|
geschoss_name = g.get("name", "EG") if g else "EG"
|
||||||
if old_meta["type"] == "wand_axis":
|
if old_meta["type"] == "wand_axis":
|
||||||
attrs.LayerIndex = _ensure_layer(doc, _layer_path_axis(doc, geschoss_name))
|
attrs.LayerIndex = _ensure_layer(doc, _layer_path_referenz(doc, geschoss_name))
|
||||||
elif old_meta["type"] == "decke_outline":
|
elif old_meta["type"] == "decke_outline":
|
||||||
attrs.LayerIndex = _ensure_layer(doc, _layer_path_decke(doc, geschoss_name))
|
attrs.LayerIndex = _ensure_layer(doc, _layer_path_decke(doc, geschoss_name))
|
||||||
elif old_meta["type"] == "dach_outline":
|
elif old_meta["type"] == "dach_outline":
|
||||||
@@ -9953,6 +10306,145 @@ def _sync_orphan_grips(doc):
|
|||||||
sc.sticky[_SELECT_BUSY] = False
|
sc.sticky[_SELECT_BUSY] = False
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_referenz_layer_once(doc):
|
||||||
|
"""One-shot pro Doc-Session: wand_axis + oeffnung_point auf den
|
||||||
|
neuen Referenzlinien-Sublayer migrieren, und alle versehentlich
|
||||||
|
versteckten Source-Objekte wieder zeigen. Sticky-Flag pro Doc-Id
|
||||||
|
damit ein Reopen sauber neu migriert.
|
||||||
|
|
||||||
|
Triggert zudem proaktiv die Registrierung der Referenzlinien-Ebene
|
||||||
|
im dossier_ebenen-Tree (auch wenn der Doc noch keine wand_axis-Objekte
|
||||||
|
hat) — dafuer wird _layer_path_referenz fuer jedes existierende
|
||||||
|
Geschoss aufgerufen, was via _find_ebene_sublayer_name den Auto-Add
|
||||||
|
+ broadcast_state ausloest."""
|
||||||
|
if doc is None: return
|
||||||
|
# Sticky-Version bumped: vorherige Versionen liefen ohne proaktive
|
||||||
|
# Ebenen-Registrierung — wenn die alten Keys gesetzt sind, wuerde die
|
||||||
|
# neue Logik nie greifen. v3 = aktuelle Implementierung.
|
||||||
|
try: key = "_dossier_referenz_migration_v3_" + str(doc.RuntimeSerialNumber)
|
||||||
|
except Exception: key = "_dossier_referenz_migration_v3_default"
|
||||||
|
if sc.sticky.get(key): return
|
||||||
|
sc.sticky[key] = True
|
||||||
|
n_moved = 0
|
||||||
|
n_shown = 0
|
||||||
|
n_geschosse = 0
|
||||||
|
by_geschoss = {}
|
||||||
|
try:
|
||||||
|
# Proaktive Registrierung: fuer jedes Geschoss die Referenzlinien-
|
||||||
|
# Layer anlegen. Damit erscheint die Ebene im Panel auch wenn noch
|
||||||
|
# keine Achsen migriert werden muessen. _layer_path_referenz ruft
|
||||||
|
# intern _find_ebene_sublayer_name, das die Referenzlinien-Ebene
|
||||||
|
# in dossier_ebenen einfuegt + broadcast_state ausloest.
|
||||||
|
geschosse = _load_geschosse(doc)
|
||||||
|
for g in geschosse:
|
||||||
|
if not isinstance(g, dict): continue
|
||||||
|
g_id = g.get("id")
|
||||||
|
g_name = g.get("name")
|
||||||
|
# Nur echte Geschosse (mit id) — nicht generische Ebenen-Eintraege
|
||||||
|
# die _load_geschosse als Fallback liefert wenn dossier_zeichnungs-
|
||||||
|
# ebenen leer ist.
|
||||||
|
if not g_id or not g_name: continue
|
||||||
|
try:
|
||||||
|
ref_idx = _ensure_layer(doc,
|
||||||
|
_layer_path_referenz(doc, g_name))
|
||||||
|
if ref_idx >= 0:
|
||||||
|
by_geschoss[g_name] = ref_idx
|
||||||
|
n_geschosse += 1
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] referenz-ebene fuer {}: {}".format(g_name, ex))
|
||||||
|
for obj in list(doc.Objects):
|
||||||
|
try:
|
||||||
|
meta = _read_meta(obj)
|
||||||
|
if not meta: continue
|
||||||
|
t = meta.get("type")
|
||||||
|
if t not in ("wand_axis", "oeffnung_point"): continue
|
||||||
|
# Unhide
|
||||||
|
if obj.IsHidden:
|
||||||
|
if doc.Objects.Show(obj.Id, True): n_shown += 1
|
||||||
|
# Layer-Migration auf Referenzlinien
|
||||||
|
g_id = meta.get("geschoss")
|
||||||
|
if not g_id: continue
|
||||||
|
g = _geschoss_by_id(doc, g_id)
|
||||||
|
if not g: continue
|
||||||
|
g_name = g.get("name") or "EG"
|
||||||
|
ref_idx = by_geschoss.get(g_name)
|
||||||
|
if ref_idx is None:
|
||||||
|
ref_idx = _ensure_layer(doc,
|
||||||
|
_layer_path_referenz(doc, g_name))
|
||||||
|
by_geschoss[g_name] = ref_idx
|
||||||
|
if ref_idx < 0: continue
|
||||||
|
if obj.Attributes.LayerIndex != ref_idx:
|
||||||
|
a = obj.Attributes.Duplicate()
|
||||||
|
a.LayerIndex = ref_idx
|
||||||
|
if doc.Objects.ModifyAttributes(obj, a, True):
|
||||||
|
n_moved += 1
|
||||||
|
except Exception: pass
|
||||||
|
print("[ELEMENTE] Referenz-Migration (v3): {} Geschoss(e) registriert, "
|
||||||
|
"{} Objekte bewegt, {} eingeblendet".format(
|
||||||
|
n_geschosse, n_moved, n_shown))
|
||||||
|
# Finaler Broadcast — falls _find_ebene_sublayer_name den Eintrag
|
||||||
|
# nicht neu angelegt hat (z.B. weil er schon existiert) wird das
|
||||||
|
# Panel sonst nicht neu gerendert.
|
||||||
|
try:
|
||||||
|
import rhinopanel
|
||||||
|
rhinopanel._broadcast_state(doc)
|
||||||
|
except Exception: pass
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] _migrate_referenz_layer_once:", ex)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_oeff_ebenen_once(doc):
|
||||||
|
"""Idle-Tick One-Shot: registriert Oeffnungen-Subtree (Öffnungen +
|
||||||
|
6 Piece-Kinder) in dossier_ebenen + migriert existierende
|
||||||
|
oeffnung_volume / oeffnung_swing Objekte auf die neuen code-
|
||||||
|
praefigierten Sublayer. Damit auch User die schon vorher Tueren/
|
||||||
|
Fenster gezeichnet haben die Layer im Panel sehen + die alten
|
||||||
|
plain-Namen-Layer leeren wir damit der Layer-Manager sauber bleibt."""
|
||||||
|
if doc is None: return
|
||||||
|
try: key = "_dossier_oeff_subtree_migration_" + str(doc.RuntimeSerialNumber)
|
||||||
|
except Exception: key = "_dossier_oeff_subtree_migration_default"
|
||||||
|
if sc.sticky.get(key): return
|
||||||
|
sc.sticky[key] = True
|
||||||
|
try:
|
||||||
|
# 1) Subtree in dossier_ebenen registrieren — feuert build_layers
|
||||||
|
# + broadcast_state intern
|
||||||
|
_ensure_oeff_ebenen_in_doc(doc)
|
||||||
|
# 2) Existierende Objekte auf die neuen Layer ziehen
|
||||||
|
n_moved = 0
|
||||||
|
by_target = {} # (geschoss_name, kind) -> layer_idx
|
||||||
|
for obj in list(doc.Objects):
|
||||||
|
try:
|
||||||
|
meta = _read_meta(obj)
|
||||||
|
if not meta: continue
|
||||||
|
t = meta.get("type")
|
||||||
|
if t not in ("oeffnung_volume", "oeffnung_swing"): continue
|
||||||
|
kind = obj.Attributes.GetUserString("dossier_oeff_piece")
|
||||||
|
if not kind: continue
|
||||||
|
if kind not in _OEFF_PIECE_DEFS: continue
|
||||||
|
g_id = meta.get("geschoss")
|
||||||
|
if not g_id: continue
|
||||||
|
g = _geschoss_by_id(doc, g_id)
|
||||||
|
if not g: continue
|
||||||
|
g_name = g.get("name") or "EG"
|
||||||
|
cache_k = (g_name, kind)
|
||||||
|
target_idx = by_target.get(cache_k)
|
||||||
|
if target_idx is None:
|
||||||
|
target_idx = _ensure_layer(doc,
|
||||||
|
_layer_path_oeff_kind(doc, g_name, kind))
|
||||||
|
by_target[cache_k] = target_idx
|
||||||
|
if target_idx < 0: continue
|
||||||
|
if obj.Attributes.LayerIndex != target_idx:
|
||||||
|
a = obj.Attributes.Duplicate()
|
||||||
|
a.LayerIndex = target_idx
|
||||||
|
if doc.Objects.ModifyAttributes(obj, a, True):
|
||||||
|
n_moved += 1
|
||||||
|
except Exception: pass
|
||||||
|
if n_moved:
|
||||||
|
print("[ELEMENTE] Oeffnungen-Subtree-Migration: {} Objekte auf neue Sublayer".format(n_moved))
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] _ensure_oeff_ebenen_once:", ex)
|
||||||
|
|
||||||
|
|
||||||
def _on_idle_selection(sender, e):
|
def _on_idle_selection(sender, e):
|
||||||
"""Pollt periodisch die Selektion + verarbeitet Pending-Regenerate-Queue.
|
"""Pollt periodisch die Selektion + verarbeitet Pending-Regenerate-Queue.
|
||||||
|
|
||||||
@@ -9974,6 +10466,15 @@ def _on_idle_selection(sender, e):
|
|||||||
doc = Rhino.RhinoDoc.ActiveDoc
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
if doc is None: return
|
if doc is None: return
|
||||||
|
|
||||||
|
# One-shot Migration: alte Source-Curves (wand_axis, oeffnung_point)
|
||||||
|
# auf den neuen Referenzlinien-Layer schieben + alle versehentlich
|
||||||
|
# versteckten wieder zeigen (Cleanup nach der abgebrochenen
|
||||||
|
# Hide-on-Select-Implementation).
|
||||||
|
_migrate_referenz_layer_once(doc)
|
||||||
|
# Oeffnungen-Tree in dossier_ebenen anlegen falls noch nicht vorhanden
|
||||||
|
# (sonst erscheinen die neuen Sublayer nicht im Ebenen-Panel).
|
||||||
|
_ensure_oeff_ebenen_once(doc)
|
||||||
|
|
||||||
# 1) Pending Regenerations abarbeiten — debounct (50 ms Ruhe).
|
# 1) Pending Regenerations abarbeiten — debounct (50 ms Ruhe).
|
||||||
# Kein EnableDrawing-Suspend mehr (das hat User-Feedback langsamer
|
# Kein EnableDrawing-Suspend mehr (das hat User-Feedback langsamer
|
||||||
# gemacht und konnte das Volumen "verschwinden" lassen wenn der
|
# gemacht und konnte das Volumen "verschwinden" lassen wenn der
|
||||||
|
|||||||
@@ -1087,6 +1087,32 @@ class OberleisteBridge(panel_base.BaseBridge):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[OBERLEISTE] open layer-combinations:", ex)
|
print("[OBERLEISTE] open layer-combinations:", ex)
|
||||||
|
|
||||||
|
# --- Anordnen (DisplayOrder Z-Stack) ----------------------------
|
||||||
|
# Nutzt Rhinos native _BringToFront / _BringForward / _SendBackward
|
||||||
|
# / _SendToBack. Diese setzen Attributes.DisplayOrder — keine
|
||||||
|
# Geometrie-Aenderung, kein Z-Offset. Selection-Check verhindert
|
||||||
|
# nervigen "Select objects"-Prompt wenn der User den Button leer
|
||||||
|
# drueckt.
|
||||||
|
elif t == "ARRANGE":
|
||||||
|
cmd = {
|
||||||
|
"front": "_BringToFront",
|
||||||
|
"forward": "_BringForward",
|
||||||
|
"backward": "_SendBackward",
|
||||||
|
"back": "_SendToBack",
|
||||||
|
}.get(p.get("dir"))
|
||||||
|
if cmd:
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is not None:
|
||||||
|
try:
|
||||||
|
sel = list(doc.Objects.GetSelectedObjects(False, False))
|
||||||
|
except Exception: sel = []
|
||||||
|
if sel:
|
||||||
|
try:
|
||||||
|
Rhino.RhinoApp.SendKeystrokes("\x1b", False)
|
||||||
|
Rhino.RhinoApp.RunScript(cmd, False)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[OBERLEISTE] arrange {}: {}".format(cmd, ex))
|
||||||
|
|
||||||
# --- Command-Line Integration -----------------------------------
|
# --- Command-Line Integration -----------------------------------
|
||||||
elif t == "RUN_COMMAND":
|
elif t == "RUN_COMMAND":
|
||||||
cmd = (p.get("cmd") or "").strip()
|
cmd = (p.get("cmd") or "").strip()
|
||||||
|
|||||||
+14
-2
@@ -1148,10 +1148,22 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
ebenen = json.loads(raw)
|
ebenen = json.loads(raw)
|
||||||
for e in ebenen:
|
# Rekursive Suche — Sub-Ebenen (z.B. WAENDE→Öffnungen→Sturz mit
|
||||||
|
# Code 20o7) liegen mehrere Ebenen tief. Frueher nur Top-Level
|
||||||
|
# iteriert → Style-Changes an nested Sublayer wurden nicht
|
||||||
|
# persistiert und kamen beim naechsten broadcast als alte Werte
|
||||||
|
# zurueck.
|
||||||
|
def _set_in_tree(lst):
|
||||||
|
for e in lst:
|
||||||
|
if not isinstance(e, dict): continue
|
||||||
if e.get("code") == code:
|
if e.get("code") == code:
|
||||||
e[field] = value
|
e[field] = value
|
||||||
break
|
return True
|
||||||
|
kids = e.get("children")
|
||||||
|
if isinstance(kids, list) and _set_in_tree(kids):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
_set_in_tree(ebenen)
|
||||||
doc.Strings.SetString("dossier_ebenen", json.dumps(ebenen, ensure_ascii=False))
|
doc.Strings.SetString("dossier_ebenen", json.dumps(ebenen, ensure_ascii=False))
|
||||||
_broadcast_state(doc)
|
_broadcast_state(doc)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|||||||
@@ -844,6 +844,71 @@ def generate_schichtenmodell(doc, contour_curves, progress=None):
|
|||||||
return created
|
return created
|
||||||
|
|
||||||
|
|
||||||
|
def generate_patch_from_contours(doc, contour_curves, progress=None):
|
||||||
|
"""Erzeugt eine NURBS-Patch-Flaeche durch alle Hoehenlinien — der
|
||||||
|
kanonische Rhino-Workflow fuer Terrain aus Konturen (Brep.CreatePatch
|
||||||
|
fittet eine Surface durch Curves + Points). Loft funktioniert hier
|
||||||
|
nicht, weil benachbarte Konturen unterschiedliche Topologie haben
|
||||||
|
(Inseln, Halbinseln, geschlossen vs. offen am Rand).
|
||||||
|
|
||||||
|
UV-Spans werden aus dem Bounding-Box-Aspect-Ratio abgeleitet, so
|
||||||
|
dass ein rechteckiger Site mehr Spans in der laengeren Richtung
|
||||||
|
bekommt.
|
||||||
|
|
||||||
|
Liefert das erstellte RhinoObject oder None."""
|
||||||
|
import System
|
||||||
|
if not contour_curves: return None
|
||||||
|
valid = [c for c in contour_curves if c is not None]
|
||||||
|
if len(valid) < 2:
|
||||||
|
if progress: progress("Patch braucht mindestens 2 Hoehenlinien")
|
||||||
|
return None
|
||||||
|
bb = rg.BoundingBox.Unset
|
||||||
|
for c in valid:
|
||||||
|
bb_c = c.GetBoundingBox(True)
|
||||||
|
if not bb_c.IsValid: continue
|
||||||
|
if not bb.IsValid:
|
||||||
|
bb = bb_c
|
||||||
|
else:
|
||||||
|
bb.Union(bb_c)
|
||||||
|
if not bb.IsValid:
|
||||||
|
if progress: progress("Patch: ungueltige Bounding-Box")
|
||||||
|
return None
|
||||||
|
dx = bb.Max.X - bb.Min.X
|
||||||
|
dy = bb.Max.Y - bb.Min.Y
|
||||||
|
base_span = 40
|
||||||
|
if dx >= dy and dy > 1e-6:
|
||||||
|
aspect = dx / dy
|
||||||
|
u_spans = int(round(base_span * math.sqrt(aspect)))
|
||||||
|
v_spans = base_span
|
||||||
|
elif dx > 1e-6:
|
||||||
|
aspect = dy / dx
|
||||||
|
u_spans = base_span
|
||||||
|
v_spans = int(round(base_span * math.sqrt(aspect)))
|
||||||
|
else:
|
||||||
|
u_spans = v_spans = base_span
|
||||||
|
u_spans = max(8, min(200, u_spans))
|
||||||
|
v_spans = max(8, min(200, v_spans))
|
||||||
|
if progress:
|
||||||
|
progress("Patch aus {} Hoehenlinien ({}x{} UV-Spans)...".format(
|
||||||
|
len(valid), u_spans, v_spans))
|
||||||
|
geom_list = System.Collections.Generic.List[rg.GeometryBase]()
|
||||||
|
for c in valid: geom_list.Add(c)
|
||||||
|
try:
|
||||||
|
brep = rg.Brep.CreatePatch(
|
||||||
|
geom_list, u_spans, v_spans, doc.ModelAbsoluteTolerance)
|
||||||
|
if brep is None:
|
||||||
|
if progress: progress("Patch fehlgeschlagen (None zurueck)")
|
||||||
|
return None
|
||||||
|
gid = doc.Objects.AddBrep(brep)
|
||||||
|
if gid and gid != System.Guid.Empty:
|
||||||
|
obj = doc.Objects.Find(gid)
|
||||||
|
if progress: progress("→ Patch-Terrain erzeugt")
|
||||||
|
return obj
|
||||||
|
except Exception as ex:
|
||||||
|
if progress: progress("Patch-Fehler: {}".format(ex))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_contour_curves(grid, shift_lv95, m_to_unit, interval=2.0,
|
def generate_contour_curves(grid, shift_lv95, m_to_unit, interval=2.0,
|
||||||
progress=None):
|
progress=None):
|
||||||
"""Generiert Hoehenlinien (Contour-Curves) aus dem Terrain-Grid via
|
"""Generiert Hoehenlinien (Contour-Curves) aus dem Terrain-Grid via
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ export default function AusschnittSettingsApp() {
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="DARSTELLUNG"
|
<Field label="DARSTELLUNG"
|
||||||
hint="SIA-400 Detaillierungsgrad — leer = per-Element-Setting respektieren">
|
hint="SIA-400 Detaillierungsgrad fuer diesen Ausschnitt — leer = beim Restore nicht aendern">
|
||||||
<select
|
<select
|
||||||
value={snap.darstellung || ''}
|
value={snap.darstellung || ''}
|
||||||
onChange={(ev) => set({ darstellung: ev.target.value })}
|
onChange={(ev) => set({ darstellung: ev.target.value })}
|
||||||
style={{ flex: 1, fontSize: 11, minWidth: 0 }}
|
style={{ flex: 1, fontSize: 11, minWidth: 0 }}
|
||||||
>
|
>
|
||||||
<option value="">— per Element —</option>
|
<option value="">— nicht aendern —</option>
|
||||||
<option value="einfach">Einfach (1:100)</option>
|
<option value="einfach">Einfach (1:100)</option>
|
||||||
<option value="standard">Standard (1:50)</option>
|
<option value="standard">Standard (1:50)</option>
|
||||||
<option value="detail">Detail (1:20)</option>
|
<option value="detail">Detail (1:20)</option>
|
||||||
|
|||||||
+10
-3
@@ -22,6 +22,8 @@ function fmtNum(v) {
|
|||||||
|
|
||||||
// Input-Komponente: zeigt formatierten Wert, sendet onCommit bei Enter/Blur.
|
// Input-Komponente: zeigt formatierten Wert, sendet onCommit bei Enter/Blur.
|
||||||
// Verhindert Update waehrend des Tippens, damit der Cursor nicht springt.
|
// Verhindert Update waehrend des Tippens, damit der Cursor nicht springt.
|
||||||
|
// Pill-Chrome (Border, Radius, bg-input) kommt aus dem globalen CSS —
|
||||||
|
// hier nur der Flex-Container fuer Input + optionalen Suffix.
|
||||||
function NumInput({ value, onCommit, disabled, suffix, width }) {
|
function NumInput({ value, onCommit, disabled, suffix, width }) {
|
||||||
const [text, setText] = useState(fmtNum(value))
|
const [text, setText] = useState(fmtNum(value))
|
||||||
const [focused, setFocused] = useState(false)
|
const [focused, setFocused] = useState(false)
|
||||||
@@ -32,7 +34,8 @@ function NumInput({ value, onCommit, disabled, suffix, width }) {
|
|||||||
else setText(fmtNum(value)) // ungueltig → zurueck auf alten Wert
|
else setText(fmtNum(value)) // ungueltig → zurueck auf alten Wert
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flex: width ? 0 : 1, width }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4,
|
||||||
|
flex: width ? 0 : 1, width }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={text}
|
value={text}
|
||||||
@@ -44,9 +47,13 @@ function NumInput({ value, onCommit, disabled, suffix, width }) {
|
|||||||
if (e.key === 'Enter') { e.target.blur() }
|
if (e.key === 'Enter') { e.target.blur() }
|
||||||
else if (e.key === 'Escape') { setText(fmtNum(value)); e.target.blur() }
|
else if (e.key === 'Escape') { setText(fmtNum(value)); e.target.blur() }
|
||||||
}}
|
}}
|
||||||
style={{ flex: 1, width: '100%', fontFamily: 'DM Mono, monospace', fontSize: 11, textAlign: 'right' }}
|
style={{ flex: 1, width: '100%', textAlign: 'right' }}
|
||||||
/>
|
/>
|
||||||
{suffix && <span style={{ fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 }}>{suffix}</span>}
|
{suffix && (
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 }}>
|
||||||
|
{suffix}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -1777,9 +1777,10 @@ function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
|
|||||||
</span>
|
</span>
|
||||||
<div style={{ flex: 1, display: 'flex' }}>
|
<div style={{ flex: 1, display: 'flex' }}>
|
||||||
<BarCombo
|
<BarCombo
|
||||||
value={oeff.darstellung || 'standard'}
|
value={oeff.darstellung || 'auto'}
|
||||||
onChange={(v) => onUpdate({ darstellung: v })}
|
onChange={(v) => onUpdate({ darstellung: v })}
|
||||||
title="Detaillierungsgrad — beeinflusst die generierte Geometrie">
|
title="Detaillierungsgrad — Auto folgt der Modelldarstellung in der Topbar">
|
||||||
|
<option value="auto">Nach Modelldarstellung</option>
|
||||||
<option value="einfach">Einfach (1:100)</option>
|
<option value="einfach">Einfach (1:100)</option>
|
||||||
<option value="standard">Standard (1:50)</option>
|
<option value="standard">Standard (1:50)</option>
|
||||||
<option value="detail">Detail (1:20)</option>
|
<option value="detail">Detail (1:20)</option>
|
||||||
@@ -1874,6 +1875,24 @@ function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
|
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
|
||||||
|
title="Sturzlinien-Anzeige bei 1:100 (gestrichelt). Aussen = Linie an Wand-Aussenseite, Innen = Wand-Innenseite, Beide = beide Linien">
|
||||||
|
Sturz
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={oeff.sturz || 'beide'}
|
||||||
|
onChange={(e) => onUpdate({ sturz: e.target.value })}
|
||||||
|
style={{ flex: 1, fontSize: 11 }}>
|
||||||
|
<option value="keine">Keine</option>
|
||||||
|
<option value="innen">Innen</option>
|
||||||
|
<option value="aussen">Aussen</option>
|
||||||
|
<option value="beide">Beide</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
|
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Breite</span>
|
||||||
<input type="text" value={breite}
|
<input type="text" value={breite}
|
||||||
|
|||||||
+62
-3
@@ -15,6 +15,7 @@ import {
|
|||||||
openAbout, createText, setTextSettings,
|
openAbout, createText, setTextSettings,
|
||||||
applyTextStyle, saveTextStyle, deleteTextStyle,
|
applyTextStyle, saveTextStyle, deleteTextStyle,
|
||||||
setDarstellung,
|
setDarstellung,
|
||||||
|
arrangeSelection,
|
||||||
} from './lib/rhinoBridge'
|
} from './lib/rhinoBridge'
|
||||||
|
|
||||||
const PRESETS = [
|
const PRESETS = [
|
||||||
@@ -403,12 +404,11 @@ export default function OberleisteApp() {
|
|||||||
{/* Reihe 1, Spalte 2: Modelldarstellung (SIA-400 LoD) */}
|
{/* Reihe 1, Spalte 2: Modelldarstellung (SIA-400 LoD) */}
|
||||||
<BarCombo
|
<BarCombo
|
||||||
icon="tune"
|
icon="tune"
|
||||||
value={state.aktiveDarstellung || ''}
|
value={state.aktiveDarstellung || 'einfach'}
|
||||||
onChange={(v) => setDarstellung(v)}
|
onChange={(v) => setDarstellung(v)}
|
||||||
title="Darstellungs-Override fuer Fenster/Tueren (SIA-400 LoD)"
|
title="Modelldarstellung — Default fuer Fenster/Tueren auf 'Auto'. Einzelobjekt-Override im Properties-Panel."
|
||||||
width={PRESET_W}
|
width={PRESET_W}
|
||||||
>
|
>
|
||||||
<option value="">— per Element —</option>
|
|
||||||
<option value="einfach">Einfach (1:100)</option>
|
<option value="einfach">Einfach (1:100)</option>
|
||||||
<option value="standard">Standard (1:50)</option>
|
<option value="standard">Standard (1:50)</option>
|
||||||
<option value="detail">Detail (1:20)</option>
|
<option value="detail">Detail (1:20)</option>
|
||||||
@@ -631,6 +631,65 @@ export default function OberleisteApp() {
|
|||||||
|
|
||||||
<div style={sep} />
|
<div style={sep} />
|
||||||
|
|
||||||
|
{/* ====== ANORDNEN (2D-Z-Stack via Rhino-DisplayOrder) ======
|
||||||
|
2x2-Grid (quadratisch): oben Aufwaerts-Aktionen, unten Abwaerts.
|
||||||
|
Reihe 1: Vorderste | 1 hoch (vertical_align_top, expand_less)
|
||||||
|
Reihe 2: 1 runter | Hinterste (expand_more, vertical_align_bottom)
|
||||||
|
Selection-Check + Rhino-DisplayOrder im Backend, keine Z-Offsets. */}
|
||||||
|
{(() => {
|
||||||
|
const CELL = 26 // quadratisch: 2 * CELL Breite, ~2 * BAR_H Hoehe
|
||||||
|
const Btn = ({ icon, dir, title, isFirst }) => (
|
||||||
|
<button onClick={() => arrangeSelection(dir)} title={title}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
|
e.currentTarget.style.color = 'var(--accent-light)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
|
e.currentTarget.style.color = 'var(--text-primary)'
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: BAR_H, minHeight: BAR_H, maxHeight: BAR_H, width: CELL,
|
||||||
|
background: 'var(--bg-input)', color: 'var(--text-primary)',
|
||||||
|
border: 'none',
|
||||||
|
borderLeft: isFirst ? 'none' : '1px solid var(--border)',
|
||||||
|
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
cursor: 'pointer', padding: 0, flexShrink: 0,
|
||||||
|
appearance: 'none', WebkitAppearance: 'none',
|
||||||
|
lineHeight: 1, boxSizing: 'border-box',
|
||||||
|
transition: 'background 0.15s, color 0.15s',
|
||||||
|
}}>
|
||||||
|
<Icon name={icon} size={11} />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
const rowStyle = {
|
||||||
|
display: 'inline-flex', width: CELL * 2,
|
||||||
|
height: BAR_H + 2, boxSizing: 'border-box',
|
||||||
|
border: '1px solid var(--border)', borderRadius: 999,
|
||||||
|
overflow: 'hidden', flexShrink: 0,
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', flexDirection: 'column', gap: 4, flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
<div style={rowStyle}>
|
||||||
|
<Btn icon="vertical_align_top" dir="front" isFirst
|
||||||
|
title="In den Vordergrund (Bring to Front)" />
|
||||||
|
<Btn icon="expand_less" dir="forward"
|
||||||
|
title="Eine Stufe hoch (Bring Forward)" />
|
||||||
|
</div>
|
||||||
|
<div style={rowStyle}>
|
||||||
|
<Btn icon="expand_more" dir="backward" isFirst
|
||||||
|
title="Eine Stufe runter (Send Backward)" />
|
||||||
|
<Btn icon="vertical_align_bottom" dir="back"
|
||||||
|
title="In den Hintergrund (Send to Back)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
|
||||||
|
<div style={sep} />
|
||||||
|
|
||||||
{/* ====== TEXT-Block (Vectorworks-Stil) ======
|
{/* ====== TEXT-Block (Vectorworks-Stil) ======
|
||||||
Reihe 1: Style ▼ | Font ▼ | Size ▼
|
Reihe 1: Style ▼ | Font ▼ | Size ▼
|
||||||
Reihe 2: [B][I][U] | [L][C][R] | [+]
|
Reihe 2: [B][I][U] | [L][C][R] | [+]
|
||||||
|
|||||||
+13
-2
@@ -64,6 +64,7 @@ export default function SwisstopoApp() {
|
|||||||
const [getContours, setGetContours] = useState(false)
|
const [getContours, setGetContours] = useState(false)
|
||||||
const [getContourTin,setGetContourTin]= useState(false)
|
const [getContourTin,setGetContourTin]= useState(false)
|
||||||
const [getContourSchicht, setGetContourSchicht] = useState(false)
|
const [getContourSchicht, setGetContourSchicht] = useState(false)
|
||||||
|
const [getContourPatch, setGetContourPatch] = useState(false)
|
||||||
const [contourInt, setContourInt] = useState('2.0')
|
const [contourInt, setContourInt] = useState('2.0')
|
||||||
// TLM3D deaktiviert: swisstopo liefert nur GDB/SHP/GPKG — kein DXF.
|
// TLM3D deaktiviert: swisstopo liefert nur GDB/SHP/GPKG — kein DXF.
|
||||||
// Rhino kann das nicht nativ importieren; OSM-Importer ist die Alternative
|
// Rhino kann das nicht nativ importieren; OSM-Importer ist die Alternative
|
||||||
@@ -134,7 +135,7 @@ export default function SwisstopoApp() {
|
|||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
if (!center) { addLog('Bitte zuerst einen Standort wählen'); return }
|
if (!center) { addLog('Bitte zuerst einen Standort wählen'); return }
|
||||||
if (!getBuild && !getTerrain && !getContours && !getContourTin && !getContourSchicht && !getTlm) {
|
if (!getBuild && !getTerrain && !getContours && !getContourTin && !getContourSchicht && !getContourPatch && !getTlm) {
|
||||||
addLog('Mindestens eine Datenquelle wählen'); return
|
addLog('Mindestens eine Datenquelle wählen'); return
|
||||||
}
|
}
|
||||||
setLogs([])
|
setLogs([])
|
||||||
@@ -147,6 +148,7 @@ export default function SwisstopoApp() {
|
|||||||
if (getContours) kinds.push('contours')
|
if (getContours) kinds.push('contours')
|
||||||
if (getContourTin) kinds.push('contour_tin')
|
if (getContourTin) kinds.push('contour_tin')
|
||||||
if (getContourSchicht)kinds.push('contour_schicht')
|
if (getContourSchicht)kinds.push('contour_schicht')
|
||||||
|
if (getContourPatch) kinds.push('contour_patch')
|
||||||
if (getTlm) kinds.push('tlm')
|
if (getTlm) kinds.push('tlm')
|
||||||
const tlmList = Object.entries(tlmKinds).filter(([, v]) => v).map(([k]) => k)
|
const tlmList = Object.entries(tlmKinds).filter(([, v]) => v).map(([k]) => k)
|
||||||
send('RUN_IMPORT', {
|
send('RUN_IMPORT', {
|
||||||
@@ -344,7 +346,16 @@ export default function SwisstopoApp() {
|
|||||||
<Icon name="stacks" size={13} /> Schichtenmodell aus Höhenlinien
|
<Icon name="stacks" size={13} /> Schichtenmodell aus Höhenlinien
|
||||||
</label>
|
</label>
|
||||||
</Field>
|
</Field>
|
||||||
{(getContours || getContourTin || getContourSchicht) && (
|
<Field label=""
|
||||||
|
hint="Patch-Terrain: NURBS-Surface gefittet durch alle Höhenlinien (Rhinos Patch-Befehl). Glatte, kontinuierliche Topo-Oberfläche — die kanonische Methode für Terrain aus Konturen.">
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: 6,
|
||||||
|
fontSize: 11, cursor: 'pointer' }}>
|
||||||
|
<input type="checkbox" checked={getContourPatch}
|
||||||
|
onChange={(e) => setGetContourPatch(e.target.checked)} />
|
||||||
|
<Icon name="landscape" size={13} /> Patch-Terrain aus Höhenlinien
|
||||||
|
</label>
|
||||||
|
</Field>
|
||||||
|
{(getContours || getContourTin || getContourSchicht || getContourPatch) && (
|
||||||
<Field label="HÖHEN-ABSTAND">
|
<Field label="HÖHEN-ABSTAND">
|
||||||
<Radio value={contourInt}
|
<Radio value={contourInt}
|
||||||
options={[
|
options={[
|
||||||
|
|||||||
@@ -263,15 +263,20 @@ function EbeneRow({ e, depth, hasChildren, expanded, onToggleExpand, active, mod
|
|||||||
minHeight: 24,
|
minHeight: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Chevron sitzt visuell weiter rechts (marginLeft) — marginRight
|
||||||
|
kompensiert das, damit die nachfolgenden Elemente (Auge, Code,
|
||||||
|
Farbe, Name) nicht mitrutschen. Spacer fuer kinderlose Zeilen
|
||||||
|
spiegelt dasselbe Offset, sonst springt die Eye-Spalte zwischen
|
||||||
|
Parent- und Leaf-Zeilen. */}
|
||||||
{hasChildren ? (
|
{hasChildren ? (
|
||||||
<button
|
<button
|
||||||
className="btn-icon-xs"
|
className="btn-icon-xs"
|
||||||
onClick={(ev) => { ev.stopPropagation(); onToggleExpand() }}
|
onClick={(ev) => { ev.stopPropagation(); onToggleExpand() }}
|
||||||
title={expanded ? 'Einklappen' : 'Aufklappen'}
|
title={expanded ? 'Einklappen' : 'Aufklappen'}
|
||||||
style={{ width: 12, height: 12 }}
|
style={{ width: 12, height: 12, marginLeft: 6, marginRight: -6 }}
|
||||||
><Icon name={expanded ? 'expand_more' : 'chevron_right'} size={11} /></button>
|
><Icon name={expanded ? 'expand_more' : 'chevron_right'} size={11} /></button>
|
||||||
) : (
|
) : (
|
||||||
<span style={{ width: 12, flexShrink: 0 }} />
|
<span style={{ width: 12, flexShrink: 0, marginLeft: 6, marginRight: -6 }} />
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
className={`btn-icon-sm ${eyeOn ? 'is-on' : ''}`}
|
||||||
|
|||||||
+20
-4
@@ -161,17 +161,33 @@ input, select {
|
|||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--r);
|
border-radius: 999px;
|
||||||
padding: 5px 8px;
|
padding: 4px 12px;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 0.16s, box-shadow 0.16s;
|
transition: border-color 0.16s, background 0.16s, box-shadow 0.16s;
|
||||||
|
}
|
||||||
|
input:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--bg-item-hover);
|
||||||
}
|
}
|
||||||
input:hover { border-color: var(--text-muted); }
|
|
||||||
input:focus, select:focus {
|
input:focus, select:focus {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 0 0 2px var(--accent-dim);
|
box-shadow: 0 0 0 2px var(--accent-dim);
|
||||||
}
|
}
|
||||||
input[type="number"]::-webkit-inner-spin-button { opacity: 0.3; }
|
input[type="number"]::-webkit-inner-spin-button { opacity: 0.3; }
|
||||||
|
/* Checkboxes + Color-Picker: kein Pill — native rendering. */
|
||||||
|
input[type="checkbox"], input[type="radio"], input[type="color"],
|
||||||
|
input[type="file"], input[type="range"] {
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
input[type="checkbox"]:hover, input[type="radio"]:hover,
|
||||||
|
input[type="color"]:hover, input[type="file"]:hover,
|
||||||
|
input[type="range"]:hover {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
/* Pill-shaped select */
|
/* Pill-shaped select */
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ export function openKameraPanel() { send('OPEN_KAMERA_PANEL', {}) }
|
|||||||
export function openElementeUebersicht() { send('OPEN_ELEMENTE_UEBERSICHT', {}) }
|
export function openElementeUebersicht() { send('OPEN_ELEMENTE_UEBERSICHT', {}) }
|
||||||
export function openElementeProperties() { send('OPEN_ELEMENTE_PROPERTIES', {}) }
|
export function openElementeProperties() { send('OPEN_ELEMENTE_PROPERTIES', {}) }
|
||||||
export function setDarstellung(d) { send('SET_DARSTELLUNG', { darstellung: d || '' }) }
|
export function setDarstellung(d) { send('SET_DARSTELLUNG', { darstellung: d || '' }) }
|
||||||
|
// Anordnen — 2D-Z-Stack via Rhino-DisplayOrder. dir: 'front'|'forward'|'backward'|'back'
|
||||||
|
export function arrangeSelection(dir) { send('ARRANGE', { dir }) }
|
||||||
export function saveOeffStyle(name, settings) {
|
export function saveOeffStyle(name, settings) {
|
||||||
send('SAVE_OEFF_STYLE', { name, settings })
|
send('SAVE_OEFF_STYLE', { name, settings })
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user